/** * Funkcja wywoływana po zaobserwowaniu zmian na deskryptorze połączenia. * * Operacja będzie zakończona, gdy pole \c state będzie równe * \c GG_STATE_PARSING. W tym miejscu działanie przejmuje zwykle funkcja * korzystająca z \c gg_http_watch_fd(). W przypadku błędu połączenia, * pole \c state będzie równe \c GG_STATE_ERROR, a kod błędu znajdzie się * w polu \c error. * * \param h Struktura połączenia * * \return \return 0 jeśli się powiodło, -1 w przypadku błędu * * \ingroup http */ int gg_http_watch_fd(struct gg_http *h) { gg_debug(GG_DEBUG_FUNCTION, "** gg_http_watch_fd(%p);\n", h); if (!h) { gg_debug(GG_DEBUG_MISC, "// gg_http_watch_fd() invalid arguments\n"); errno = EFAULT; return -1; } if (h->state == GG_STATE_RESOLVING) { struct in_addr a; gg_debug(GG_DEBUG_MISC, "=> http, resolving done\n"); if (gg_sock_read(h->fd, &a, sizeof(a)) < (signed)sizeof(a) || a.s_addr == INADDR_NONE) { gg_debug(GG_DEBUG_MISC, "=> http, resolver thread failed\n"); gg_http_error(GG_ERROR_RESOLVING); } gg_sock_close(h->fd); h->fd = -1; h->resolver_cleanup(&h->resolver, 0); gg_debug(GG_DEBUG_MISC, "=> http, connecting to %s:%d\n", inet_ntoa(a), h->port); if ((h->fd = gg_connect(&a, h->port, h->async)) == -1) { gg_debug(GG_DEBUG_MISC, "=> http, connection failed (errno=%d, %s)\n", errno, strerror(errno)); gg_http_error(GG_ERROR_CONNECTING); } h->state = GG_STATE_CONNECTING; h->check = GG_CHECK_WRITE; h->timeout = GG_DEFAULT_TIMEOUT; return 0; } if (h->state == GG_STATE_CONNECTING) { int res = 0; unsigned int res_size = sizeof(res); if (h->async && (gg_getsockopt(h->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) { gg_debug(GG_DEBUG_MISC, "=> http, async connection failed (errno=%d, %s)\n", (res) ? res : errno , strerror((res) ? res : errno)); gg_sock_close(h->fd); h->fd = -1; h->state = GG_STATE_ERROR; h->error = GG_ERROR_CONNECTING; if (res) errno = res; return 0; } gg_debug(GG_DEBUG_MISC, "=> http, connected, sending request\n"); h->state = GG_STATE_SENDING_QUERY; } if (h->state == GG_STATE_SENDING_QUERY) { int res; if ((res = gg_sock_write(h->fd, h->query, strlen(h->query))) < 1) { gg_debug(GG_DEBUG_MISC, "=> http, write() failed (len=%d, res=%d, errno=%d)\n", strlen(h->query), res, errno); gg_http_error(GG_ERROR_WRITING); } if (res < (int)strlen(h->query)) { gg_debug(GG_DEBUG_MISC, "=> http, partial header sent (led=%d, sent=%d)\n", strlen(h->query), res); memmove(h->query, h->query + res, strlen(h->query) - res + 1); h->state = GG_STATE_SENDING_QUERY; h->check = GG_CHECK_WRITE; h->timeout = GG_DEFAULT_TIMEOUT; } else { gg_debug(GG_DEBUG_MISC, "=> http, request sent (len=%d)\n", strlen(h->query)); free(h->query); h->query = NULL; h->state = GG_STATE_READING_HEADER; h->check = GG_CHECK_READ; h->timeout = GG_DEFAULT_TIMEOUT; } return 0; } if (h->state == GG_STATE_READING_HEADER) { char buf[1024], *tmp; int res; if ((res = gg_sock_read(h->fd, buf, sizeof(buf))) == -1) { gg_debug(GG_DEBUG_MISC, "=> http, reading header failed (errno=%d)\n", errno); if (h->header) { free(h->header); h->header = NULL; } gg_http_error(GG_ERROR_READING); } if (!res) { gg_debug(GG_DEBUG_MISC, "=> http, connection reset by peer\n"); if (h->header) { free(h->header); h->header = NULL; } gg_http_error(GG_ERROR_READING); } gg_debug(GG_DEBUG_MISC, "=> http, read %d bytes of header\n", res); if (!(tmp = realloc(h->header, h->header_size + res + 1))) { gg_debug(GG_DEBUG_MISC, "=> http, not enough memory for header\n"); free(h->header); h->header = NULL; gg_http_error(GG_ERROR_READING); } h->header = tmp; memcpy(h->header + h->header_size, buf, res); h->header_size += res; gg_debug(GG_DEBUG_MISC, "=> http, header_buf=%p, header_size=%d\n", h->header, h->header_size); h->header[h->header_size] = 0; if ((tmp = strstr(h->header, "\r\n\r\n")) || (tmp = strstr(h->header, "\n\n"))) { int sep_len = (*tmp == '\r') ? 4 : 2; unsigned int left; char *line; left = h->header_size - ((long)(tmp) - (long)(h->header) + sep_len); gg_debug(GG_DEBUG_MISC, "=> http, got all header (%d bytes, %d left)\n", h->header_size - left, left); /* HTTP/1.1 200 OK */ if (strlen(h->header) < 16 || strncmp(h->header + 9, "200", 3)) { gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-HEADER-----\n%s\n=> -----END-HTTP-HEADER-----\n", h->header); gg_debug(GG_DEBUG_MISC, "=> http, didn't get 200 OK -- no results\n"); free(h->header); h->header = NULL; gg_http_error(GG_ERROR_CONNECTING); } h->body_size = 0; line = h->header; *tmp = 0; gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-HEADER-----\n%s\n=> -----END-HTTP-HEADER-----\n", h->header); while (line) { if (!strncasecmp(line, "Content-length: ", 16)) { h->body_size = atoi(line + 16); } line = strchr(line, '\n'); if (line) line++; } if (h->body_size <= 0) { gg_debug(GG_DEBUG_MISC, "=> http, content-length not found\n"); h->body_size = left; } if (left > h->body_size) { gg_debug(GG_DEBUG_MISC, "=> http, oversized reply (%d bytes needed, %d bytes left)\n", h->body_size, left); h->body_size = left; } gg_debug(GG_DEBUG_MISC, "=> http, body_size=%d\n", h->body_size); if (!(h->body = malloc(h->body_size + 1))) { gg_debug(GG_DEBUG_MISC, "=> http, not enough memory (%d bytes for body_buf)\n", h->body_size + 1); free(h->header); h->header = NULL; gg_http_error(GG_ERROR_READING); } if (left) { memcpy(h->body, tmp + sep_len, left); h->body_done = left; } h->body[left] = 0; h->state = GG_STATE_READING_DATA; h->check = GG_CHECK_READ; h->timeout = GG_DEFAULT_TIMEOUT; } return 0; } if (h->state == GG_STATE_READING_DATA) { char buf[1024]; int res; if ((res = gg_sock_read(h->fd, buf, sizeof(buf))) == -1) { gg_debug(GG_DEBUG_MISC, "=> http, reading body failed (errno=%d)\n", errno); if (h->body) { free(h->body); h->body = NULL; } gg_http_error(GG_ERROR_READING); } if (!res) { if (h->body_done >= h->body_size) { gg_debug(GG_DEBUG_MISC, "=> http, we're done, closing socket\n"); h->state = GG_STATE_PARSING; gg_sock_close(h->fd); h->fd = -1; } else { gg_debug(GG_DEBUG_MISC, "=> http, connection closed while reading (have %d, need %d)\n", h->body_done, h->body_size); if (h->body) { free(h->body); h->body = NULL; } gg_http_error(GG_ERROR_READING); } return 0; } gg_debug(GG_DEBUG_MISC, "=> http, read %d bytes of body\n", res); if (h->body_done + res > h->body_size) { char *tmp; gg_debug(GG_DEBUG_MISC, "=> http, too much data (%d bytes, %d needed), enlarging buffer\n", h->body_done + res, h->body_size); if (!(tmp = realloc(h->body, h->body_done + res + 1))) { gg_debug(GG_DEBUG_MISC, "=> http, not enough memory for data (%d needed)\n", h->body_done + res + 1); free(h->body); h->body = NULL; gg_http_error(GG_ERROR_READING); } h->body = tmp; h->body_size = h->body_done + res; } h->body[h->body_done + res] = 0; memcpy(h->body + h->body_done, buf, res); h->body_done += res; gg_debug(GG_DEBUG_MISC, "=> body_done=%d, body_size=%d\n", h->body_done, h->body_size); return 0; } if (h->fd != -1) gg_sock_close(h->fd); h->fd = -1; h->state = GG_STATE_ERROR; h->error = 0; return -1; }
/** * Funkcja wywoływana po zaobserwowaniu zmian na deskryptorze połączenia. * * 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 dcc Struktura połączenia * * \return Struktura zdarzenia lub \c NULL jeśli wystąpił błąd * * \ingroup dcc7 */ struct gg_event *gg_dcc7_watch_fd(struct gg_dcc7 *dcc) { struct gg_event *e; gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_FUNCTION, "** gg_dcc7_watch_fd(%p)\n", dcc); if (!dcc || (dcc->type != GG_SESSION_DCC7_SEND && dcc->type != GG_SESSION_DCC7_GET && dcc->type != GG_SESSION_DCC7_VOICE)) { gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() invalid parameters\n"); errno = EINVAL; return NULL; } if (!(e = malloc(sizeof(struct gg_event)))) { gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() not enough memory\n"); return NULL; } memset(e, 0, sizeof(struct gg_event)); e->type = GG_EVENT_NONE; switch (dcc->state) { case GG_STATE_LISTENING: { struct sockaddr_in sin; SOCKET fd; int one = 1; unsigned int sin_len = sizeof(sin); gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_LISTENING\n"); if ((fd = accept(dcc->fd, (struct sockaddr*) &sin, &sin_len)) == -1) { gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() accept() failed (%s)\n", strerror(errno)); return e; } gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() connection from %s:%d\n", inet_ntoa(sin.sin_addr), htons(sin.sin_port)); #ifdef FIONBIO if (ioctl(fd, FIONBIO, &one) == -1) { #else if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { #endif gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() can't set nonblocking (%s)\n", strerror(errno)); gg_sock_close(fd); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; e->event.dcc7_error_ex.dcc7 = dcc; return e; } gg_sock_close(dcc->fd); dcc->fd = fd; dcc->state = GG_STATE_READING_ID; dcc->check = GG_CHECK_READ; dcc->timeout = GG_DEFAULT_TIMEOUT; dcc->incoming = 1; dcc->remote_port = ntohs(sin.sin_port); dcc->remote_addr = sin.sin_addr.s_addr; e->type = GG_EVENT_DCC7_CONNECTED; e->event.dcc7_connected.dcc7 = dcc; return e; } case GG_STATE_CONNECTING: { int res = 0, error = 0; unsigned int error_size = sizeof(error); gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_CONNECTING\n"); dcc->soft_timeout = 0; if (dcc->timeout == 0) error = ETIMEDOUT; if (error || (res = gg_getsockopt(dcc->fd, SOL_SOCKET, SO_ERROR, &error, &error_size)) == -1 || error != 0) { gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() connection failed (%s)\n", (res == -1) ? strerror(errno) : strerror(error)); if (dcc->relay) { for (dcc->relay_index++; dcc->relay_index < dcc->relay_count; dcc->relay_index++) { dcc->remote_addr = dcc->relay_list[dcc->relay_index].addr; dcc->remote_port = dcc->relay_list[dcc->relay_index].port; if (gg_dcc7_connect(dcc) == 0) break; } if (dcc->relay_index >= dcc->relay_count) { gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() no relay available"); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc7_error = GG_ERROR_DCC7_RELAY; e->event.dcc7_error_ex.dcc7 = dcc; return e; } } else { if (gg_dcc7_reverse_connect(dcc) != -1) { e->type = GG_EVENT_DCC7_PENDING; e->event.dcc7_pending.dcc7 = dcc; } else { e->type = GG_EVENT_DCC7_ERROR; e->event.dcc7_error = GG_ERROR_DCC7_NET; e->event.dcc7_error_ex.dcc7 = dcc; } return e; } } gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() connected, sending id\n"); dcc->state = GG_STATE_SENDING_ID; dcc->check = GG_CHECK_WRITE; dcc->timeout = GG_DEFAULT_TIMEOUT; dcc->incoming = 0; return e; } case GG_STATE_READING_ID: { int res; gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_READING_ID\n"); if (!dcc->relay) { struct gg_dcc7_welcome_p2p welcome, welcome_ok; welcome_ok.id = dcc->cid; if ((res = gg_sock_read(dcc->fd, &welcome, sizeof(welcome))) != sizeof(welcome)) { gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() read() failed (%d, %s)\n", res, strerror(errno)); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; e->event.dcc7_error_ex.dcc7 = dcc; return e; } if (memcmp(&welcome, &welcome_ok, sizeof(welcome))) { gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() invalid id\n"); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; e->event.dcc7_error_ex.dcc7 = dcc; return e; } } else { struct gg_dcc7_welcome_server welcome, welcome_ok; welcome_ok.magic = GG_DCC7_WELCOME_SERVER; welcome_ok.id = dcc->cid; if ((res = gg_sock_read(dcc->fd, &welcome, sizeof(welcome))) != sizeof(welcome)) { gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() read() failed (%d, %s)\n", res, strerror(errno)); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; e->event.dcc7_error_ex.dcc7 = dcc; return e; } if (memcmp(&welcome, &welcome_ok, sizeof(welcome)) != 0) { gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() invalid id\n"); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; e->event.dcc7_error_ex.dcc7 = dcc; return e; } } if (dcc->incoming) { dcc->state = GG_STATE_SENDING_ID; dcc->check = GG_CHECK_WRITE; dcc->timeout = GG_DEFAULT_TIMEOUT; } else { gg_dcc7_postauth_fixup(dcc); dcc->timeout = GG_DEFAULT_TIMEOUT; } return e; } case GG_STATE_SENDING_ID: { int res; gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_SENDING_ID\n"); if (!dcc->relay) { struct gg_dcc7_welcome_p2p welcome; welcome.id = dcc->cid; if ((res = gg_sock_write(dcc->fd, &welcome, sizeof(welcome))) != sizeof(welcome)) { gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() write() failed (%d, %s)", res, strerror(errno)); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; e->event.dcc7_error_ex.dcc7 = dcc; return e; } } else { struct gg_dcc7_welcome_server welcome; welcome.magic = gg_fix32(GG_DCC7_WELCOME_SERVER); welcome.id = dcc->cid; if ((res = gg_sock_write(dcc->fd, &welcome, sizeof(welcome))) != sizeof(welcome)) { gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() write() failed (%d, %s)\n", res, strerror(errno)); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; e->event.dcc7_error_ex.dcc7 = dcc; return e; } } if (dcc->incoming) { gg_dcc7_postauth_fixup(dcc); dcc->timeout = GG_DEFAULT_TIMEOUT; } else { dcc->state = GG_STATE_READING_ID; dcc->check = GG_CHECK_READ; dcc->timeout = GG_DEFAULT_TIMEOUT; } return e; } case GG_STATE_SENDING_FILE: { char buf[1024]; int chunk, res; gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_SENDING_FILE (offset=%d, size=%d)\n", dcc->offset, dcc->size); if (dcc->offset >= dcc->size) { gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() offset >= size, finished\n"); e->type = GG_EVENT_DCC7_DONE; e->event.dcc7_done.dcc7 = dcc; return e; } if (dcc->seek && lseek(dcc->file_fd, dcc->offset, SEEK_SET) == (off_t) -1) { gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() lseek() failed (%s)\n", strerror(errno)); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc7_error = GG_ERROR_DCC7_FILE; e->event.dcc7_error_ex.dcc7 = dcc; return e; } if ((chunk = dcc->size - dcc->offset) > sizeof(buf)) chunk = sizeof(buf); if ((res = read(dcc->file_fd, buf, chunk)) < 1) { gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() read() failed (res=%d, %s)\n", res, strerror(errno)); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc7_error = (res == -1) ? GG_ERROR_DCC7_FILE : GG_ERROR_DCC7_EOF; e->event.dcc7_error_ex.dcc7 = dcc; return e; } if ((res = gg_sock_write(dcc->fd, buf, res)) == -1) { gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() write() failed (%s)\n", strerror(errno)); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc7_error = GG_ERROR_DCC7_NET; e->event.dcc7_error_ex.dcc7 = dcc; return e; } dcc->offset += res; if (dcc->offset >= dcc->size) { gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() finished\n"); e->type = GG_EVENT_DCC7_DONE; e->event.dcc7_done.dcc7 = dcc; return e; } dcc->state = GG_STATE_SENDING_FILE; dcc->check = GG_CHECK_WRITE; dcc->timeout = GG_DCC7_TIMEOUT_SEND; return e; } case GG_STATE_GETTING_FILE: { char buf[1024]; int res, wres; gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_GETTING_FILE (offset=%d, size=%d)\n", dcc->offset, dcc->size); if (dcc->offset >= dcc->size) { gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() finished\n"); e->type = GG_EVENT_DCC7_DONE; e->event.dcc7_done.dcc7 = dcc; return e; } if ((res = gg_sock_read(dcc->fd, buf, sizeof(buf))) < 1) { gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() read() failed (fd=%d, res=%d, %s)\n", dcc->fd, res, strerror(errno)); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc7_error = (res == -1) ? GG_ERROR_DCC7_NET : GG_ERROR_DCC7_EOF; e->event.dcc7_error_ex.dcc7 = dcc; return e; } // XXX zapisywać do skutku? if ((wres = write(dcc->file_fd, buf, res)) < res) { gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() write() failed (fd=%d, res=%d, %s)\n", dcc->file_fd, wres, strerror(errno)); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc7_error = GG_ERROR_DCC7_FILE; e->event.dcc7_error_ex.dcc7 = dcc; return e; } dcc->offset += res; if (dcc->offset >= dcc->size) { gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() finished\n"); e->type = GG_EVENT_DCC7_DONE; e->event.dcc7_done.dcc7 = dcc; return e; } dcc->state = GG_STATE_GETTING_FILE; dcc->check = GG_CHECK_READ; dcc->timeout = GG_DCC7_TIMEOUT_GET; return e; } case GG_STATE_RESOLVING_RELAY: { struct in_addr addr; gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_RESOLVING_RELAY\n"); if (gg_sock_read(dcc->fd, &addr, sizeof(addr)) < sizeof(addr) || addr.s_addr == INADDR_NONE) { int errno_save = errno; gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() resolving failed\n"); gg_sock_close(dcc->fd); dcc->fd = -1; dcc->sess->resolver_cleanup(&dcc->resolver, 0); errno = errno_save; e->type = GG_EVENT_DCC7_ERROR; e->event.dcc7_error = GG_ERROR_DCC7_RELAY; e->event.dcc7_error_ex.dcc7 = dcc; return e; } gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() resolved, connecting to %s:%d\n", inet_ntoa(addr), GG_RELAY_PORT); if ((dcc->fd = gg_connect(&addr, GG_RELAY_PORT, 1)) == -1) { gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() connection failed (errno=%d, %s), critical\n", errno, strerror(errno)); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc7_error = GG_ERROR_DCC7_RELAY; e->event.dcc7_error_ex.dcc7 = dcc; return e; } dcc->state = GG_STATE_CONNECTING_RELAY; dcc->check = GG_CHECK_WRITE; dcc->timeout = GG_DEFAULT_TIMEOUT; return e; } case GG_STATE_CONNECTING_RELAY: { int res; unsigned int res_size = sizeof(res); struct gg_dcc7_relay_req pkt; gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_CONNECTING_RELAY\n"); if (gg_getsockopt(dcc->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) != 0 || res != 0) { gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() connection failed (errno=%d, %s)\n", res, strerror(res)); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc7_error = GG_ERROR_DCC7_RELAY; e->event.dcc7_error_ex.dcc7 = dcc; return e; } memset(&pkt, 0, sizeof(pkt)); pkt.magic = gg_fix32(GG_DCC7_RELAY_REQUEST); pkt.len = gg_fix32(sizeof(pkt)); pkt.id = dcc->cid; pkt.type = gg_fix16(GG_DCC7_RELAY_TYPE_SERVER); pkt.dunno1 = gg_fix16(GG_DCC7_RELAY_DUNNO1); gg_debug_dump_session((dcc) ? (dcc)->sess : NULL, &pkt, sizeof(pkt), "// gg_dcc7_watch_fd() send pkt(0x%.2x)\n", gg_fix32(pkt.magic)); if ((res = gg_sock_write(dcc->fd, &pkt, sizeof(pkt))) != sizeof(pkt)) { gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() sending failed\n"); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc7_error = GG_ERROR_DCC7_RELAY; e->event.dcc7_error_ex.dcc7 = dcc; return e; } dcc->state = GG_STATE_READING_RELAY; dcc->check = GG_CHECK_READ; dcc->timeout = GG_DEFAULT_TIMEOUT; return e; } case GG_STATE_READING_RELAY: { char buf[256]; struct gg_dcc7_relay_reply *pkt; struct gg_dcc7_relay_reply_server srv; int res; int i; gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_READING_RELAY\n"); if ((res = gg_sock_read(dcc->fd, buf, sizeof(buf))) < sizeof(*pkt)) { if (res == 0) errno = ECONNRESET; gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() read() failed (%d, %s)\n", res, strerror(errno)); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc7_error = GG_ERROR_DCC7_RELAY; e->event.dcc7_error_ex.dcc7 = dcc; return e; } pkt = (struct gg_dcc7_relay_reply*) buf; if (gg_fix32(pkt->magic) != GG_DCC7_RELAY_REPLY || gg_fix32(pkt->rcount) < 1 || gg_fix32(pkt->rcount > 256) || gg_fix32(pkt->len) < sizeof(*pkt) + gg_fix32(pkt->rcount) * sizeof(srv)) { gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_wathc_fd() invalid reply\n"); errno = EINVAL; e->type = GG_EVENT_DCC7_ERROR; e->event.dcc7_error = GG_ERROR_DCC7_RELAY; e->event.dcc7_error_ex.dcc7 = dcc; return e; } gg_debug_dump_session((dcc) ? (dcc)->sess : NULL, buf, res, "// gg_dcc7_get_relay() read pkt(0x%.2x)\n", gg_fix32(pkt->magic)); free(dcc->relay_list); dcc->relay_index = 0; dcc->relay_count = gg_fix32(pkt->rcount); dcc->relay_list = malloc(dcc->relay_count * sizeof(gg_dcc7_relay_t)); if (dcc->relay_list == NULL) { gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() not enough memory"); dcc->relay_count = 0; free(e); return NULL; } for (i = 0; i < dcc->relay_count; i++) { struct in_addr addr; memcpy(&srv, buf + sizeof(*pkt) + i * sizeof(srv), sizeof(srv)); dcc->relay_list[i].addr = srv.addr; dcc->relay_list[i].port = gg_fix16(srv.port); dcc->relay_list[i].family = srv.family; addr.s_addr = srv.addr; gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// %s %d %d\n", inet_ntoa(addr), gg_fix16(srv.port), srv.family); } dcc->relay = 1; for (; dcc->relay_index < dcc->relay_count; dcc->relay_index++) { dcc->remote_addr = dcc->relay_list[dcc->relay_index].addr; dcc->remote_port = dcc->relay_list[dcc->relay_index].port; if (gg_dcc7_connect(dcc) == 0) break; } if (dcc->relay_index >= dcc->relay_count) { gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() no relay available"); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc7_error = GG_ERROR_DCC7_RELAY; e->event.dcc7_error_ex.dcc7 = dcc; return e; } return e; } default: { gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_???\n"); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; e->event.dcc7_error_ex.dcc7 = dcc; return e; } } return e; } /** * Zwalnia zasoby używane przez połączenie bezpośrednie. * * \param dcc Struktura połączenia * * \ingroup dcc7 */ void gg_dcc7_free(struct gg_dcc7 *dcc) { gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_FUNCTION, "** gg_dcc7_free(%p)\n", dcc); if (!dcc) return; if (dcc->fd != -1) gg_sock_close(dcc->fd); if (dcc->file_fd != -1) close(dcc->file_fd); if (dcc->sess) gg_dcc7_session_remove(dcc->sess, dcc); free(dcc->relay_list); free(dcc); }
int gg_gethostbyname_real(const char *hostname, struct in_addr *addr, int pthread) { #ifdef GG_CONFIG_HAVE_GETHOSTBYNAME_R char *buf = NULL; char *new_buf = NULL; struct hostent he; struct hostent *he_ptr = NULL; size_t buf_len = 1024; int result = -1; int h_errnop; int ret = 0; #ifdef GG_CONFIG_HAVE_PTHREAD int old_state; #endif #ifdef GG_CONFIG_HAVE_PTHREAD pthread_cleanup_push(gg_gethostbyname_cleaner, &buf); if (pthread) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state); #endif buf = malloc(buf_len); #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(old_state, NULL); #endif if (buf != NULL) { #ifndef sun while ((ret = gethostbyname_r(hostname, &he, buf, buf_len, &he_ptr, &h_errnop)) == ERANGE) { #else while (((he_ptr = gethostbyname_r(hostname, &he, buf, buf_len, &h_errnop)) == NULL) && (errno == ERANGE)) { #endif buf_len *= 2; #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state); #endif new_buf = realloc(buf, buf_len); if (new_buf != NULL) buf = new_buf; #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(old_state, NULL); #endif if (new_buf == NULL) { ret = ENOMEM; break; } } if (ret == 0 && he_ptr != NULL) { memcpy(addr, he_ptr->h_addr, sizeof(struct in_addr)); result = 0; } #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state); #endif free(buf); buf = NULL; #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(old_state, NULL); #endif } #ifdef GG_CONFIG_HAVE_PTHREAD pthread_cleanup_pop(1); #endif return result; #else struct hostent *he; he = gethostbyname(hostname); if (he == NULL) return -1; memcpy(addr, he->h_addr, sizeof(struct in_addr)); return 0; #endif /* GG_CONFIG_HAVE_GETHOSTBYNAME_R */ } struct in_addr *gg_gethostbyname(const char *hostname) { struct in_addr *addr; if (!(addr = malloc(sizeof(struct in_addr)))) return NULL; if (gg_gethostbyname_real(hostname, addr, 0)) { free(addr); return NULL; } return addr; } /** * \internal Struktura przekazywana do wątku rozwiązującego nazwę. */ struct gg_resolver_fork_data { int pid; /*< Identyfikator procesu */ }; static int gg_resolver_fork_start(SOCKET *fd, void **priv_data, const char *hostname) { struct gg_resolver_fork_data *data = NULL; struct in_addr addr; int new_errno; SOCKET pipes[2]; gg_debug(GG_DEBUG_FUNCTION, "** gg_resolver_fork_start(%p, %p, \"%s\");\n", fd, priv_data, hostname); if (fd == NULL || priv_data == NULL || hostname == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_fork_start() invalid arguments\n"); errno = EFAULT; return -1; } data = malloc(sizeof(struct gg_resolver_fork_data)); if (data == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_fork_start() out of memory for resolver data\n"); return -1; } if (pipe(pipes) == -1) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_fork_start() unable to create pipes (errno=%d, %s)\n", errno, strerror(errno)); free(data); return -1; } data->pid = fork(); if (data->pid == -1) { new_errno = errno; goto cleanup; } if (data->pid == 0) { gg_sock_close(pipes[0]); if ((addr.s_addr = inet_addr(hostname)) == INADDR_NONE) { /* W przypadku błędu gg_gethostbyname_real() zwróci -1 * i nie zmieni &addr. Tam jest już INADDR_NONE, * więc nie musimy robić nic więcej. */ gg_gethostbyname_real(hostname, &addr, 0); } if (gg_sock_write(pipes[1], &addr, sizeof(addr)) != sizeof(addr)) exit(1); exit(0); } gg_sock_close(pipes[1]); gg_debug(GG_DEBUG_MISC, "// gg_resolver_fork_start() %p\n", data); *fd = pipes[0]; *priv_data = data; return 0; cleanup: free(data); gg_sock_close(pipes[0]); gg_sock_close(pipes[1]); errno = new_errno; return -1; } static void gg_resolver_fork_cleanup(void **priv_data, int force) { struct gg_resolver_fork_data *data; if (priv_data == NULL || *priv_data == NULL) return; data = (struct gg_resolver_fork_data*) *priv_data; *priv_data = NULL; if (force) kill(data->pid, SIGKILL); waitpid(data->pid, NULL, WNOHANG); free(data); } #ifdef GG_CONFIG_HAVE_PTHREAD /** * \internal Struktura przekazywana do wątku rozwiązującego nazwę. */ struct gg_resolver_pthread_data { pthread_t thread; /*< Identyfikator wątku */ char *hostname; /*< Nazwa serwera */ SOCKET rfd; /*< Deskryptor do odczytu */ SOCKET wfd; /*< Deskryptor do zapisu */ }; static void gg_resolver_pthread_cleanup(void **priv_data, int force) { struct gg_resolver_pthread_data *data; if (priv_data == NULL || *priv_data == NULL) return; data = (struct gg_resolver_pthread_data *) *priv_data; *priv_data = NULL; if (force) { pthread_cancel(&data->thread); pthread_join(&data->thread, NULL); } free(data->hostname); data->hostname = NULL; if (data->wfd != -1) { gg_sock_close(data->wfd); data->wfd = -1; } free(data); } static void *__stdcall gg_resolver_pthread_thread(void *arg) { struct gg_resolver_pthread_data *data = arg; struct in_addr addr; pthread_detach(pthread_self()); if ((addr.s_addr = inet_addr(data->hostname)) == INADDR_NONE) { /* W przypadku błędu gg_gethostbyname_real() zwróci -1 * i nie zmieni &addr. Tam jest już INADDR_NONE, * więc nie musimy robić nic więcej. */ gg_gethostbyname_real(data->hostname, &addr, 1); } if (gg_sock_write(data->wfd, &addr, sizeof(addr)) == sizeof(addr)) pthread_exit(NULL); else pthread_exit((void*) -1); return NULL; /* żeby kompilator nie marudził */ } static int gg_resolver_pthread_start(SOCKET *fd, void **priv_data, const char *hostname) { struct gg_resolver_pthread_data *data = NULL; int new_errno; SOCKET pipes[2]; gg_debug(GG_DEBUG_FUNCTION, "** gg_resolver_pthread_start(%p, %p, \"%s\");\n", fd, priv_data, hostname); if (fd == NULL || priv_data == NULL || hostname == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() invalid arguments\n"); errno = EFAULT; return -1; } data = malloc(sizeof(struct gg_resolver_pthread_data)); if (data == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() out of memory for resolver data\n"); return -1; } if (pipe(pipes) == -1) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() unable to create pipes (errno=%d, %s)\n", errno, strerror(errno)); free(data); return -1; } data->hostname = strdup(hostname); if (data->hostname == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() out of memory\n"); new_errno = errno; goto cleanup; } data->rfd = pipes[0]; data->wfd = pipes[1]; if (pthread_create(&data->thread, NULL, gg_resolver_pthread_thread, data)) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() unable to create thread\n"); new_errno = errno; goto cleanup; } gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() %p\n", data); *fd = pipes[0]; *priv_data = data; return 0; cleanup: if (data) { free(data->hostname); free(data); } gg_sock_close(pipes[0]); gg_sock_close(pipes[1]); errno = new_errno; return -1; } #endif /* GG_CONFIG_HAVE_PTHREAD */ int gg_session_set_resolver(struct gg_session *gs, gg_resolver_t type) { if (gs == NULL) { errno = EINVAL; return -1; } if (type == GG_RESOLVER_DEFAULT) { if (gg_global_resolver_type != GG_RESOLVER_DEFAULT) { gs->resolver_type = gg_global_resolver_type; gs->resolver_start = gg_global_resolver_start; gs->resolver_cleanup = gg_global_resolver_cleanup; return 0; } #if !defined(GG_CONFIG_HAVE_PTHREAD) || !defined(GG_CONFIG_PTHREAD_DEFAULT) type = GG_RESOLVER_FORK; #else type = GG_RESOLVER_PTHREAD; #endif } switch (type) { case GG_RESOLVER_FORK: gs->resolver_type = type; gs->resolver_start = gg_resolver_fork_start; gs->resolver_cleanup = gg_resolver_fork_cleanup; return 0; #ifdef GG_CONFIG_HAVE_PTHREAD case GG_RESOLVER_PTHREAD: gs->resolver_type = type; gs->resolver_start = gg_resolver_pthread_start; gs->resolver_cleanup = gg_resolver_pthread_cleanup; return 0; #endif default: errno = EINVAL; return -1; } } gg_resolver_t gg_session_get_resolver(struct gg_session *gs) { if (gs == NULL) { errno = EINVAL; return GG_RESOLVER_INVALID; } return gs->resolver_type; } int gg_session_set_custom_resolver(struct gg_session *gs, int (*resolver_start)(SOCKET*, void**, const char*), void (*resolver_cleanup)(void**, int)) { if (gs == NULL || resolver_start == NULL || resolver_cleanup == NULL) { errno = EINVAL; return -1; } gs->resolver_type = GG_RESOLVER_CUSTOM; gs->resolver_start = resolver_start; gs->resolver_cleanup = resolver_cleanup; return 0; } int gg_http_set_resolver(struct gg_http *gh, gg_resolver_t type) { if (gh == NULL) { errno = EINVAL; return -1; } if (type == GG_RESOLVER_DEFAULT) { if (gg_global_resolver_type != GG_RESOLVER_DEFAULT) { gh->resolver_type = gg_global_resolver_type; gh->resolver_start = gg_global_resolver_start; gh->resolver_cleanup = gg_global_resolver_cleanup; return 0; } #if !defined(GG_CONFIG_HAVE_PTHREAD) || !defined(GG_CONFIG_PTHREAD_DEFAULT) type = GG_RESOLVER_FORK; #else type = GG_RESOLVER_PTHREAD; #endif } switch (type) { case GG_RESOLVER_FORK: gh->resolver_type = type; gh->resolver_start = gg_resolver_fork_start; gh->resolver_cleanup = gg_resolver_fork_cleanup; return 0; #ifdef GG_CONFIG_HAVE_PTHREAD case GG_RESOLVER_PTHREAD: gh->resolver_type = type; gh->resolver_start = gg_resolver_pthread_start; gh->resolver_cleanup = gg_resolver_pthread_cleanup; return 0; #endif default: errno = EINVAL; return -1; } } gg_resolver_t gg_http_get_resolver(struct gg_http *gh) { if (gh == NULL) { errno = EINVAL; return GG_RESOLVER_INVALID; } return gh->resolver_type; } int gg_http_set_custom_resolver(struct gg_http *gh, int (*resolver_start)(SOCKET*, void**, const char*), void (*resolver_cleanup)(void**, int)) { if (gh == NULL || resolver_start == NULL || resolver_cleanup == NULL) { errno = EINVAL; return -1; } gh->resolver_type = GG_RESOLVER_CUSTOM; gh->resolver_start = resolver_start; gh->resolver_cleanup = resolver_cleanup; return 0; } int gg_global_set_resolver(gg_resolver_t type) { switch (type) { case GG_RESOLVER_DEFAULT: gg_global_resolver_type = type; gg_global_resolver_start = NULL; gg_global_resolver_cleanup = NULL; return 0; case GG_RESOLVER_FORK: gg_global_resolver_type = type; gg_global_resolver_start = gg_resolver_fork_start; gg_global_resolver_cleanup = gg_resolver_fork_cleanup; return 0; #ifdef GG_CONFIG_HAVE_PTHREAD case GG_RESOLVER_PTHREAD: gg_global_resolver_type = type; gg_global_resolver_start = gg_resolver_pthread_start; gg_global_resolver_cleanup = gg_resolver_pthread_cleanup; return 0; #endif default: errno = EINVAL; return -1; } } gg_resolver_t gg_global_get_resolver(void) { return gg_global_resolver_type; } int gg_global_set_custom_resolver(int (*resolver_start)(SOCKET*, void**, const char*), void (*resolver_cleanup)(void**, int)) { if (resolver_start == NULL || resolver_cleanup == NULL) { errno = EINVAL; return -1; } gg_global_resolver_type = GG_RESOLVER_CUSTOM; gg_global_resolver_start = resolver_start; gg_global_resolver_cleanup = resolver_cleanup; return 0; }
/** * \internal Odpowiednik \c gethostbyname zapewniający współbieżność. * * Jeśli dany system dostarcza \c gethostbyname_r, używa się tej wersji, jeśli * nie, to zwykłej \c gethostbyname. * * \param hostname Nazwa serwera * \param addr Wskaźnik na rezultat rozwiązywania nazwy * \param pthread Flaga blokowania unicestwiania wątku podczas alokacji pamięci * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ int gg_gethostbyname_real(const char *hostname, struct in_addr *addr, int pthread) { #ifdef GG_CONFIG_HAVE_GETHOSTBYNAME_R char *buf = NULL; char *new_buf = NULL; struct hostent he; struct hostent *he_ptr = NULL; size_t buf_len = 1024; int result = -1; int h_errnop; int ret = 0; #ifdef GG_CONFIG_HAVE_PTHREAD int old_state; #endif #ifdef GG_CONFIG_HAVE_PTHREAD pthread_cleanup_push(gg_gethostbyname_cleaner, &buf); if (pthread) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state); #endif buf = malloc(buf_len); #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(old_state, NULL); #endif if (buf != NULL) { #ifndef sun while ((ret = gethostbyname_r(hostname, &he, buf, buf_len, &he_ptr, &h_errnop)) == ERANGE) { #else while (((he_ptr = gethostbyname_r(hostname, &he, buf, buf_len, &h_errnop)) == NULL) && (errno == ERANGE)) { #endif buf_len *= 2; #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state); #endif new_buf = realloc(buf, buf_len); if (new_buf != NULL) buf = new_buf; #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(old_state, NULL); #endif if (new_buf == NULL) { ret = ENOMEM; break; } } if (ret == 0 && he_ptr != NULL) { memcpy(addr, he_ptr->h_addr, sizeof(struct in_addr)); result = 0; } #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state); #endif free(buf); buf = NULL; #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(old_state, NULL); #endif } #ifdef GG_CONFIG_HAVE_PTHREAD pthread_cleanup_pop(1); #endif return result; #else struct hostent *he; he = gethostbyname(hostname); if (he == NULL) return -1; memcpy(addr, he->h_addr, sizeof(struct in_addr)); return 0; #endif /* GG_CONFIG_HAVE_GETHOSTBYNAME_R */ } /** * \internal Odpowiednik \c gethostbyname zapewniający współbieżność. * * Jeśli dany system dostarcza \c gethostbyname_r, używa się tej wersji, jeśli * nie, to zwykłej \c gethostbyname. * * \param hostname Nazwa serwera * * \return Zaalokowana struktura \c in_addr lub NULL w przypadku błędu. */ struct in_addr *gg_gethostbyname(const char *hostname) { struct in_addr *addr; if (!(addr = (in_addr*)malloc(sizeof(struct in_addr)))) return NULL; if (gg_gethostbyname_real(hostname, addr, 0)) { free(addr); return NULL; } return addr; } /** * \internal Struktura przekazywana do wątku rozwiązującego nazwę. */ struct gg_resolver_fork_data { int pid; /*< Identyfikator procesu */ }; /** * \internal Rozwiązuje nazwę serwera w osobnym procesie. * * Połączenia asynchroniczne nie mogą blokować procesu w trakcie rozwiązywania * nazwy serwera. W tym celu tworzony jest potok, nowy proces i dopiero w nim * przeprowadzane jest rozwiązywanie nazwy. Deskryptor strony do odczytu * zapisuje się w strukturze sieci i czeka na dane w postaci struktury * \c in_addr. Jeśli nie znaleziono nazwy, zwracana jest \c INADDR_NONE. * * \param fd Wskaźnik na zmienną, gdzie zostanie umieszczony deskryptor * potoku * \param priv_data Wskaźnik na zmienną, gdzie zostanie umieszczony wskaźnik * do numeru procesu potomnego rozwiązującego nazwę * \param hostname Nazwa serwera do rozwiązania * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ static int gg_resolver_fork_start(SOCKET *fd, void **priv_data, const char *hostname) { struct gg_resolver_fork_data *data = NULL; struct in_addr addr; int new_errno; SOCKET pipes[2]; gg_debug(GG_DEBUG_FUNCTION, "** gg_resolver_fork_start(%p, %p, \"%s\");\n", fd, priv_data, hostname); if (fd == NULL || priv_data == NULL || hostname == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_fork_start() invalid arguments\n"); errno = EFAULT; return -1; } data = (gg_resolver_fork_data*)malloc(sizeof(struct gg_resolver_fork_data)); if (data == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_fork_start() out of memory for resolver data\n"); return -1; } if (pipe(pipes) == -1) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_fork_start() unable to create pipes (errno=%d, %s)\n", errno, strerror(errno)); free(data); return -1; } data->pid = fork(); if (data->pid == -1) { new_errno = errno; goto cleanup; } if (data->pid == 0) { gg_sock_close(pipes[0]); if ((addr.s_addr = inet_addr(hostname)) == INADDR_NONE) { /* W przypadku błędu gg_gethostbyname_real() zwróci -1 * i nie zmieni &addr. Tam jest już INADDR_NONE, * więc nie musimy robić nic więcej. */ gg_gethostbyname_real(hostname, &addr, 0); } if (gg_sock_write(pipes[1], &addr, sizeof(addr)) != sizeof(addr)) exit(1); exit(0); } gg_sock_close(pipes[1]); gg_debug(GG_DEBUG_MISC, "// gg_resolver_fork_start() %p\n", data); *fd = pipes[0]; *priv_data = data; return 0; cleanup: free(data); gg_sock_close(pipes[0]); gg_sock_close(pipes[1]); errno = new_errno; return -1; } /** * \internal Usuwanie zasobów po procesie rozwiązywaniu nazwy. * * Funkcja wywoływana po zakończeniu rozwiązanywania nazwy lub przy zwalnianiu * zasobów sesji podczas rozwiązywania nazwy. * * \param priv_data Wskaźnik na zmienną przechowującą wskaźnik do prywatnych * danych * \param force Flaga usuwania zasobów przed zakończeniem działania */ static void gg_resolver_fork_cleanup(void **priv_data, int force) { struct gg_resolver_fork_data *data; if (priv_data == NULL || *priv_data == NULL) return; data = (struct gg_resolver_fork_data*) *priv_data; *priv_data = NULL; if (force) kill(data->pid, SIGKILL); waitpid(data->pid, NULL, WNOHANG); free(data); } #ifdef GG_CONFIG_HAVE_PTHREAD /** * \internal Struktura przekazywana do wątku rozwiązującego nazwę. */ struct gg_resolver_pthread_data { pthread_t thread; /*< Identyfikator wątku */ char *hostname; /*< Nazwa serwera */ SOCKET rfd; /*< Deskryptor do odczytu */ SOCKET wfd; /*< Deskryptor do zapisu */ }; /** * \internal Usuwanie zasobów po wątku rozwiązywaniu nazwy. * * Funkcja wywoływana po zakończeniu rozwiązanywania nazwy lub przy zwalnianiu * zasobów sesji podczas rozwiązywania nazwy. * * \param priv_data Wskaźnik na zmienną przechowującą wskaźnik do prywatnych * danych * \param force Flaga usuwania zasobów przed zakończeniem działania */ static void gg_resolver_pthread_cleanup(void **priv_data, int force) { struct gg_resolver_pthread_data *data; if (priv_data == NULL || *priv_data == NULL) return; data = (struct gg_resolver_pthread_data *) *priv_data; *priv_data = NULL; if (force) { pthread_cancel(&data->thread); pthread_join(&data->thread, NULL); } free(data->hostname); data->hostname = NULL; if (data->wfd != -1) { gg_sock_close(data->wfd); data->wfd = -1; } free(data); } /** * \internal Wątek rozwiązujący nazwę. * * \param arg Wskaźnik na strukturę \c gg_resolver_pthread_data */ static void *__stdcall gg_resolver_pthread_thread(void *arg) { struct gg_resolver_pthread_data *data = (gg_resolver_pthread_data*)arg; struct in_addr addr; pthread_detach(pthread_self()); if ((addr.s_addr = inet_addr(data->hostname)) == INADDR_NONE) { /* W przypadku błędu gg_gethostbyname_real() zwróci -1 * i nie zmieni &addr. Tam jest już INADDR_NONE, * więc nie musimy robić nic więcej. */ gg_gethostbyname_real(data->hostname, &addr, 1); } if (gg_sock_write(data->wfd, &addr, sizeof(addr)) == sizeof(addr)) pthread_exit(NULL); else pthread_exit((void*) -1); return NULL; /* żeby kompilator nie marudził */ } /** * \internal Rozwiązuje nazwę serwera w osobnym wątku. * * Funkcja działa analogicznie do \c gg_resolver_fork_start(), z tą różnicą, * że działa na wątkach, nie procesach. Jest dostępna wyłącznie gdy podczas * kompilacji włączono odpowiednią opcję. * * \param fd Wskaźnik na zmienną, gdzie zostanie umieszczony deskryptor * potoku * \param priv_data Wskaźnik na zmienną, gdzie zostanie umieszczony wskaźnik * do prywatnych danych wątku rozwiązującego nazwę * \param hostname Nazwa serwera do rozwiązania * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ static int gg_resolver_pthread_start(SOCKET *fd, void **priv_data, const char *hostname) { struct gg_resolver_pthread_data *data = NULL; int new_errno; SOCKET pipes[2]; gg_debug(GG_DEBUG_FUNCTION, "** gg_resolver_pthread_start(%p, %p, \"%s\");\n", fd, priv_data, hostname); if (fd == NULL || priv_data == NULL || hostname == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() invalid arguments\n"); errno = EFAULT; return -1; } data = (gg_resolver_pthread_data*)malloc(sizeof(struct gg_resolver_pthread_data)); if (data == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() out of memory for resolver data\n"); return -1; } if (pipe(pipes) == -1) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() unable to create pipes (errno=%d, %s)\n", errno, strerror(errno)); free(data); return -1; } data->hostname = strdup(hostname); if (data->hostname == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() out of memory\n"); new_errno = errno; goto cleanup; } data->rfd = pipes[0]; data->wfd = pipes[1]; if (pthread_create(&data->thread, NULL, gg_resolver_pthread_thread, data)) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() unable to create thread\n"); new_errno = errno; goto cleanup; } gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() %p\n", data); *fd = pipes[0]; *priv_data = data; return 0; cleanup: if (data) { free(data->hostname); free(data); } gg_sock_close(pipes[0]); gg_sock_close(pipes[1]); errno = new_errno; return -1; } #endif /* GG_CONFIG_HAVE_PTHREAD */ /** * Ustawia sposób rozwiązywania nazw w sesji. * * \param gs Struktura sesji * \param type Sposób rozwiązywania nazw (patrz \ref build-resolver) * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ int gg_session_set_resolver(struct gg_session *gs, gg_resolver_t type) { if (gs == NULL) { errno = EINVAL; return -1; } if (type == GG_RESOLVER_DEFAULT) { if (gg_global_resolver_type != GG_RESOLVER_DEFAULT) { gs->resolver_type = gg_global_resolver_type; gs->resolver_start = gg_global_resolver_start; gs->resolver_cleanup = gg_global_resolver_cleanup; return 0; } #if !defined(GG_CONFIG_HAVE_PTHREAD) || !defined(GG_CONFIG_PTHREAD_DEFAULT) type = GG_RESOLVER_FORK; #else type = GG_RESOLVER_PTHREAD; #endif } switch (type) { case GG_RESOLVER_FORK: gs->resolver_type = type; gs->resolver_start = gg_resolver_fork_start; gs->resolver_cleanup = gg_resolver_fork_cleanup; return 0; #ifdef GG_CONFIG_HAVE_PTHREAD case GG_RESOLVER_PTHREAD: gs->resolver_type = type; gs->resolver_start = gg_resolver_pthread_start; gs->resolver_cleanup = gg_resolver_pthread_cleanup; return 0; #endif default: errno = EINVAL; return -1; } } /** * Zwraca sposób rozwiązywania nazw w sesji. * * \param gs Struktura sesji * * \return Sposób rozwiązywania nazw */ gg_resolver_t gg_session_get_resolver(struct gg_session *gs) { if (gs == NULL) { errno = EINVAL; return GG_RESOLVER_INVALID; } return gs->resolver_type; } /** * Ustawia własny sposób rozwiązywania nazw w sesji. * * \param gs Struktura sesji * \param resolver_start Funkcja rozpoczynająca rozwiązywanie nazwy * \param resolver_cleanup Funkcja zwalniająca zasoby * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ int gg_session_set_custom_resolver(struct gg_session *gs, int (*resolver_start)(SOCKET*, void**, const char*), void (*resolver_cleanup)(void**, int)) { if (gs == NULL || resolver_start == NULL || resolver_cleanup == NULL) { errno = EINVAL; return -1; } gs->resolver_type = GG_RESOLVER_CUSTOM; gs->resolver_start = resolver_start; gs->resolver_cleanup = resolver_cleanup; return 0; } /** * Ustawia sposób rozwiązywania nazw połączenia HTTP. * * \param gh Struktura połączenia * \param type Sposób rozwiązywania nazw (patrz \ref build-resolver) * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ int gg_http_set_resolver(struct gg_http *gh, gg_resolver_t type) { if (gh == NULL) { errno = EINVAL; return -1; } if (type == GG_RESOLVER_DEFAULT) { if (gg_global_resolver_type != GG_RESOLVER_DEFAULT) { gh->resolver_type = gg_global_resolver_type; gh->resolver_start = gg_global_resolver_start; gh->resolver_cleanup = gg_global_resolver_cleanup; return 0; } #if !defined(GG_CONFIG_HAVE_PTHREAD) || !defined(GG_CONFIG_PTHREAD_DEFAULT) type = GG_RESOLVER_FORK; #else type = GG_RESOLVER_PTHREAD; #endif } switch (type) { case GG_RESOLVER_FORK: gh->resolver_type = type; gh->resolver_start = gg_resolver_fork_start; gh->resolver_cleanup = gg_resolver_fork_cleanup; return 0; #ifdef GG_CONFIG_HAVE_PTHREAD case GG_RESOLVER_PTHREAD: gh->resolver_type = type; gh->resolver_start = gg_resolver_pthread_start; gh->resolver_cleanup = gg_resolver_pthread_cleanup; return 0; #endif default: errno = EINVAL; return -1; } } /** * Zwraca sposób rozwiązywania nazw połączenia HTTP. * * \param gh Struktura połączenia * * \return Sposób rozwiązywania nazw */ gg_resolver_t gg_http_get_resolver(struct gg_http *gh) { if (gh == NULL) { errno = EINVAL; return GG_RESOLVER_INVALID; } return gh->resolver_type; } /** * Ustawia własny sposób rozwiązywania nazw połączenia HTTP. * * \param gh Struktura sesji * \param resolver_start Funkcja rozpoczynająca rozwiązywanie nazwy * \param resolver_cleanup Funkcja zwalniająca zasoby * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ int gg_http_set_custom_resolver(struct gg_http *gh, int (*resolver_start)(SOCKET*, void**, const char*), void (*resolver_cleanup)(void**, int)) { if (gh == NULL || resolver_start == NULL || resolver_cleanup == NULL) { errno = EINVAL; return -1; } gh->resolver_type = GG_RESOLVER_CUSTOM; gh->resolver_start = resolver_start; gh->resolver_cleanup = resolver_cleanup; return 0; } /** * Ustawia sposób rozwiązywania nazw globalnie dla biblioteki. * * \param type Sposób rozwiązywania nazw (patrz \ref build-resolver) * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ int gg_global_set_resolver(gg_resolver_t type) { switch (type) { case GG_RESOLVER_DEFAULT: gg_global_resolver_type = type; gg_global_resolver_start = NULL; gg_global_resolver_cleanup = NULL; return 0; case GG_RESOLVER_FORK: gg_global_resolver_type = type; gg_global_resolver_start = gg_resolver_fork_start; gg_global_resolver_cleanup = gg_resolver_fork_cleanup; return 0; #ifdef GG_CONFIG_HAVE_PTHREAD case GG_RESOLVER_PTHREAD: gg_global_resolver_type = type; gg_global_resolver_start = gg_resolver_pthread_start; gg_global_resolver_cleanup = gg_resolver_pthread_cleanup; return 0; #endif default: errno = EINVAL; return -1; } } /** * Zwraca sposób rozwiązywania nazw globalnie dla biblioteki. * * \return Sposób rozwiązywania nazw */ gg_resolver_t gg_global_get_resolver(void) { return gg_global_resolver_type; } /** * Ustawia własny sposób rozwiązywania nazw globalnie dla biblioteki. * * \param resolver_start Funkcja rozpoczynająca rozwiązywanie nazwy * \param resolver_cleanup Funkcja zwalniająca zasoby * * Parametry funkcji rozpoczynającej rozwiązywanie nazwy wyglądają następująco: * - \c "SOCKET *fd" — wskaźnik na zmienną, gdzie zostanie umieszczony deskryptor potoku * - \c "void **priv_data" — wskaźnik na zmienną, gdzie można umieścić wskaźnik do prywatnych danych na potrzeby rozwiązywania nazwy * - \c "const char *name" — nazwa serwera do rozwiązania * * Parametry funkcji zwalniającej zasoby wyglądają następująco: * - \c "void **priv_data" — wskaźnik na zmienną przechowującą wskaźnik do prywatnych danych, należy go ustawić na \c NULL po zakończeniu * - \c "int force" — flaga mówiąca o tym, że zasoby są zwalniane przed zakończeniem rozwiązywania nazwy, np. z powodu zamknięcia sesji. * * Własny kod rozwiązywania nazwy powinien stworzyć potok, parę gniazd lub * inny deskryptor pozwalający na co najmniej jednostronną komunikację i * przekazać go w parametrze \c fd. Po zakończeniu rozwiązywania nazwy, * powinien wysłać otrzymany adres IP w postaci sieciowej (big-endian) do * deskryptora. Jeśli rozwiązywanie nazwy się nie powiedzie, należy wysłać * \c INADDR_NONE. Następnie zostanie wywołana funkcja zwalniająca zasoby * z parametrem \c force równym \c 0. Gdyby sesja została zakończona przed * rozwiązaniem nazwy, np. za pomocą funkcji \c gg_logoff(), funkcja * zwalniająca zasoby zostanie wywołana z parametrem \c force równym \c 1. * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ int gg_global_set_custom_resolver(int (*resolver_start)(SOCKET*, void**, const char*), void (*resolver_cleanup)(void**, int)) { if (resolver_start == NULL || resolver_cleanup == NULL) { errno = EINVAL; return -1; } gg_global_resolver_type = GG_RESOLVER_CUSTOM; gg_global_resolver_start = resolver_start; gg_global_resolver_cleanup = resolver_cleanup; return 0; }