Beispiel #1
0
/**
 * 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;
}
Beispiel #2
0
/**
 * 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);
}
Beispiel #3
0
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;
}
Beispiel #4
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" &mdash; wskaźnik na zmienną, gdzie zostanie umieszczony deskryptor potoku
 *  - \c "void **priv_data" &mdash; wskaźnik na zmienną, gdzie można umieścić wskaźnik do prywatnych danych na potrzeby rozwiązywania nazwy
 *  - \c "const char *name" &mdash; nazwa serwera do rozwiązania
 *
 * Parametry funkcji zwalniającej zasoby wyglądają następująco:
 *  - \c "void **priv_data" &mdash; wskaźnik na zmienną przechowującą wskaźnik do prywatnych danych, należy go ustawić na \c NULL po zakończeniu
 *  - \c "int force" &mdash; 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;
}