diff -r fdd1f3a3c6df -r e83a87761544 libpurple/protocols/gg/lib/events.c --- a/libpurple/protocols/gg/lib/events.c Thu Feb 13 04:27:27 2014 +0100 +++ b/libpurple/protocols/gg/lib/events.c Thu Feb 13 18:29:10 2014 +0100 @@ -1,4 +1,4 @@ -/* $Id: events.c 1144 2011-07-09 15:43:00Z wojtekka $ */ +/* $Id$ */ /* * (C) Copyright 2001-2006 Wojtek Kaniewski @@ -25,25 +25,26 @@ * \file events.c * * \brief Obsługa zdarzeń + * + * \todo Poprawna obsługa gg_proxy_http_only */ -#include -#include +#include "strman.h" +#include "network.h" -#include "compat.h" #include "libgadu.h" #include "protocol.h" #include "internal.h" #include "encoding.h" #include "debug.h" #include "session.h" +#include "resolver.h" +#include "config.h" #include -#include #include #include #include -#include #include #ifdef GG_CONFIG_HAVE_GNUTLS # include @@ -52,6 +53,7 @@ #ifdef GG_CONFIG_HAVE_OPENSSL # include # include +# include #endif /** @@ -132,9 +134,14 @@ free(e->event.xml_event.data); break; + case GG_EVENT_JSON_EVENT: + free(e->event.json_event.data); + free(e->event.json_event.type); + break; + case GG_EVENT_USER_DATA: { - int i, j; + unsigned int i, j; for (i = 0; i < e->event.user_data.user_count; i++) { for (j = 0; j < e->event.user_data.users[i].attr_count; j++) { @@ -165,6 +172,14 @@ case GG_EVENT_USERLIST100_REPLY: free(e->event.userlist100_reply.reply); break; + + case GG_EVENT_IMTOKEN: + free(e->event.imtoken.imtoken); + break; + + case GG_EVENT_CHAT_INFO: + free(e->event.chat_info.participants); + break; } free(e); @@ -213,6 +228,1461 @@ /** \endcond */ /** + * \internal Inicjalizuje struktury SSL. + * + * \param gs Struktura sesji + * + * \return 0 jeśli się powiodło, -1 jeśli wystąpił błąd + */ +int gg_session_init_ssl(struct gg_session *gs) +{ +#ifdef GG_CONFIG_HAVE_GNUTLS + gg_session_gnutls_t *tmp; + + tmp = (gg_session_gnutls_t*) gs->ssl; + + if (tmp == NULL) { + tmp = malloc(sizeof(gg_session_gnutls_t)); + + if (tmp == NULL) { + gg_debug(GG_DEBUG_MISC, "// gg_session_connect() out of memory for GnuTLS session\n"); + return -1; + } + + memset(tmp, 0, sizeof(gg_session_gnutls_t)); + + gs->ssl = tmp; + + gnutls_global_init(); + gnutls_certificate_allocate_credentials(&tmp->xcred); +#ifdef GG_CONFIG_SSL_SYSTEM_TRUST +#ifdef HAVE_GNUTLS_CERTIFICATE_SET_X509_SYSTEM_TRUST + gnutls_certificate_set_x509_system_trust(tmp->xcred); +#else + gnutls_certificate_set_x509_trust_file(tmp->xcred, GG_CONFIG_GNUTLS_SYSTEM_TRUST_STORE, GNUTLS_X509_FMT_PEM); +#endif +#endif + } else { + gnutls_deinit(tmp->session); + } + + gnutls_init(&tmp->session, GNUTLS_CLIENT); + gnutls_set_default_priority(tmp->session); + gnutls_credentials_set(tmp->session, GNUTLS_CRD_CERTIFICATE, tmp->xcred); + gnutls_transport_set_ptr(tmp->session, (gnutls_transport_ptr_t) (long) gs->fd); +#endif + +#ifdef GG_CONFIG_HAVE_OPENSSL + char buf[1024]; + + OpenSSL_add_ssl_algorithms(); + + if (!RAND_status()) { + char rdata[1024]; + struct { + time_t time; + void *ptr; + } rstruct; + + time(&rstruct.time); + rstruct.ptr = (void *) &rstruct; + + RAND_seed((void *) rdata, sizeof(rdata)); + RAND_seed((void *) &rstruct, sizeof(rstruct)); + } + + if (gs->ssl_ctx == NULL) { + gs->ssl_ctx = SSL_CTX_new(SSLv3_client_method()); + + if (gs->ssl_ctx == NULL) { + ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); + gg_debug(GG_DEBUG_MISC, "// gg_session_connect() SSL_CTX_new() failed: %s\n", buf); + return -1; + } + + SSL_CTX_set_verify(gs->ssl_ctx, SSL_VERIFY_NONE, NULL); +#ifdef GG_CONFIG_SSL_SYSTEM_TRUST + SSL_CTX_set_default_verify_paths(gs->ssl_ctx); +#endif + } + + if (gs->ssl != NULL) + SSL_free(gs->ssl); + + gs->ssl = SSL_new(gs->ssl_ctx); + + if (gs->ssl == NULL) { + ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); + gg_debug(GG_DEBUG_MISC, "// gg_session_connect() SSL_new() failed: %s\n", buf); + return -1; + } + + SSL_set_fd(gs->ssl, gs->fd); +#endif + + return 0; +} + +/** + * \internal Funkcja próbuje wysłać dane zakolejkowane do wysyłki. + * + * \param sess Struktura sesji + * + * \return 0 jeśli się powiodło, -1 jeśli wystąpił błąd + */ +static int gg_send_queued_data(struct gg_session *sess) +{ + int res; + + if (sess->send_buf == NULL || sess->send_left == 0) + return 0; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sending %d bytes of queued data\n", sess->send_left); + + res = send(sess->fd, sess->send_buf, sess->send_left, 0); + + if (res == -1) { + if (errno == EAGAIN || errno == EINTR) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() non-critical send error (errno=%d, %s)\n", errno, strerror(errno)); + + return 0; + } + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() send() failed (errno=%d, %s)\n", errno, strerror(errno)); + + return -1; + } + + if (res == sess->send_left) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sent all queued data\n"); + free(sess->send_buf); + sess->send_buf = NULL; + sess->send_left = 0; + } else if (res > 0) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sent %d bytes of queued data, %d bytes left\n", res, sess->send_left - res); + + memmove(sess->send_buf, sess->send_buf + res, sess->send_left - res); + sess->send_left -= res; + } + + return 0; +} + +/** + * \internal Sprawdza wynik połączenia asynchronicznego. + * \param gs Struktura sesji + * \param res_ptr Wskaźnik na kod błędu + * \return 0 jeśli się powiodło, -1 jeśli wystąpił błąd + */ +static int gg_async_connect_failed(struct gg_session *gs, int *res_ptr) +{ + int res = 0; + socklen_t res_size = sizeof(res); + + if (!gs->async) + return 0; + + if (gs->timeout == 0) { + *res_ptr = ETIMEDOUT; + return 1; + } + + if (getsockopt(gs->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) == -1) { + *res_ptr = errno; + return 1; + } + + if (res != 0) { + *res_ptr = res; + return 1; + } + + *res_ptr = 0; + + return 0; +} + +typedef enum +{ + GG_ACTION_WAIT, + GG_ACTION_NEXT, + GG_ACTION_FAIL +} gg_action_t; + +typedef gg_action_t (*gg_state_handler_t)(struct gg_session *gs, struct gg_event *ge, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state); + +typedef struct +{ + enum gg_state_t state; + gg_state_handler_t handler; + enum gg_state_t next_state; + enum gg_state_t alt_state; + enum gg_state_t alt2_state; +} gg_state_transition_t; + +/* zwraca: + * -1 w przypadku błędu + * 0 jeżeli nie ma ustawionego specjalnego managera gniazdek + * 1 w przypadku powodzenia + */ +static int gg_handle_resolve_custom(struct gg_session *sess, enum gg_state_t next_state) +{ + struct gg_session_private *p = sess->private_data; + int is_tls = 0; + int port; + + if (p->socket_manager_type == GG_SOCKET_MANAGER_TYPE_INTERNAL) + return 0; + + if (p->socket_manager.connect_cb == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR, + "// gg_handle_resolve_custom() socket_manager.connect " + "callback is empty\n"); + return -1; + } + + if (p->socket_handle != NULL) { + gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR, + "// gg_handle_resolve_custom() socket_handle is not " + "NULL\n"); + return -1; + } + + port = sess->connect_port[sess->connect_index]; + if (next_state == GG_STATE_SEND_HUB) + port = GG_APPMSG_PORT; + + if (sess->ssl_flag != GG_SSL_DISABLED && + next_state == GG_STATE_READING_KEY) + { + /* XXX: w tej chwili nie ma możliwości łączenia się do HUBa po + * SSL, ale może będzie w przyszłości */ + is_tls = 1; + } + + if (is_tls && p->socket_manager_type == GG_SOCKET_MANAGER_TYPE_TCP) { + is_tls = 0; + next_state = GG_STATE_TLS_NEGOTIATION; + } + + if (port <= 0) { + gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR, + "// gg_handle_resolve_custom() port <= 0\n"); + return -1; + } + + p->socket_failure = 0; + p->socket_next_state = next_state; + p->socket_handle = p->socket_manager.connect_cb( + p->socket_manager.cb_data, sess->resolver_host, port, is_tls, + sess->async, sess); + + if (p->socket_failure != 0) { + if (p->socket_handle != NULL) { + gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_WARNING, + "// gg_handle_resolve_custom() handle should be" + " empty on error\n"); + } + return -1; + } + + if (p->socket_handle == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR, + "// gg_handle_resolve_custom() returned empty " + "handle\n"); + return -1; + } + + return 1; +} + +static gg_action_t gg_handle_resolve_sync(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) +{ + struct in_addr addr; + int res; + + res = gg_handle_resolve_custom(sess, alt_state); + if (res == 1) + return GG_ACTION_NEXT; + else if (res == -1) + return GG_ACTION_FAIL; + + addr.s_addr = inet_addr(sess->resolver_host); + + if (addr.s_addr == INADDR_NONE) { + struct in_addr *addr_list = NULL; + unsigned int addr_count; + + if (gg_gethostbyname_real(sess->resolver_host, &addr_list, &addr_count, 0) == -1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() host %s not found\n", sess->resolver_host); + e->event.failure = GG_FAILURE_RESOLVING; + free(addr_list); + return GG_ACTION_FAIL; + } + + sess->resolver_result = addr_list; + sess->resolver_count = addr_count; + sess->resolver_index = 0; + } else { + sess->resolver_result = malloc(sizeof(struct in_addr)); + + if (sess->resolver_result == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of memory\n"); + return GG_ACTION_FAIL; + } + + sess->resolver_result[0].s_addr = addr.s_addr; + sess->resolver_count = 1; + sess->resolver_index = 0; + } + + sess->state = next_state; + + return GG_ACTION_NEXT; +} + +static gg_action_t gg_handle_resolve_async(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) +{ + int res; + + res = gg_handle_resolve_custom(sess, alt_state); + if (res == 1) + return GG_ACTION_WAIT; + else if (res == -1) + return GG_ACTION_FAIL; + + if (sess->resolver_start(&sess->fd, &sess->resolver, sess->resolver_host) == -1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() resolving failed (errno=%d, %s)\n", errno, strerror(errno)); + e->event.failure = GG_FAILURE_RESOLVING; + return GG_ACTION_FAIL; + } + + sess->state = next_state; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + + return GG_ACTION_WAIT; +} + +static gg_action_t gg_handle_resolving(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) +{ + char buf[256]; + int count = -1; + int res; + unsigned int i; + + res = gg_resolver_recv(sess->fd, buf, sizeof(buf)); + + if (res == -1 && (errno == EAGAIN || errno == EINTR)) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() non-critical error (errno=%d, %s)\n", errno, strerror(errno)); + return GG_ACTION_WAIT; + } + + sess->resolver_cleanup(&sess->resolver, 0); + + if (res == -1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() read error (errno=%d, %s)\n", errno, strerror(errno)); + e->event.failure = GG_FAILURE_RESOLVING; + return GG_ACTION_FAIL; + } + + if (res > 0) { + char *tmp; + + tmp = realloc(sess->recv_buf, sess->recv_done + res); + + if (tmp == NULL) + return GG_ACTION_FAIL; + + sess->recv_buf = tmp; + memcpy(sess->recv_buf + sess->recv_done, buf, res); + sess->recv_done += res; + } + + /* Sprawdź, czy mamy listę zakończoną INADDR_NONE */ + + for (i = 0; i < sess->recv_done / sizeof(struct in_addr); i++) { + if (((struct in_addr*) sess->recv_buf)[i].s_addr == INADDR_NONE) { + count = i; + break; + } + } + + /* Nie znaleziono hosta */ + + if (count == 0) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() host not found\n"); + e->event.failure = GG_FAILURE_RESOLVING; + return GG_ACTION_FAIL; + } + + /* Nie mamy pełnej listy, ale połączenie zerwane */ + + if (res == 0 && count == -1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection broken\n"); + e->event.failure = GG_FAILURE_RESOLVING; + return GG_ACTION_FAIL; + } + + /* Nie mamy pełnej listy, normalna sytuacja */ + + if (count == -1) + return GG_ACTION_WAIT; + +#ifndef GG_DEBUG_DISABLE + if ((gg_debug_level & GG_DEBUG_DUMP) && (count > 0)) { + char *list; + size_t len; + + len = 0; + + for (i = 0; i < (unsigned int) count; i++) { + if (i > 0) + len += 2; + + len += strlen(inet_ntoa(((struct in_addr*) sess->recv_buf)[i])); + } + + list = malloc(len + 1); + + if (list == NULL) + return GG_ACTION_FAIL; + + list[0] = 0; + + for (i = 0; i < (unsigned int) count; i++) { + if (i > 0) + strcat(list, ", "); + + strcat(list, inet_ntoa(((struct in_addr*) sess->recv_buf)[i])); + } + + gg_debug_session(sess, GG_DEBUG_DUMP, "// gg_watch_fd() resolved: %s\n", list); + + free(list); + } +#endif + + gg_close(sess); + + sess->state = next_state; + sess->resolver_result = (struct in_addr*) sess->recv_buf; + sess->resolver_count = count; + sess->resolver_index = 0; + sess->recv_buf = NULL; + sess->recv_done = 0; + + return GG_ACTION_NEXT; +} + +static gg_action_t gg_handle_connect(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) +{ + struct in_addr addr; + int port; + + if (sess->resolver_index >= sess->resolver_count) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of addresses to connect to\n"); + e->event.failure = GG_FAILURE_CONNECTING; + return GG_ACTION_FAIL; + } + + addr = sess->resolver_result[sess->resolver_index]; + + if (sess->state == GG_STATE_CONNECT_HUB) { + port = GG_APPMSG_PORT; + } else { + sess->proxy_addr = addr.s_addr; + port = sess->proxy_port; + } + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connecting to %s:%d\n", inet_ntoa(addr), port); + + sess->fd = gg_connect(&addr, port, sess->async); + + if (sess->fd == -1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + sess->resolver_index++; + return GG_ACTION_NEXT; + } + + sess->state = next_state; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + sess->soft_timeout = 1; + + return GG_ACTION_WAIT; +} + +static gg_action_t gg_handle_connecting(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) +{ + int res; + + sess->soft_timeout = 0; + + if (gg_async_connect_failed(sess, &res)) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", res, strerror(res)); + gg_close(sess); + sess->resolver_index++; + sess->state = alt_state; + } else { + /* Z proxy zwykle łączymy się dwa razy, więc nie zwalniamy + * adresów IP po pierwszym połączeniu. */ + if (sess->state != GG_STATE_CONNECTING_PROXY_HUB) { + free(sess->resolver_result); + sess->resolver_result = NULL; + } + + sess->state = next_state; + } + + return GG_ACTION_NEXT; +} + +static gg_action_t gg_handle_connect_gg(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) +{ + struct in_addr addr; + uint16_t port; + + gg_debug_session(sess, GG_DEBUG_MISC, "resolver_index=%d, connect_index=%d, connect_port={%d,%d}\n", sess->resolver_index, sess->connect_index, sess->connect_port[0], sess->connect_port[1]); + + if ((unsigned int) sess->connect_index >= sizeof(sess->connect_port) / sizeof(sess->connect_port[0]) || sess->connect_port[sess->connect_index] == 0) { + sess->connect_index = 0; + sess->resolver_index++; + if (sess->resolver_index >= sess->resolver_count) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of addresses to connect to\n"); + e->event.failure = GG_FAILURE_CONNECTING; + return GG_ACTION_FAIL; + } + } + + addr = sess->resolver_result[sess->resolver_index]; + port = sess->connect_port[sess->connect_index]; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connecting to %s:%d\n", inet_ntoa(addr), port); + + sess->server_addr = addr.s_addr; + sess->fd = gg_connect(&addr, port, sess->async); + + if (sess->fd == -1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + sess->connect_index++; + return GG_ACTION_NEXT; + } + + sess->state = next_state; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + sess->soft_timeout = 1; + + return GG_ACTION_WAIT; +} + +static gg_action_t gg_handle_connecting_gg(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) +{ + int res; + + sess->soft_timeout = 0; + + /* jeśli wystąpił błąd podczas łączenia się... */ + if (gg_async_connect_failed(sess, &res)) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", res, strerror(res)); + gg_close(sess); + sess->connect_index++; + sess->state = alt_state; + return GG_ACTION_NEXT; + } + + free(sess->resolver_result); + sess->resolver_result = NULL; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connected\n"); + + if (sess->ssl_flag != GG_SSL_DISABLED) { + if (gg_session_init_ssl(sess) == -1) { + e->event.failure = GG_FAILURE_TLS; + return GG_ACTION_FAIL; + } + + sess->state = alt2_state; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + + return GG_ACTION_NEXT; + } else { + sess->state = next_state; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + + return GG_ACTION_WAIT; + } +} + +static gg_action_t gg_handle_send_hub(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) +{ + char *req, *client, *auth; + const char *host; + int res; + int proxy; + size_t req_len; + + if (sess->client_version != NULL && isdigit(sess->client_version[0])) + client = gg_urlencode(sess->client_version); + else if (sess->protocol_version <= GG_PROTOCOL_VERSION_100) + client = gg_urlencode(GG_DEFAULT_CLIENT_VERSION_100); + else /* sess->protocol_version >= GG_PROTOCOL_VERSION_110 */ + client = gg_urlencode(GG_DEFAULT_CLIENT_VERSION_110); + + if (client == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of memory for client version\n"); + return GG_ACTION_FAIL; + } + + if (sess->proxy_addr && sess->proxy_port) { + host = "http://" GG_APPMSG_HOST; + proxy = 1; + } else { + host = ""; + proxy = 0; + } + + auth = gg_proxy_auth(); + + if (sess->ssl_flag != GG_SSL_DISABLED) { + req = gg_saprintf + ("GET %s/appsvc/appmsg_ver10.asp?fmnumber=%u&fmt=2&lastmsg=%d&version=%s&age=2&gender=1 HTTP/1.0\r\n" + "Connection: close\r\n" + "Host: " GG_APPMSG_HOST "\r\n" + "%s" + "\r\n", host, sess->uin, sess->last_sysmsg, client, (auth) ? auth : ""); + } else { + req = gg_saprintf + ("GET %s/appsvc/appmsg_ver8.asp?fmnumber=%u&fmt=2&lastmsg=%d&version=%s HTTP/1.0\r\n" + "Host: " GG_APPMSG_HOST "\r\n" + "%s" + "\r\n", host, sess->uin, sess->last_sysmsg, client, (auth) ? auth : ""); + } + + if (req == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of memory\n"); + e->event.failure = GG_FAILURE_PROXY; + return GG_ACTION_FAIL; + } + + req_len = strlen(req); + + free(auth); + free(client); + + gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// sending http query:\n%s", req); + + res = send(sess->fd, req, req_len, 0); + + free(req); + + if (res == -1 && errno != EINTR && errno != EAGAIN) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sending query failed\n"); + e->event.failure = (!proxy) ? GG_FAILURE_HUB : GG_FAILURE_PROXY; + return GG_ACTION_FAIL; + } + + if ((size_t) res < req_len) { + sess->state = alt_state; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + } else { + sess->state = next_state; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + } + + return GG_ACTION_WAIT; +} + +static gg_action_t gg_handle_sending_hub_proxy(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) +{ + if (gg_send_queued_data(sess) == -1) { + e->event.failure = GG_FAILURE_WRITING; + return GG_ACTION_FAIL; + } + + if (sess->send_left > 0) + return GG_ACTION_WAIT; + + sess->state = next_state; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + + return GG_ACTION_WAIT; +} + +static gg_action_t gg_handle_reading_hub_proxy(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) +{ + char buf[1024], *tmp, host[128]; + int port = GG_DEFAULT_PORT; + int reply; + const char *body; + struct in_addr addr; + int res; + char **host_white; + char *host_white_default[] = GG_DEFAULT_HOST_WHITE_LIST; + + res = recv(sess->fd, buf, sizeof(buf), 0); + + if (res == -1 && (errno == EAGAIN || errno == EINTR)) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() non-critical recv error (errno=%d, %s)\n", errno, strerror(errno)); + return GG_ACTION_WAIT; + } + + if (res == -1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() recv error (errno=%d, %s)\n", errno, strerror(errno)); + e->event.failure = GG_FAILURE_CONNECTING; + return GG_ACTION_FAIL; + } + + if (res != 0) { + tmp = realloc(sess->recv_buf, sess->recv_done + res + 1); + + if (tmp == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() not enough memory for http reply\n"); + return GG_ACTION_FAIL; + } + + sess->recv_buf = tmp; + memcpy(sess->recv_buf + sess->recv_done, buf, res); + sess->recv_done += res; + sess->recv_buf[sess->recv_done] = 0; + } + + if (res == 0 && sess->recv_buf == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection closed\n"); + e->event.failure = GG_FAILURE_CONNECTING; + return GG_ACTION_FAIL; + } + + if (res != 0) + return GG_ACTION_WAIT; + + gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// received http reply:\n%s", sess->recv_buf); + + res = sscanf(sess->recv_buf, "HTTP/1.%*d %3d ", &reply); + + /* sprawdzamy, czy wszystko w porządku. */ + if (res != 1 || reply != 200) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() invalid http reply, connection failed\n"); + e->event.failure = GG_FAILURE_CONNECTING; + return GG_ACTION_FAIL; + } + + /* szukamy początku treści */ + body = strstr(sess->recv_buf, "\r\n\r\n"); + + if (body == NULL) { + body = strstr(sess->recv_buf, "\n\n"); + + if (body == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() can't find body\n"); + e->event.failure = GG_FAILURE_CONNECTING; + return GG_ACTION_FAIL; + } else { + body += 2; + } + } else { + body += 4; + } + + // 17591 0 91.197.13.71:8074 91.197.13.71 + res = sscanf(body, "%d %*d %128s", &reply, host); + + if (res != 2) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() invalid hub reply, connection failed\n"); + e->event.failure = GG_FAILURE_CONNECTING; + return GG_ACTION_FAIL; + } + + gg_debug_session(sess, GG_DEBUG_MISC, "reply=%d, host=\"%s\"\n", reply, host); + + /* jeśli pierwsza liczba w linii nie jest równa zeru, + * oznacza to, że mamy wiadomość systemową. */ + if (reply != 0) { + tmp = strchr(body, '\n'); + + if (tmp != NULL) { + e->type = GG_EVENT_MSG; + e->event.msg.msgclass = reply; + e->event.msg.sender = 0; + e->event.msg.message = (unsigned char*) strdup(tmp + 1); + + if (e->event.msg.message == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() not enough memory for system message\n"); + return GG_ACTION_FAIL; + } + } + } + + gg_close(sess); + + tmp = strchr(host, ':'); + + if (tmp != NULL) { + *tmp = 0; + port = atoi(tmp + 1); + } + + if (strcmp(host, "notoperating") == 0) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() service unavailable\n", errno, strerror(errno)); + e->event.failure = GG_FAILURE_UNAVAILABLE; + return GG_ACTION_FAIL; + } + + addr.s_addr = inet_addr(host); + if (addr.s_addr == INADDR_NONE) + addr.s_addr = 0; + sess->server_addr = addr.s_addr; + + free(sess->recv_buf); + sess->recv_buf = NULL; + sess->recv_done = 0; + + if (sess->state != GG_STATE_READING_PROXY_HUB) { + if (sess->port == 0) { + sess->connect_port[0] = port; + sess->connect_port[1] = (port != GG_HTTPS_PORT) ? GG_HTTPS_PORT : 0; + } else { + sess->connect_port[0] = sess->port; + sess->connect_port[1] = 0; + } + } else { + sess->connect_port[0] = (sess->port == 0) ? GG_HTTPS_PORT : sess->port; + sess->connect_port[1] = 0; + } + + free(sess->connect_host); + sess->connect_host = strdup(host); + + if (sess->connect_host == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() not enough memory\n"); + return GG_ACTION_FAIL; + } + + host_white = sess->private_data->host_white_list; + if (!host_white) + host_white = host_white_default; + + if (sess->ssl_flag == GG_SSL_REQUIRED && host_white[0] != NULL) { + int host_ok = 0; + char **it; + int host_len; + + host_len = strlen(sess->connect_host); + + for (it = host_white; *it != NULL; it++) { + const char *white = *it; + int white_len, dom_offset; + + white_len = strlen(white); + if (white_len > host_len) + continue; + + dom_offset = host_len - white_len; + if (strncasecmp(sess->connect_host + dom_offset, white, + white_len) != 0) + { + continue; + } + + if (white_len < host_len) { + if (sess->connect_host[dom_offset - 1] != '.') + continue; + } + + host_ok = 1; + break; + } + + if (!host_ok) { + gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR, + "// gg_watch_fd() the HUB server returned " + "a host that is not trusted (%s)\n", + sess->connect_host); + e->event.failure = GG_FAILURE_TLS; + return GG_ACTION_FAIL; + } + } + + if (sess->state == GG_STATE_READING_HUB) + sess->resolver_host = sess->connect_host; + + /* Jeśli łączymy się przez proxy, zacznijmy od początku listy */ + sess->resolver_index = 0; + + sess->state = (sess->async) ? next_state : alt_state; + + return GG_ACTION_NEXT; +} + +static gg_action_t gg_handle_send_proxy_gg(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) +{ + char *req, *auth; + size_t req_len; + int res; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() %s\n", gg_debug_state(sess->state)); + + if (sess->connect_index > 1 || sess->connect_port[sess->connect_index] == 0) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of connection candidates\n"); + e->event.failure = GG_FAILURE_CONNECTING; + return GG_ACTION_FAIL; + } + + auth = gg_proxy_auth(); + + req = gg_saprintf("CONNECT %s:%d HTTP/1.0\r\n%s\r\n", sess->connect_host, sess->connect_port[sess->connect_index], (auth) ? auth : ""); + + free(auth); + + sess->connect_index++; + + if (req == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of memory\n"); + e->event.failure = GG_FAILURE_PROXY; + return GG_ACTION_FAIL; + } + + req_len = strlen(req); + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() proxy request:\n%s", req); + + res = send(sess->fd, req, req_len, 0); + + free(req); + + if (res == -1 && errno != EINTR && errno != EAGAIN) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sending query failed\n"); + e->event.failure = GG_FAILURE_PROXY; + return GG_ACTION_FAIL; + } + + if ((size_t) res < req_len) { + sess->state = alt_state; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + } else { + sess->state = next_state; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + } + + return GG_ACTION_WAIT; +} + +static gg_action_t gg_handle_tls_negotiation(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) +{ + int valid_hostname = 0; + +#ifdef GG_CONFIG_HAVE_GNUTLS + unsigned int status; + int res; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_TLS_NEGOTIATION\n"); + + for (;;) { + res = gnutls_handshake(GG_SESSION_GNUTLS(sess)); + + if (res == GNUTLS_E_AGAIN) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS handshake GNUTLS_E_AGAIN\n"); + + if (gnutls_record_get_direction(GG_SESSION_GNUTLS(sess)) == 0) + sess->check = GG_CHECK_READ; + else + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + return GG_ACTION_WAIT; + } + + if (res == GNUTLS_E_INTERRUPTED) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS handshake GNUTLS_E_INTERRUPTED\n"); + continue; + } + + if (res != GNUTLS_E_SUCCESS) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS handshake error: %d, %s\n", res, gnutls_strerror(res)); + e->event.failure = GG_FAILURE_TLS; + return GG_ACTION_FAIL; + } + + break; + } + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS negotiation succeded:\n"); + gg_debug_session(sess, GG_DEBUG_MISC, "// cipher: VERS-%s:%s:%s:%s:COMP-%s\n", + gnutls_protocol_get_name(gnutls_protocol_get_version(GG_SESSION_GNUTLS(sess))), + gnutls_cipher_get_name(gnutls_cipher_get(GG_SESSION_GNUTLS(sess))), + gnutls_kx_get_name(gnutls_kx_get(GG_SESSION_GNUTLS(sess))), + gnutls_mac_get_name(gnutls_mac_get(GG_SESSION_GNUTLS(sess))), + gnutls_compression_get_name(gnutls_compression_get(GG_SESSION_GNUTLS(sess)))); + + if (gnutls_certificate_type_get(GG_SESSION_GNUTLS(sess)) == GNUTLS_CRT_X509) { + unsigned int peer_count; + const gnutls_datum_t *peers; + gnutls_x509_crt_t cert; + + if (gnutls_x509_crt_init(&cert) == 0) { + peers = gnutls_certificate_get_peers(GG_SESSION_GNUTLS(sess), &peer_count); + + if (peers != NULL) { + char buf[256]; + size_t size; + + if (gnutls_x509_crt_import(cert, &peers[0], GNUTLS_X509_FMT_DER) == 0) { + size = sizeof(buf); + gnutls_x509_crt_get_dn(cert, buf, &size); + gg_debug_session(sess, GG_DEBUG_MISC, "// cert subject: %s\n", buf); + size = sizeof(buf); + gnutls_x509_crt_get_issuer_dn(cert, buf, &size); + gg_debug_session(sess, GG_DEBUG_MISC, "// cert issuer: %s\n", buf); + + if (gnutls_x509_crt_check_hostname(cert, sess->connect_host) != 0) + valid_hostname = 1; + } + } + + gnutls_x509_crt_deinit(cert); + } + } + + res = gnutls_certificate_verify_peers2(GG_SESSION_GNUTLS(sess), &status); + + if (res != 0 || status != 0) { + gg_debug_session(sess, GG_DEBUG_MISC, "//   WARNING! unable to verify peer certificate: 0x%x, %d, %s\n", status, res, gnutls_strerror(res)); + + if (sess->ssl_flag == GG_SSL_REQUIRED) { + e->event.failure = GG_FAILURE_TLS; + return GG_ACTION_FAIL; + } + } else { + gg_debug_session(sess, GG_DEBUG_MISC, "// verified peer certificate\n"); + } + + +#elif defined GG_CONFIG_HAVE_OPENSSL + + X509 *peer; + int res; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() %s\n", gg_debug_state(sess->state)); + + res = SSL_connect(GG_SESSION_OPENSSL(sess)); + + if (res <= 0) { + int err; + + err = SSL_get_error(GG_SESSION_OPENSSL(sess), res); + + if (res == 0) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() disconnected during TLS negotiation\n"); + e->event.failure = GG_FAILURE_TLS; + return GG_ACTION_FAIL; + } + + if (err == SSL_ERROR_WANT_READ) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() wants to read\n"); + + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + return GG_ACTION_WAIT; + } else if (err == SSL_ERROR_WANT_WRITE) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() wants to write\n"); + + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + return GG_ACTION_WAIT; + } else { + char buf[256]; + + ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() bailed out: %s\n", buf); + + e->event.failure = GG_FAILURE_TLS; + return GG_ACTION_FAIL; + } + } + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS negotiation succeded:\n// cipher: %s\n", SSL_get_cipher_name(GG_SESSION_OPENSSL(sess))); + + peer = SSL_get_peer_certificate(GG_SESSION_OPENSSL(sess)); + + if (peer == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// WARNING! unable to get peer certificate!\n"); + + if (sess->ssl_flag == GG_SSL_REQUIRED) { + e->event.failure = GG_FAILURE_TLS; + return GG_ACTION_FAIL; + } + } else { + char buf[256]; + long res; + + X509_NAME_oneline(X509_get_subject_name(peer), buf, sizeof(buf)); + gg_debug_session(sess, GG_DEBUG_MISC, "// cert subject: %s\n", buf); + + X509_NAME_oneline(X509_get_issuer_name(peer), buf, sizeof(buf)); + gg_debug_session(sess, GG_DEBUG_MISC, "// cert issuer: %s\n", buf); + + res = SSL_get_verify_result(GG_SESSION_OPENSSL(sess)); + + if (res != X509_V_OK) { + gg_debug_session(sess, GG_DEBUG_MISC, "//   WARNING! unable to verify peer certificate! res=%ld\n", res); + + if (sess->ssl_flag == GG_SSL_REQUIRED) { + e->event.failure = GG_FAILURE_TLS; + return GG_ACTION_FAIL; + } + } else { + gg_debug_session(sess, GG_DEBUG_MISC, "// verified peer certificate\n"); + } + + if (X509_NAME_get_text_by_NID(X509_get_subject_name(peer), NID_commonName, buf, sizeof(buf)) == -1) + buf[0] = 0; + + /* Obsługa certyfikatów z wieloznacznikiem */ + if (strchr(buf, '*') == buf && strchr(buf + 1, '*') == NULL) { + char *tmp; + + tmp = strchr(sess->connect_host, '.'); + + if (tmp != NULL) + valid_hostname = (strcasecmp(tmp, buf + 1) == 0); + } else { + valid_hostname = (strcasecmp(sess->connect_host, buf) == 0); + } + } + +#else + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() no SSL support\n"); + e->event.failure = GG_FAILURE_TLS; + return GG_ACTION_FAIL; + +#endif + + if (!valid_hostname) { + gg_debug_session(sess, GG_DEBUG_MISC, "//   WARNING! unable to verify hostname\n"); + + if (sess->ssl_flag == GG_SSL_REQUIRED) { + e->event.failure = GG_FAILURE_TLS; + return GG_ACTION_FAIL; + } + } + + sess->state = next_state; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + + return GG_ACTION_WAIT; +} + +static gg_action_t gg_handle_reading_proxy_gg(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) +{ + char buf[256]; + int res; + int reply; + char *body; + + res = recv(sess->fd, buf, sizeof(buf), 0); + + gg_debug_session(sess, GG_DEBUG_MISC, "recv() = %d\n", res); + + if (res == -1 && (errno == EAGAIN || errno == EINTR)) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() non-critical recv error (errno=%d, %s)\n", errno, strerror(errno)); + return GG_ACTION_WAIT; + } + + if (res == -1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() recv error (errno=%d, %s)\n", errno, strerror(errno)); + e->event.failure = GG_FAILURE_CONNECTING; + return GG_ACTION_FAIL; + } + + if (res != 0) { + char *tmp; + + tmp = realloc(sess->recv_buf, sess->recv_done + res + 1); + + if (tmp == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() not enough memory for http reply\n"); + return GG_ACTION_FAIL; + } + + sess->recv_buf = tmp; + memcpy(sess->recv_buf + sess->recv_done, buf, res); + sess->recv_done += res; + sess->recv_buf[sess->recv_done] = 0; + } + + if (res == 0 && sess->recv_buf == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection closed\n"); + e->event.failure = GG_FAILURE_CONNECTING; + return GG_ACTION_FAIL; + } + + /* szukamy początku treści */ + body = strstr(sess->recv_buf, "\r\n\r\n"); + + if (body == NULL) { + body = strstr(sess->recv_buf, "\n\n"); + + if (body == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() can't find body\n"); + e->event.failure = GG_FAILURE_CONNECTING; + return GG_ACTION_FAIL; + } else { + body += 2; + } + } else { + body += 4; + } + + if (res != 0 && body == NULL) + return GG_ACTION_WAIT; + + gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// received proxy reply:\n%s\n", sess->recv_buf); + + res = sscanf(sess->recv_buf, "HTTP/1.%*d %3d ", &reply); + + gg_debug_session(sess, GG_DEBUG_MISC, "res = %d, reply = %d\n", res, reply); + + /* sprawdzamy, czy wszystko w porządku. */ + if (res != 1 || reply != 200) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() invalid http reply, connection failed\n"); + e->event.failure = GG_FAILURE_CONNECTING; + return GG_ACTION_FAIL; + } + + if (body == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() can't find body\n"); + e->event.failure = GG_FAILURE_CONNECTING; + return GG_ACTION_FAIL; + } + + gg_debug_session(sess, GG_DEBUG_MISC, "// found body!\n"); + + if (sess->ssl_flag != GG_SSL_DISABLED) { + if (gg_session_init_ssl(sess) == -1) { + e->event.failure = GG_FAILURE_TLS; + return GG_ACTION_FAIL; + } + + /* Teoretycznie SSL jest inicjowany przez klienta, więc serwer + * nie powinien niczego wysłać. */ + if (sess->recv_buf + sess->recv_done > body) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() unexpected SSL data\n"); + e->event.failure = GG_FAILURE_TLS; + return GG_ACTION_FAIL; + } + + free(sess->recv_buf); + sess->recv_buf = NULL; + sess->recv_done = 0; + + sess->state = alt_state; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + + return GG_ACTION_WAIT; + } + + sess->state = next_state; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; /* Pierwszy pakiet musi przyjść */ + + // Jeśli zbuforowaliśmy za dużo, przeanalizuj + + if (sess->recv_buf + sess->recv_done > body) { + sess->recv_done = sess->recv_done - (body - sess->recv_buf); + memmove(sess->recv_buf, body, sess->recv_done); + sess->state = alt2_state; + return GG_ACTION_NEXT; + } else { + free(sess->recv_buf); + sess->recv_buf = NULL; + sess->recv_done = 0; + } + + return GG_ACTION_WAIT; +} + +static gg_action_t gg_handle_connected(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) +{ +#if 0 + char buf[1024]; + int res; + + if (gg_send_queued_data(sess) == -1) + return GG_ACTION_FAIL; + + res = gg_read(sess, buf, sizeof(buf)); + + if (res == -1 && (errno == EAGAIN || errno == EINTR)) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() non-critical read error (errno=%d, %s)\n", errno, strerror(errno)); + return GG_ACTION_WAIT; + } + + if (res == -1 || res == 0) { + if (res == -1) + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() read error (errno=%d, %s)\n", errno, strerror(errno)); + else + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection closed\n"); + + if (sess->state == GG_STATE_DISCONNECTING && res == 0) { + e->type = GG_EVENT_DISCONNECT_ACK; + } else if (sess->state == GG_STATE_READING_KEY) { + e->event.failure = GG_FAILURE_INVALID; + return GG_ACTION_FAIL; + } + + return GG_ACTION_FAIL; + } + + gg_debug_dump(sess, GG_DEBUG_DUMP, buf, res); + + if (gg_session_handle_data(sess, buf, res, e) == -1) + return GG_ACTION_FAIL; + + if (sess->send_buf != NULL) + sess->check |= GG_CHECK_WRITE; + + return GG_ACTION_WAIT; +#else + struct gg_header *gh; + + if (gg_send_queued_data(sess) == -1) + return GG_ACTION_FAIL; + + gh = gg_recv_packet(sess); + + if (gh == NULL) { + if (sess->state == GG_STATE_DISCONNECTING) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection broken expectedly\n"); + e->type = GG_EVENT_DISCONNECT_ACK; + return GG_ACTION_WAIT; + } + + if (errno != EAGAIN) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() gg_recv_packet failed (errno=%d, %s)\n", errno, strerror(errno)); + return GG_ACTION_FAIL; + } + } else { + if (gg_session_handle_packet(sess, gh->type, (const char *) gh + sizeof(struct gg_header), gh->length, e) == -1) { + free(gh); + return GG_ACTION_FAIL; + } + + free(gh); + } + + sess->check = GG_CHECK_READ; + + if (sess->send_buf != NULL) + sess->check |= GG_CHECK_WRITE; + + return GG_ACTION_WAIT; +#endif +} + +static gg_action_t gg_handle_error(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) +{ + struct gg_session_private *p = sess->private_data; + + gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_handle_error() failure=%d\n", p->socket_failure); + + e->event.failure = p->socket_failure; + + return GG_ACTION_FAIL; +} + +static const gg_state_transition_t handlers[] = +{ + { GG_STATE_RESOLVE_HUB_SYNC, gg_handle_resolve_sync, GG_STATE_CONNECT_HUB, GG_STATE_SEND_HUB, 0 }, + { GG_STATE_RESOLVE_GG_SYNC, gg_handle_resolve_sync, GG_STATE_CONNECT_GG, GG_STATE_READING_KEY, 0 }, + { GG_STATE_RESOLVE_PROXY_HUB_SYNC, gg_handle_resolve_sync, GG_STATE_CONNECT_PROXY_HUB, GG_STATE_SEND_PROXY_HUB, 0 }, + { GG_STATE_RESOLVE_PROXY_GG_SYNC, gg_handle_resolve_sync, GG_STATE_CONNECT_PROXY_GG, GG_STATE_SEND_PROXY_GG, 0 }, + + { GG_STATE_RESOLVE_HUB_ASYNC, gg_handle_resolve_async, GG_STATE_RESOLVING_HUB, GG_STATE_SEND_HUB, 0 }, + { GG_STATE_RESOLVE_GG_ASYNC, gg_handle_resolve_async, GG_STATE_RESOLVING_GG, GG_STATE_READING_KEY, 0 }, + { GG_STATE_RESOLVE_PROXY_HUB_ASYNC, gg_handle_resolve_async, GG_STATE_RESOLVING_PROXY_HUB, GG_STATE_SEND_PROXY_HUB, 0 }, + { GG_STATE_RESOLVE_PROXY_GG_ASYNC, gg_handle_resolve_async, GG_STATE_RESOLVING_PROXY_GG, GG_STATE_SEND_PROXY_GG, 0 }, + + { GG_STATE_RESOLVING_HUB, gg_handle_resolving, GG_STATE_CONNECT_HUB, 0, 0 }, + { GG_STATE_RESOLVING_GG, gg_handle_resolving, GG_STATE_CONNECT_GG, 0, 0 }, + { GG_STATE_RESOLVING_PROXY_HUB, gg_handle_resolving, GG_STATE_CONNECT_PROXY_HUB, 0, 0 }, + { GG_STATE_RESOLVING_PROXY_GG, gg_handle_resolving, GG_STATE_CONNECT_PROXY_GG, 0, 0 }, + + { GG_STATE_CONNECT_HUB, gg_handle_connect, GG_STATE_CONNECTING_HUB, 0, 0 }, + { GG_STATE_CONNECT_PROXY_HUB, gg_handle_connect, GG_STATE_CONNECTING_PROXY_HUB, 0, 0 }, + { GG_STATE_CONNECT_PROXY_GG, gg_handle_connect, GG_STATE_CONNECTING_PROXY_GG, 0, 0 }, + + { GG_STATE_CONNECT_GG, gg_handle_connect_gg, GG_STATE_CONNECTING_GG, 0, 0 }, + + { GG_STATE_CONNECTING_HUB, gg_handle_connecting, GG_STATE_SEND_HUB, GG_STATE_CONNECT_HUB, 0 }, + { GG_STATE_CONNECTING_PROXY_HUB, gg_handle_connecting, GG_STATE_SEND_PROXY_HUB, GG_STATE_CONNECT_PROXY_HUB, 0 }, + { GG_STATE_CONNECTING_PROXY_GG, gg_handle_connecting, GG_STATE_SEND_PROXY_GG, GG_STATE_CONNECT_PROXY_GG, 0 }, + + { GG_STATE_CONNECTING_GG, gg_handle_connecting_gg, GG_STATE_READING_KEY, GG_STATE_CONNECT_GG, GG_STATE_TLS_NEGOTIATION }, + + { GG_STATE_SEND_HUB, gg_handle_send_hub, GG_STATE_READING_HUB, GG_STATE_SENDING_HUB, 0 }, + { GG_STATE_SEND_PROXY_HUB, gg_handle_send_hub, GG_STATE_READING_PROXY_HUB, GG_STATE_SENDING_PROXY_HUB, 0 }, + + { GG_STATE_SEND_PROXY_GG, gg_handle_send_proxy_gg, GG_STATE_READING_PROXY_GG, GG_STATE_SENDING_PROXY_GG, 0 }, + + { GG_STATE_SENDING_HUB, gg_handle_sending_hub_proxy, GG_STATE_READING_HUB, 0, 0 }, + { GG_STATE_SENDING_PROXY_HUB, gg_handle_sending_hub_proxy, GG_STATE_READING_PROXY_HUB, 0, 0 }, + { GG_STATE_SENDING_PROXY_GG, gg_handle_sending_hub_proxy, GG_STATE_READING_PROXY_GG, 0, 0 }, + + { GG_STATE_READING_HUB, gg_handle_reading_hub_proxy, GG_STATE_RESOLVE_GG_ASYNC, GG_STATE_RESOLVE_GG_SYNC, 0 }, + { GG_STATE_READING_PROXY_HUB, gg_handle_reading_hub_proxy, GG_STATE_CONNECT_PROXY_GG, GG_STATE_CONNECT_PROXY_GG, 0 }, + + { GG_STATE_READING_PROXY_GG, gg_handle_reading_proxy_gg, GG_STATE_READING_KEY, GG_STATE_TLS_NEGOTIATION, GG_STATE_READING_KEY }, + + { GG_STATE_TLS_NEGOTIATION, gg_handle_tls_negotiation, GG_STATE_READING_KEY, 0, 0 }, + + { GG_STATE_READING_KEY, gg_handle_connected, 0, 0, 0 }, + { GG_STATE_READING_REPLY, gg_handle_connected, 0, 0, 0 }, + { GG_STATE_CONNECTED, gg_handle_connected, 0, 0, 0 }, + { GG_STATE_DISCONNECTING, gg_handle_connected, 0, 0, 0 }, + { GG_STATE_ERROR, gg_handle_error, 0, 0, 0 }, +}; + +struct gg_event *gg_eventqueue_add(struct gg_session *sess) +{ + struct gg_event *ge; + gg_eventqueue_t *queue_el, *it; + + queue_el = gg_new0(sizeof(gg_eventqueue_t)); + ge = gg_new0(sizeof(struct gg_event)); + + if (queue_el == NULL || ge == NULL) { + free(queue_el); + free(ge); + return NULL; + } + + ge->type = GG_EVENT_NONE; + + queue_el->event = ge; + if (sess->private_data->event_queue == NULL) + sess->private_data->event_queue = queue_el; + else { + it = sess->private_data->event_queue; + while (it->next != NULL) + it = it->next; + it->next = queue_el; + } + + return ge; +} + +/** * Funkcja wywoływana po zaobserwowaniu zmian na deskryptorze sesji. * * Funkcja zwraca strukturę zdarzenia \c gg_event. Jeśli rodzaj zdarzenia @@ -227,787 +1697,99 @@ */ struct gg_event *gg_watch_fd(struct gg_session *sess) { - struct gg_event *e; - int res = 0; - int port = 0; - int errno2 = 0; + struct gg_event *ge; + struct gg_session_private *priv; gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_watch_fd(%p);\n", sess); - if (!sess) { + if (sess == NULL) { errno = EFAULT; return NULL; } - if (!(e = (void*) calloc(1, sizeof(*e)))) { + priv = sess->private_data; + + if (priv->event_queue != NULL) { + gg_eventqueue_t *next; + + ge = priv->event_queue->event; + next = priv->event_queue->next; + free(priv->event_queue); + priv->event_queue = next; + + if (next == NULL) { + sess->check = priv->check_after_queue; + sess->fd = priv->fd_after_queue; + } + return ge; + } + + ge = malloc(sizeof(struct gg_event)); + + if (ge == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() not enough memory for event data\n"); return NULL; } - e->type = GG_EVENT_NONE; - - if (sess->send_buf && (sess->state == GG_STATE_READING_REPLY || sess->state == GG_STATE_CONNECTED)) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sending %d bytes of queued data\n", sess->send_left); - - res = write(sess->fd, sess->send_buf, sess->send_left); - - if (res == -1 && errno != EAGAIN) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() write() failed (errno=%d, %s)\n", errno, strerror(errno)); - - if (sess->state == GG_STATE_READING_REPLY) - e->event.failure = GG_FAILURE_CONNECTING; - - goto fail; - } + memset(ge, 0, sizeof(struct gg_event)); - if (res == sess->send_left) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sent all queued data\n"); - free(sess->send_buf); - sess->send_buf = NULL; - sess->send_left = 0; - } else if (res > 0) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sent %d bytes of queued data, %d bytes left\n", res, sess->send_left - res); - - memmove(sess->send_buf, sess->send_buf + res, sess->send_left - res); - sess->send_left -= res; - } - - res = 0; - } - - switch (sess->state) { - case GG_STATE_RESOLVING: - { - struct in_addr addr; - int failed = 0; - - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_RESOLVING\n"); + ge->type = GG_EVENT_NONE; - if (read(sess->fd, &addr, sizeof(addr)) < (signed)sizeof(addr) || addr.s_addr == INADDR_NONE) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() resolving failed\n"); - failed = 1; - errno2 = errno; - } - - close(sess->fd); - sess->fd = -1; - - sess->resolver_cleanup(&sess->resolver, 0); - - if (failed) { - errno = errno2; - goto fail_proxy_hub; - } - - /* jeśli jesteśmy w resolverze i mamy ustawiony port - * proxy, znaczy, że resolvowaliśmy proxy. zatem - * wpiszmy jego adres. */ - if (sess->proxy_port) - sess->proxy_addr = addr.s_addr; - - /* zapiszmy sobie adres huba i adres serwera (do - * bezpośredniego połączenia, jeśli hub leży) - * z resolvera. */ - if (sess->proxy_addr && sess->proxy_port) - port = sess->proxy_port; - else { - sess->server_addr = sess->hub_addr = addr.s_addr; - port = GG_APPMSG_PORT; - } - - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() resolved, connecting to %s:%d\n", inet_ntoa(addr), port); - - /* łączymy się albo z hubem, albo z proxy, zależnie - * od tego, co resolvowaliśmy. */ - if ((sess->fd = gg_connect(&addr, port, sess->async)) == -1) { - /* jeśli w trybie asynchronicznym gg_connect() - * zwróci błąd, nie ma sensu próbować dalej. */ - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), critical\n", errno, strerror(errno)); - goto fail_proxy_hub; - } + for (;;) { + unsigned int i, found = 0; + gg_action_t res; - /* jeśli podano serwer i łączmy się przez proxy, - * jest to bezpośrednie połączenie, inaczej jest - * do huba. */ - - if (sess->proxy_addr && sess->proxy_port && sess->server_addr) { - sess->state = GG_STATE_CONNECTING_GG; - sess->soft_timeout = 1; - } else - sess->state = GG_STATE_CONNECTING_HUB; - - sess->check = GG_CHECK_WRITE; - sess->timeout = GG_DEFAULT_TIMEOUT; - - break; - } - - case GG_STATE_CONNECTING_HUB: - { - char buf[1024], *client, *auth; - int res = 0; - socklen_t res_size = sizeof(res); - const char *host; - - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTING_HUB\n"); - - /* jeśli asynchroniczne, sprawdzamy, czy nie wystąpił - * przypadkiem jakiś błąd. */ - if (sess->async && (getsockopt(sess->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection to %s failed (errno=%d, %s)\n", (sess->proxy_addr && sess->proxy_port) ? "proxy" : "hub", res, strerror(res)); - goto fail_proxy_hub; - } - - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connected to hub, sending query\n"); - - if (sess->client_version != NULL && isdigit(sess->client_version[0])) - client = gg_urlencode(sess->client_version); - else - client = gg_urlencode(GG_DEFAULT_CLIENT_VERSION); - - if (client == NULL) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of memory for client version\n"); - goto fail; - } + res = GG_ACTION_FAIL; - if (!gg_proxy_http_only && sess->proxy_addr && sess->proxy_port) - host = "http://" GG_APPMSG_HOST; - else - host = ""; - - auth = gg_proxy_auth(); - -#if defined(GG_CONFIG_HAVE_GNUTLS) || defined(GG_CONFIG_HAVE_OPENSSL) - if (sess->ssl != NULL) { - snprintf(buf, sizeof(buf) - 1, - "GET %s/appsvc/appmsg_ver10.asp?fmnumber=%u&fmt=2&lastmsg=%d&version=%s&age=2&gender=1 HTTP/1.0\r\n" - "Connection: close\r\n" - "Host: " GG_APPMSG_HOST "\r\n" - "%s" - "\r\n", host, sess->uin, sess->last_sysmsg, client, (auth) ? auth : ""); - } else -#endif - { - snprintf(buf, sizeof(buf) - 1, - "GET %s/appsvc/appmsg_ver8.asp?fmnumber=%u&fmt=2&lastmsg=%d&version=%s HTTP/1.0\r\n" - "Host: " GG_APPMSG_HOST "\r\n" - "%s" - "\r\n", host, sess->uin, sess->last_sysmsg, client, (auth) ? auth : ""); + for (i = 0; i < sizeof(handlers) / sizeof(handlers[0]); i++) { + if (handlers[i].state == (enum gg_state_t) sess->state) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() %s\n", gg_debug_state(sess->state)); + res = (*handlers[i].handler)(sess, ge, handlers[i].next_state, handlers[i].alt_state, handlers[i].alt2_state); + found = 1; + break; } - - free(auth); - free(client); - - gg_debug_session(sess, GG_DEBUG_MISC, "=> -----BEGIN-HTTP-QUERY-----\n%s\n=> -----END-HTTP-QUERY-----\n", buf); - - /* zapytanie jest krótkie, więc zawsze zmieści się - * do bufora gniazda. jeśli write() zwróci mniej, - * stało się coś złego. */ - if (write(sess->fd, buf, strlen(buf)) < (signed)strlen(buf)) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sending query failed\n"); - goto fail_proxy_hub; - } - - sess->state = GG_STATE_READING_DATA; - sess->check = GG_CHECK_READ; - sess->timeout = GG_DEFAULT_TIMEOUT; - - break; } - case GG_STATE_READING_DATA: - { - char buf[1024], *tmp, *host; - int port = GG_DEFAULT_PORT; - struct in_addr addr; - - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_DATA\n"); - - /* czytamy linię z gniazda i obcinamy \r\n. */ - gg_read_line(sess->fd, buf, sizeof(buf) - 1); - gg_chomp(buf); - gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// gg_watch_fd() received http header (%s)\n", buf); - - /* sprawdzamy, czy wszystko w porządku. */ - if (strncmp(buf, "HTTP/1.", 7) || strncmp(buf + 9, "200", 3)) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() invalid http reply, connection failed\n"); - goto fail_proxy_hub; - } - - /* ignorujemy resztę nagłówka. */ - while (strcmp(buf, "\r\n") && strcmp(buf, "")) - gg_read_line(sess->fd, buf, sizeof(buf) - 1); - - /* czytamy pierwszą linię danych. */ - if (gg_read_line(sess->fd, buf, sizeof(buf) - 1) == NULL) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() read error\n"); - goto fail_proxy_hub; - } - gg_chomp(buf); - - /* jeśli pierwsza liczba w linii nie jest równa zeru, - * oznacza to, że mamy wiadomość systemową. */ - if (atoi(buf)) { - char tmp[1024], *foo, *sysmsg_buf = NULL; - int len = 0; - - while (gg_read_line(sess->fd, tmp, sizeof(tmp) - 1)) { - if (!(foo = realloc(sysmsg_buf, len + strlen(tmp) + 2))) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of memory for system message, ignoring\n"); - break; - } - - sysmsg_buf = foo; - - if (!len) - strcpy(sysmsg_buf, tmp); - else - strcat(sysmsg_buf, tmp); - - len += strlen(tmp); - } - - e->type = GG_EVENT_MSG; - e->event.msg.msgclass = atoi(buf); - e->event.msg.sender = 0; - e->event.msg.message = (unsigned char*) sysmsg_buf; - } - - close(sess->fd); - sess->fd = -1; - - gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// gg_watch_fd() received http data (%s)\n", buf); - - /* analizujemy otrzymane dane. */ - tmp = buf; - - while (*tmp && *tmp != ' ') - tmp++; - while (*tmp && *tmp == ' ') - tmp++; - while (*tmp && *tmp != ' ') - tmp++; - while (*tmp && *tmp == ' ') - tmp++; - host = tmp; - while (*tmp && *tmp != ' ') - tmp++; - *tmp = 0; - - if ((tmp = strchr(host, ':'))) { - *tmp = 0; - port = atoi(tmp + 1); - } - - if (strcmp(host, "") == 0) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() invalid response\n"); - e->event.failure = GG_FAILURE_HUB; - goto fail; - } - - if (!strcmp(host, "notoperating")) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() service unavailable\n", errno, strerror(errno)); - e->event.failure = GG_FAILURE_UNAVAILABLE; - goto fail; - } - - addr.s_addr = inet_addr(host); - sess->server_addr = addr.s_addr; - - if (!gg_proxy_http_only && sess->proxy_addr && sess->proxy_port) { - /* jeśli mamy proxy, łączymy się z nim. */ - if ((sess->fd = gg_connect(&sess->proxy_addr, sess->proxy_port, sess->async)) == -1) { - /* nie wyszło? trudno. */ - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection to proxy failed (errno=%d, %s)\n", errno, strerror(errno)); - e->event.failure = GG_FAILURE_PROXY; - goto fail; - } - - sess->state = GG_STATE_CONNECTING_GG; - sess->check = GG_CHECK_WRITE; - sess->timeout = GG_DEFAULT_TIMEOUT; - sess->soft_timeout = 1; - break; - } - - sess->port = port; - - /* Jeśli podano nazwę, nie adres serwera... */ - if (sess->server_addr == INADDR_NONE) { - if (sess->resolver_start(&sess->fd, &sess->resolver, host) == -1) { - gg_debug(GG_DEBUG_MISC, "// gg_login() resolving failed (errno=%d, %s)\n", errno, strerror(errno)); - goto fail; - } - - sess->state = GG_STATE_RESOLVING_GG; - sess->check = GG_CHECK_READ; - sess->timeout = GG_DEFAULT_TIMEOUT; - break; - } - - /* łączymy się z właściwym serwerem. */ - if ((sess->fd = gg_connect(&addr, sess->port, sess->async)) == -1) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), trying https\n", errno, strerror(errno)); - - sess->port = GG_HTTPS_PORT; - - /* nie wyszło? próbujemy portu 443. */ - if ((sess->fd = gg_connect(&addr, GG_HTTPS_PORT, sess->async)) == -1) { - /* ostatnia deska ratunku zawiodła? - * w takim razie zwijamy manatki. */ - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno)); - e->event.failure = GG_FAILURE_CONNECTING; - goto fail; - } - } - - sess->state = GG_STATE_CONNECTING_GG; - sess->check = GG_CHECK_WRITE; - sess->timeout = GG_DEFAULT_TIMEOUT; - sess->soft_timeout = 1; - - break; - } - - case GG_STATE_RESOLVING_GG: - { - struct in_addr addr; - int failed = 0; - - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_RESOLVING_GG\n"); - - if (read(sess->fd, &addr, sizeof(addr)) < (signed)sizeof(addr) || addr.s_addr == INADDR_NONE) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() resolving failed\n"); - failed = 1; - errno2 = errno; - } - - close(sess->fd); - sess->fd = -1; - - sess->resolver_cleanup(&sess->resolver, 0); - - if (failed) { - errno = errno2; - e->event.failure = GG_FAILURE_RESOLVING; - goto fail; - } - - sess->server_addr = addr.s_addr; - - /* łączymy się z właściwym serwerem. */ - if ((sess->fd = gg_connect(&addr, sess->port, sess->async)) == -1) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), trying https\n", errno, strerror(errno)); - - sess->port = GG_HTTPS_PORT; - - /* nie wyszło? próbujemy portu 443. */ - if ((sess->fd = gg_connect(&addr, GG_HTTPS_PORT, sess->async)) == -1) { - /* ostatnia deska ratunku zawiodła? - * w takim razie zwijamy manatki. */ - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno)); - e->event.failure = GG_FAILURE_CONNECTING; - goto fail; - } - } - - sess->state = GG_STATE_CONNECTING_GG; - sess->check = GG_CHECK_WRITE; - sess->timeout = GG_DEFAULT_TIMEOUT; - sess->soft_timeout = 1; - - break; + if (!found) { + gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_watch_fd() invalid state %s\n", gg_debug_state(sess->state)); + ge->event.failure = GG_FAILURE_INTERNAL; } - case GG_STATE_CONNECTING_GG: - { - int res = 0; - socklen_t res_size = sizeof(res); - - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTING_GG\n"); - - sess->soft_timeout = 0; - - /* jeśli wystąpił błąd podczas łączenia się... */ - if (sess->async && (sess->timeout == 0 || getsockopt(sess->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) { - /* jeśli nie udało się połączenie z proxy, - * nie mamy czego próbować więcej. */ - if (sess->proxy_addr && sess->proxy_port) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection to proxy failed (errno=%d, %s)\n", res, strerror(res)); - e->event.failure = GG_FAILURE_PROXY; - goto fail; - } - - close(sess->fd); - sess->fd = -1; - -#ifdef ETIMEDOUT - if (sess->timeout == 0) - errno = ETIMEDOUT; -#endif - -#if defined(GG_CONFIG_HAVE_GNUTLS) || defined(GG_CONFIG_HAVE_OPENSSL) - /* jeśli logujemy się po TLS, nie próbujemy - * się łączyć już z niczym innym w przypadku - * błędu. nie dość, że nie ma sensu, to i - * trzeba by się bawić w tworzenie na nowo - * SSL i SSL_CTX. */ - - if (sess->ssl) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", res, strerror(res)); - e->event.failure = GG_FAILURE_CONNECTING; - goto fail; - } -#endif - - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), trying https\n", res, strerror(res)); - - if (sess->port == GG_HTTPS_PORT) { - e->event.failure = GG_FAILURE_CONNECTING; - goto fail; - } - - sess->port = GG_HTTPS_PORT; + if (!sess->async && ge->type == GG_EVENT_NONE && res == GG_ACTION_WAIT) + res = GG_ACTION_NEXT; - /* próbujemy na port 443. */ - if ((sess->fd = gg_connect(&sess->server_addr, sess->port, sess->async)) == -1) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno)); - e->event.failure = GG_FAILURE_CONNECTING; - goto fail; + switch (res) { + case GG_ACTION_WAIT: + if (priv->event_queue != NULL) { + priv->fd_after_queue = sess->fd; + priv->check_after_queue = sess->check; + /* wymuszamy ponowne wywołanie gg_watch_fd */ + sess->fd = gg_get_dummy_fd(sess); + if (sess->fd < 0) + sess->fd = priv->fd_after_queue; + sess->check = GG_CHECK_READ | GG_CHECK_WRITE; } - - sess->state = GG_STATE_CONNECTING_GG; - sess->check = GG_CHECK_WRITE; - sess->timeout = GG_DEFAULT_TIMEOUT; - sess->soft_timeout = 1; - - break; - } - - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connected\n"); - - if (gg_proxy_http_only) - sess->proxy_port = 0; - - /* jeśli mamy proxy, wyślijmy zapytanie. */ - if (sess->proxy_addr && sess->proxy_port) { - char buf[100], *auth = gg_proxy_auth(); - struct in_addr addr; + return ge; - if (sess->server_addr) - addr.s_addr = sess->server_addr; - else - addr.s_addr = sess->hub_addr; + case GG_ACTION_NEXT: + continue; - snprintf(buf, sizeof(buf), "CONNECT %s:%d HTTP/1.0\r\n", inet_ntoa(addr), sess->port); - - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() proxy request:\n// %s", buf); + case GG_ACTION_FAIL: + sess->state = GG_STATE_IDLE; - /* wysyłamy zapytanie. jest ono na tyle krótkie, - * że musi się zmieścić w buforze gniazda. jeśli - * write() zawiedzie, stało się coś złego. */ - if (write(sess->fd, buf, strlen(buf)) < (signed)strlen(buf)) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() can't send proxy request\n"); - free(auth); - e->event.failure = GG_FAILURE_PROXY; - goto fail; - } + gg_close(sess); - if (auth) { - gg_debug_session(sess, GG_DEBUG_MISC, "// %s", auth); - if (write(sess->fd, auth, strlen(auth)) < (signed)strlen(auth)) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() can't send proxy request\n"); - free(auth); - e->event.failure = GG_FAILURE_PROXY; - goto fail; - } - - free(auth); + if (ge->event.failure != 0) { + ge->type = GG_EVENT_CONN_FAILED; + } else { + free(ge); + ge = NULL; } - if (write(sess->fd, "\r\n", 2) < 2) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() can't send proxy request\n"); - e->event.failure = GG_FAILURE_PROXY; - goto fail; - } - } - -#if defined(GG_CONFIG_HAVE_GNUTLS) || defined(GG_CONFIG_HAVE_OPENSSL) - if (sess->ssl != NULL) { -#ifdef GG_CONFIG_HAVE_GNUTLS - gnutls_transport_set_ptr(GG_SESSION_GNUTLS(sess), (gnutls_transport_ptr_t)(long)sess->fd); -#endif -#ifdef GG_CONFIG_HAVE_OPENSSL - SSL_set_fd(sess->ssl, sess->fd); -#endif - - sess->state = GG_STATE_TLS_NEGOTIATION; - sess->check = GG_CHECK_WRITE; - sess->timeout = GG_DEFAULT_TIMEOUT; - - break; - } -#endif - - sess->state = GG_STATE_READING_KEY; - sess->check = GG_CHECK_READ; - sess->timeout = GG_DEFAULT_TIMEOUT; - - break; - } - -#ifdef GG_CONFIG_HAVE_GNUTLS - case GG_STATE_TLS_NEGOTIATION: - { - int res; - - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_TLS_NEGOTIATION\n"); - -gnutls_handshake_repeat: - res = gnutls_handshake(GG_SESSION_GNUTLS(sess)); - - if (res == GNUTLS_E_AGAIN) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS handshake GNUTLS_E_AGAIN\n"); - - sess->state = GG_STATE_TLS_NEGOTIATION; - if (gnutls_record_get_direction(GG_SESSION_GNUTLS(sess)) == 0) - sess->check = GG_CHECK_READ; - else - sess->check = GG_CHECK_WRITE; - sess->timeout = GG_DEFAULT_TIMEOUT; - break; - } - - if (res == GNUTLS_E_INTERRUPTED) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS handshake GNUTLS_E_INTERRUPTED\n"); - goto gnutls_handshake_repeat; - } - - if (res != 0) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS handshake error %d\n", res); - e->type = GG_EVENT_CONN_FAILED; - e->event.failure = GG_FAILURE_TLS; - sess->state = GG_STATE_IDLE; - close(sess->fd); - sess->fd = -1; - break; - } - - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS negotiation succeded:\n"); - gg_debug_session(sess, GG_DEBUG_MISC, "// cipher: VERS-%s:%s:%s:%s:COMP-%s\n", - gnutls_protocol_get_name(gnutls_protocol_get_version(GG_SESSION_GNUTLS(sess))), - gnutls_cipher_get_name(gnutls_cipher_get(GG_SESSION_GNUTLS(sess))), - gnutls_kx_get_name(gnutls_kx_get(GG_SESSION_GNUTLS(sess))), - gnutls_mac_get_name(gnutls_mac_get(GG_SESSION_GNUTLS(sess))), - gnutls_compression_get_name(gnutls_compression_get(GG_SESSION_GNUTLS(sess)))); - - if (gnutls_certificate_type_get(GG_SESSION_GNUTLS(sess)) == GNUTLS_CRT_X509) { - unsigned int peer_count; - const gnutls_datum_t *peers; - gnutls_x509_crt_t cert; - - if (gnutls_x509_crt_init(&cert) == 0) { - peers = gnutls_certificate_get_peers(GG_SESSION_GNUTLS(sess), &peer_count); - - if (peers != NULL) { - char buf[256]; - size_t size; - - if (gnutls_x509_crt_import(cert, &peers[0], GNUTLS_X509_FMT_DER) == 0) { - size = sizeof(buf); - gnutls_x509_crt_get_dn(cert, buf, &size); - gg_debug_session(sess, GG_DEBUG_MISC, "// cert subject: %s\n", buf); - size = sizeof(buf); - gnutls_x509_crt_get_issuer_dn(cert, buf, &size); - gg_debug_session(sess, GG_DEBUG_MISC, "// cert issuer: %s\n", buf); - } - } - - gnutls_x509_crt_deinit(cert); - } - } - - sess->state = GG_STATE_READING_KEY; - sess->check = GG_CHECK_READ; - sess->timeout = GG_DEFAULT_TIMEOUT; - - break; - } -#endif - -#ifdef GG_CONFIG_HAVE_OPENSSL - case GG_STATE_TLS_NEGOTIATION: - { - int res; - X509 *peer; - - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_TLS_NEGOTIATION\n"); - - if ((res = SSL_connect(sess->ssl)) <= 0) { - int err = SSL_get_error(sess->ssl, res); - - if (res == 0) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() disconnected during TLS negotiation\n"); - - e->type = GG_EVENT_CONN_FAILED; - e->event.failure = GG_FAILURE_TLS; - sess->state = GG_STATE_IDLE; - close(sess->fd); - sess->fd = -1; - break; - } - - if (err == SSL_ERROR_WANT_READ) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() wants to read\n"); - - sess->state = GG_STATE_TLS_NEGOTIATION; - sess->check = GG_CHECK_READ; - sess->timeout = GG_DEFAULT_TIMEOUT; + return ge; - break; - } else if (err == SSL_ERROR_WANT_WRITE) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() wants to write\n"); - - sess->state = GG_STATE_TLS_NEGOTIATION; - sess->check = GG_CHECK_WRITE; - sess->timeout = GG_DEFAULT_TIMEOUT; - - break; - } else { - char buf[256]; - - ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); - - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() bailed out: %s\n", buf); - - e->type = GG_EVENT_CONN_FAILED; - e->event.failure = GG_FAILURE_TLS; - sess->state = GG_STATE_IDLE; - close(sess->fd); - sess->fd = -1; - break; - } - } - - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS negotiation succeded:\n// cipher: %s\n", SSL_get_cipher_name(sess->ssl)); - - peer = SSL_get_peer_certificate(sess->ssl); - - if (!peer) - gg_debug_session(sess, GG_DEBUG_MISC, "// WARNING! unable to get peer certificate!\n"); - else { - char buf[256]; - - X509_NAME_oneline(X509_get_subject_name(peer), buf, sizeof(buf)); - gg_debug_session(sess, GG_DEBUG_MISC, "// cert subject: %s\n", buf); - - X509_NAME_oneline(X509_get_issuer_name(peer), buf, sizeof(buf)); - gg_debug_session(sess, GG_DEBUG_MISC, "// cert issuer: %s\n", buf); - } - - sess->state = GG_STATE_READING_KEY; - sess->check = GG_CHECK_READ; - sess->timeout = GG_DEFAULT_TIMEOUT; - - break; + /* Celowo nie ma default */ } -#endif - - case GG_STATE_READING_KEY: - case GG_STATE_READING_REPLY: - case GG_STATE_CONNECTED: - case GG_STATE_DISCONNECTING: - { - struct gg_header *gh; - - if (sess->state == GG_STATE_READING_KEY) - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_KEY\n"); - else if (sess->state == GG_STATE_READING_REPLY) - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_REPLY\n"); - else if (sess->state == GG_STATE_CONNECTED) - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTED\n"); - else if (sess->state == GG_STATE_DISCONNECTING) - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_DISCONNECTING\n"); - - /* XXX bardzo, bardzo, bardzo głupi pomysł na pozbycie - * się tekstu wrzucanego przez proxy. */ - if (sess->state == GG_STATE_READING_KEY && sess->proxy_addr && sess->proxy_port) { - char buf[100]; - - strcpy(buf, ""); - gg_read_line(sess->fd, buf, sizeof(buf) - 1); - gg_chomp(buf); - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() proxy response:\n// %s\n", buf); - - while (strcmp(buf, "")) { - gg_read_line(sess->fd, buf, sizeof(buf) - 1); - gg_chomp(buf); - if (strcmp(buf, "")) - gg_debug_session(sess, GG_DEBUG_MISC, "// %s\n", buf); - } - - /* XXX niech czeka jeszcze raz w tej samej - * fazie. głupio, ale działa. */ - sess->proxy_port = 0; - - break; - } - - sess->last_event = time(NULL); - - gh = gg_recv_packet(sess); - - if (gh == NULL) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() gg_recv_packet failed (errno=%d, %s)\n", errno, strerror(errno)); - - if (errno != EAGAIN) - goto fail; - } else { - if (gg_session_handle_packet(sess, gh->type, (const char *) gh + sizeof(struct gg_header), gh->length, e) == -1) { - free(gh); - goto fail; - } - - free(gh); - } - - sess->check = GG_CHECK_READ; - - break; - } - } - - if (sess->send_buf && (sess->state == GG_STATE_READING_REPLY || sess->state == GG_STATE_CONNECTED)) - sess->check |= GG_CHECK_WRITE; - - return e; - -fail_proxy_hub: - if (sess->proxy_port) - e->event.failure = GG_FAILURE_PROXY; - else - e->event.failure = GG_FAILURE_HUB; - -fail: - sess->resolver_cleanup(&sess->resolver, 1); - - sess->state = GG_STATE_IDLE; - - if (sess->fd != -1) { - int errno2; - - errno2 = errno; - close(sess->fd); - errno = errno2; - sess->fd = -1; - } - - if (e->event.failure != 0) { - e->type = GG_EVENT_CONN_FAILED; - return e; - } else { - free(e); - return NULL; } }