/* $Id$ */ /* * (C) Copyright 2001-2006 Wojtek Kaniewski * Robert J. Woźny * Arkadiusz Miśkiewicz * Adam Wysocki * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License Version * 2.1 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, * USA. */ /** * \file events.c * * \brief Obsługa zdarzeń * * \todo Poprawna obsługa gg_proxy_http_only */ #include "strman.h" #include "network.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 #ifdef GG_CONFIG_HAVE_GNUTLS # include # include #endif #ifdef GG_CONFIG_HAVE_OPENSSL # include # include # include #endif /** * Zwalnia pamięć zajmowaną przez informację o zdarzeniu. * * Funkcję należy wywoływać za każdym razem gdy funkcja biblioteki zwróci * strukturę \c gg_event. * * \param e Struktura zdarzenia * * \ingroup events */ void gg_event_free(struct gg_event *e) { gg_debug(GG_DEBUG_FUNCTION, "** gg_event_free(%p);\n", e); if (!e) return; switch (e->type) { case GG_EVENT_MSG: case GG_EVENT_MULTILOGON_MSG: free(e->event.msg.message); free(e->event.msg.formats); free(e->event.msg.recipients); free(e->event.msg.xhtml_message); break; case GG_EVENT_NOTIFY: free(e->event.notify); break; case GG_EVENT_NOTIFY60: { int i; for (i = 0; e->event.notify60[i].uin; i++) free(e->event.notify60[i].descr); free(e->event.notify60); break; } case GG_EVENT_STATUS60: free(e->event.status60.descr); break; case GG_EVENT_STATUS: free(e->event.status.descr); break; case GG_EVENT_NOTIFY_DESCR: free(e->event.notify_descr.notify); free(e->event.notify_descr.descr); break; case GG_EVENT_DCC_VOICE_DATA: free(e->event.dcc_voice_data.data); break; case GG_EVENT_PUBDIR50_SEARCH_REPLY: case GG_EVENT_PUBDIR50_READ: case GG_EVENT_PUBDIR50_WRITE: gg_pubdir50_free(e->event.pubdir50); break; case GG_EVENT_USERLIST: free(e->event.userlist.reply); break; case GG_EVENT_IMAGE_REPLY: free(e->event.image_reply.filename); free(e->event.image_reply.image); break; case GG_EVENT_XML_EVENT: 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: { 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++) { free(e->event.user_data.users[i].attrs[j].key); free(e->event.user_data.users[i].attrs[j].value); } free(e->event.user_data.users[i].attrs); } free(e->event.user_data.users); break; } case GG_EVENT_MULTILOGON_INFO: { int i; for (i = 0; i < e->event.multilogon_info.count; i++) free(e->event.multilogon_info.sessions[i].name); free(e->event.multilogon_info.sessions); break; } 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); } /** \cond internal */ /** * \internal Usuwa obrazek z kolejki do wysłania. * * \param s Struktura sesji * \param q Struktura obrazka * \param freeq Flaga zwolnienia elementu kolejki * * \return 0 jeśli się powiodło, -1 jeśli wystąpił błąd */ int gg_image_queue_remove(struct gg_session *s, struct gg_image_queue *q, int freeq) { if (!s || !q) { errno = EFAULT; return -1; } if (s->images == q) s->images = q->next; else { struct gg_image_queue *qq; for (qq = s->images; qq; qq = qq->next) { if (qq->next == q) { qq->next = q->next; break; } } } if (freeq) { free(q->image); free(q->filename); free(q); } return 0; } /** \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) (intptr_t) 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; struct in_addr *addrs; 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 */ addrs = (struct in_addr *)(void *)sess->recv_buf; for (i = 0; i < sess->recv_done / sizeof(struct in_addr); i++) { if (addrs[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(addrs[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(addrs[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 = addrs; 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) { sess->hub_addr = addr.s_addr; 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 : ""); } free(auth); free(client); 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_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[129]; 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"); 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) { #if defined(GG_CONFIG_HAVE_GNUTLS) || defined(GG_CONFIG_HAVE_OPENSSL) int valid_hostname = 0; #endif #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 defined(GG_CONFIG_HAVE_GNUTLS) || defined(GG_CONFIG_HAVE_OPENSSL) 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; #endif } 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; } gg_debug_session(sess, GG_DEBUG_MISC, "// found body!\n"); 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 (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[] = { /* style:maxlinelength:start-ignore */ { 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 }, /* style:maxlinelength:end-ignore */ }; 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 * to \c GG_EVENT_NONE, nie wydarzyło się jeszcze nic wartego odnotowania. * Strukturę zdarzenia należy zwolnić funkcja \c gg_event_free(). * * \param sess Struktura sesji * * \return Struktura zdarzenia lub \c NULL jeśli wystąpił błąd * * \ingroup events */ struct gg_event *gg_watch_fd(struct gg_session *sess) { struct gg_event *ge; struct gg_session_private *priv; gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_watch_fd(%p);\n", sess); if (sess == NULL) { errno = EFAULT; return NULL; } 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; } memset(ge, 0, sizeof(struct gg_event)); ge->type = GG_EVENT_NONE; for (;;) { unsigned int i, found = 0; gg_action_t res; res = GG_ACTION_FAIL; 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; } } 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; } if (!sess->async && ge->type == GG_EVENT_NONE && res == GG_ACTION_WAIT) res = GG_ACTION_NEXT; 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; } return ge; case GG_ACTION_NEXT: continue; case GG_ACTION_FAIL: sess->state = GG_STATE_IDLE; gg_close(sess); if (ge->event.failure != 0) { ge->type = GG_EVENT_CONN_FAILED; } else { free(ge); ge = NULL; } return ge; /* Celowo nie ma default */ } } } /* * Local variables: * c-indentation-style: k&r * c-basic-offset: 8 * indent-tabs-mode: notnil * End: * * vim: shiftwidth=8: */