summaryrefslogtreecommitdiff
path: root/connect.c
diff options
context:
space:
mode:
Diffstat (limited to 'connect.c')
-rw-r--r--connect.c581
1 files changed, 581 insertions, 0 deletions
diff --git a/connect.c b/connect.c
new file mode 100644
index 0000000..305a4d1
--- /dev/null
+++ b/connect.c
@@ -0,0 +1,581 @@
+/* connect.c
+ * (c) 2002 Mikulas Patocka
+ * This file is a part of the Links program, released under GPL.
+ */
+
+#include "links.h"
+
+/*
+#define LOG_TRANSFER "/tmp/log"
+*/
+
+#ifdef LOG_TRANSFER
+static void log_data(unsigned char *data, int len)
+{
+ static int hlaseno = 0;
+ int fd;
+ if (!hlaseno) {
+ printf("\n\033[1mWARNING -- LOGGING NETWORK TRANSFERS !!!\033[0m%c\n", 7);
+ fflush(stdout);
+ sleep(1);
+ hlaseno = 1;
+ }
+ EINTRLOOP(fd, open(LOG_TRANSFER, O_WRONLY | O_APPEND | O_CREAT, 0600));
+ if (fd != -1) {
+ int rw;
+ set_bin(fd);
+ EINTRLOOP(rw, write(fd, data, len));
+ EINTRLOOP(rw, close(fd));
+ }
+}
+
+#else
+#define log_data(x, y)
+#endif
+
+static void connected(struct connection *);
+static void dns_found(struct connection *, int);
+static void handle_socks_reply(struct connection *);
+
+static void exception(struct connection *c)
+{
+ setcstate(c, S_EXCEPT);
+ retry_connection(c);
+}
+
+int socket_and_bind(unsigned char *address)
+{
+ int s;
+ int rs;
+ EINTRLOOP(s, socket(PF_INET, SOCK_STREAM, IPPROTO_TCP));
+ if (s == -1)
+ return -1;
+ if (address && *address) {
+ struct sockaddr_in sa;
+ ip__address addr;
+ if (numeric_ip_address(address, &addr) == -1) {
+ errno = EINVAL;
+ return -1;
+ }
+ memset(&sa, 0, sizeof(struct sockaddr_in));
+ sa.sin_family = AF_INET;
+ sa.sin_addr.s_addr = addr;
+ sa.sin_port = htons(0);
+ EINTRLOOP(rs, bind(s, (struct sockaddr *)(void *)&sa, sizeof sa));
+ if (rs) {
+ int sv_errno = errno;
+ EINTRLOOP(rs, close(s));
+ errno = sv_errno;
+ return -1;
+ }
+ }
+ return s;
+}
+
+void close_socket(int *s)
+{
+ int rs;
+ if (*s == -1) return;
+ EINTRLOOP(rs, close(*s));
+ set_handlers(*s, NULL, NULL, NULL, NULL);
+ *s = -1;
+}
+
+struct conn_info {
+ void (*func)(struct connection *);
+ ip__address addr;
+ int port;
+ int *sock;
+ int real_port;
+ int socks_byte_count;
+ unsigned char socks_reply[8];
+ unsigned char dns_append[1];
+};
+
+void make_connection(struct connection *c, int port, int *sock, void (*func)(struct connection *))
+{
+ int real_port = -1;
+ int as;
+ unsigned char *dns_append = "";
+ unsigned char *host;
+ struct conn_info *b;
+ if (*c->socks_proxy) {
+ unsigned char *p = strchr(c->socks_proxy, '@');
+ if (p) p++;
+ else p = c->socks_proxy;
+ host = stracpy(p);
+ real_port = port;
+ port = 1080;
+ if ((p = strchr(host, ':'))) {
+ *p++ = 0;
+ if (!*p) goto badu;
+ port = strtoul(p, (char **)(void *)&p, 10);
+ if (*p) {
+ badu:
+ mem_free(host);
+ setcstate(c, S_BAD_URL);
+ abort_connection(c);
+ return;
+ }
+ }
+ dns_append = proxies.dns_append;
+ } else if (!(host = get_host_name(c->url))) {
+ setcstate(c, S_INTERNAL);
+ abort_connection(c);
+ return;
+ }
+ if (c->newconn)
+ internal("already making a connection");
+ b = mem_alloc(sizeof(struct conn_info) + strlen(dns_append));
+ b->func = func;
+ b->sock = sock;
+ b->port = port;
+ b->real_port = real_port;
+ b->socks_byte_count = 0;
+ strcpy(b->dns_append, dns_append);
+ c->newconn = b;
+ log_data("\nCONNECTION: ", 13);
+ log_data(host, strlen(host));
+ log_data("\n", 1);
+ if (c->no_cache >= NC_RELOAD) as = find_host_no_cache(host, &b->addr, &c->dnsquery, (void(*)(void *, int))dns_found, c);
+ else as = find_host(host, &b->addr, &c->dnsquery, (void(*)(void *, int))dns_found, c);
+ mem_free(host);
+ if (as) setcstate(c, S_DNS);
+}
+
+int get_pasv_socket(struct connection *c, int cc, int *sock, unsigned char *port)
+{
+ int s;
+ int rs;
+ struct sockaddr_in sa;
+ struct sockaddr_in sb;
+ socklen_t len = sizeof(sa);
+ memset(&sa, 0, sizeof sa);
+ memset(&sb, 0, sizeof sb);
+ EINTRLOOP(rs, getsockname(cc, (struct sockaddr *)(void *)&sa, &len));
+ if (rs) {
+ e:
+ setcstate(c, get_error_from_errno(errno));
+ retry_connection(c);
+ return -1;
+ }
+ EINTRLOOP(s, socket(PF_INET, SOCK_STREAM, IPPROTO_TCP));
+ if (s == -1) goto e;
+ EINTRLOOP(rs, fcntl(s, F_SETFL, O_NONBLOCK));
+ *sock = s;
+ memcpy(&sb, &sa, sizeof(struct sockaddr_in));
+ sb.sin_port = htons(0);
+ EINTRLOOP(rs, bind(s, (struct sockaddr *)(void *)&sb, sizeof sb));
+ if (rs) goto e;
+ len = sizeof(sa);
+ EINTRLOOP(rs, getsockname(s, (struct sockaddr *)(void *)&sa, &len));
+ if (rs) goto e;
+ EINTRLOOP(rs, listen(s, 1));
+ if (rs) goto e;
+ memcpy(port, &sa.sin_addr.s_addr, 4);
+ memcpy(port + 4, &sa.sin_port, 2);
+ return 0;
+}
+
+#ifdef HAVE_SSL
+static void ssl_want_read(struct connection *c)
+{
+ struct conn_info *b = c->newconn;
+
+ set_timeout(c);
+
+#ifndef HAVE_NSS
+ if (c->no_tsl) c->ssl->options |= SSL_OP_NO_TLSv1;
+#endif
+ switch (SSL_get_error(c->ssl, SSL_connect(c->ssl))) {
+ case SSL_ERROR_NONE:
+ c->newconn = NULL;
+ b->func(c);
+ mem_free(b);
+ break;
+ case SSL_ERROR_WANT_READ:
+ set_handlers(*b->sock, (void(*)(void *))ssl_want_read, NULL, (void(*)(void *))exception, c);
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ set_handlers(*b->sock, NULL, (void(*)(void *))ssl_want_read, (void(*)(void *))exception, c);
+ break;
+ default:
+ c->no_tsl++;
+ setcstate(c, S_SSL_ERROR);
+ retry_connection(c);
+ break;
+ }
+}
+#endif
+
+static void handle_socks(struct connection *c)
+{
+ struct conn_info *b = c->newconn;
+ unsigned char *command = init_str();
+ int len = 0;
+ unsigned char *host;
+ int wr;
+ setcstate(c, S_SOCKS_NEG);
+ set_timeout(c);
+ add_bytes_to_str(&command, &len, "\004\001", 2);
+ add_chr_to_str(&command, &len, b->real_port >> 8);
+ add_chr_to_str(&command, &len, b->real_port);
+ add_bytes_to_str(&command, &len, "\000\000\000\001", 4);
+ if (strchr(c->socks_proxy, '@'))
+ add_bytes_to_str(&command, &len, c->socks_proxy, strcspn(c->socks_proxy, "@"));
+ add_chr_to_str(&command, &len, 0);
+ if (!(host = get_host_name(c->url))) {
+ mem_free(command);
+ setcstate(c, S_INTERNAL);
+ abort_connection(c);
+ return;
+ }
+ add_to_str(&command, &len, host);
+ add_to_str(&command, &len, b->dns_append);
+ add_chr_to_str(&command, &len, 0);
+ mem_free(host);
+ if (b->socks_byte_count >= len) {
+ mem_free(command);
+ setcstate(c, S_MODIFIED);
+ retry_connection(c);
+ return;
+ }
+ EINTRLOOP(wr, write(*b->sock, command + b->socks_byte_count, len - b->socks_byte_count));
+ mem_free(command);
+ if (wr <= 0) {
+ setcstate(c, wr ? get_error_from_errno(errno) : S_CANT_WRITE);
+ retry_connection(c);
+ return;
+ }
+ b->socks_byte_count += wr;
+ if (b->socks_byte_count < len) {
+ set_handlers(*b->sock, NULL, (void(*)(void *))handle_socks, (void(*)(void *))exception, c);
+ return;
+ } else {
+ b->socks_byte_count = 0;
+ set_handlers(*b->sock, (void(*)(void *))handle_socks_reply, NULL, (void(*)(void *))exception, c);
+ return;
+ }
+}
+
+static void handle_socks_reply(struct connection *c)
+{
+ struct conn_info *b = c->newconn;
+ int rd;
+ set_timeout(c);
+ EINTRLOOP(rd, read(*b->sock, b->socks_reply + b->socks_byte_count, sizeof b->socks_reply - b->socks_byte_count));
+ if (rd <= 0) {
+ setcstate(c, rd ? get_error_from_errno(errno) : S_CANT_READ);
+ retry_connection(c);
+ return;
+ }
+ b->socks_byte_count += rd;
+ if (b->socks_byte_count < (int)sizeof b->socks_reply) return;
+ /* debug("%x %x %x %x %x %x %x %x", b->socks_reply[0], b->socks_reply[1], b->socks_reply[2], b->socks_reply[3], b->socks_reply[4], b->socks_reply[5], b->socks_reply[6], b->socks_reply[7]); */
+ if (b->socks_reply[0]) {
+ setcstate(c, S_BAD_SOCKS_VERSION);
+ abort_connection(c);
+ return;
+ }
+ switch (b->socks_reply[1]) {
+ case 91:
+ setcstate(c, S_SOCKS_REJECTED);
+ retry_connection(c);
+ return;
+ case 92:
+ setcstate(c, S_SOCKS_NO_IDENTD);
+ abort_connection(c);
+ return;
+ case 93:
+ setcstate(c, S_SOCKS_BAD_USERID);
+ abort_connection(c);
+ return;
+ default:
+ setcstate(c, S_SOCKS_UNKNOWN_ERROR);
+ retry_connection(c);
+ return;
+ case 90:
+ break;
+ }
+ b->real_port = -1;
+ connected(c);
+}
+
+static void dns_found(struct connection *c, int state)
+{
+ int s;
+ int rs;
+ struct conn_info *b = c->newconn;
+ struct sockaddr_in sa;
+ if (state) {
+ setcstate(c, S_NO_DNS);
+ abort_connection(c);
+ return;
+ }
+ if ((s = socket_and_bind(bind_ip_address)) == -1) {
+ setcstate(c, get_error_from_errno(errno));
+ retry_connection(c);
+ return;
+ }
+ EINTRLOOP(rs, fcntl(s, F_SETFL, O_NONBLOCK));
+ *b->sock = s;
+ memset(&sa, 0, sizeof(struct sockaddr_in));
+ sa.sin_family = AF_INET;
+ sa.sin_addr.s_addr = b->addr;
+ sa.sin_port = htons(b->port);
+ EINTRLOOP(rs, connect(s, (struct sockaddr *)(void *)&sa, sizeof sa));
+ if (rs) {
+ if (errno != EALREADY && errno != EINPROGRESS) {
+#ifdef BEOS
+ if (errno == EWOULDBLOCK) errno = ETIMEDOUT;
+#endif
+ setcstate(c, get_error_from_errno(errno));
+ retry_connection(c);
+ return;
+ }
+ set_handlers(s, NULL, (void(*)(void *))connected, (void(*)(void *))exception, c);
+ setcstate(c, S_CONN);
+ } else {
+ connected(c);
+ }
+}
+
+static void connected(struct connection *c)
+{
+ struct conn_info *b = c->newconn;
+ int err = 0;
+ socklen_t len = sizeof(int);
+ int rs;
+ errno = 0;
+ EINTRLOOP(rs, getsockopt(*b->sock, SOL_SOCKET, SO_ERROR, (void *)&err, &len));
+ if (!rs) {
+ if (err >= 10000) err -= 10000; /* Why does EMX return so large values? */
+ } else {
+ if (!(err = errno)) {
+ setcstate(c, S_STATE);
+ retry_connection(c);
+ return;
+ }
+ }
+ if (err > 0) {
+ setcstate(c, get_error_from_errno(err));
+ retry_connection(c);
+ return;
+ }
+ set_timeout(c);
+ if (b->real_port != -1) {
+ handle_socks(c);
+ return;
+ }
+#ifdef HAVE_SSL
+ if (c->ssl) {
+ c->ssl = getSSL();
+ if (!c->ssl) {
+ goto ssl_error;
+ }
+ SSL_set_fd(c->ssl, *b->sock);
+#ifndef HAVE_NSS
+ if (c->no_tsl) c->ssl->options |= SSL_OP_NO_TLSv1;
+#endif
+ switch (SSL_get_error(c->ssl, SSL_connect(c->ssl))) {
+ case SSL_ERROR_WANT_READ:
+ setcstate(c, S_SSL_NEG);
+ set_handlers(*b->sock, (void(*)(void *))ssl_want_read, NULL, (void(*)(void *))exception, c);
+ return;
+ case SSL_ERROR_WANT_WRITE:
+ setcstate(c, S_SSL_NEG);
+ set_handlers(*b->sock, NULL, (void(*)(void *))ssl_want_read, (void(*)(void *))exception, c);
+ return;
+ case SSL_ERROR_NONE:
+ break;
+ default:
+ ssl_error:
+ c->no_tsl++;
+ setcstate(c, S_SSL_ERROR);
+ retry_connection(c);
+ return;
+ }
+ }
+#endif
+ c->newconn = NULL;
+ b->func(c);
+ mem_free(b);
+}
+
+struct write_buffer {
+ int sock;
+ int len;
+ int pos;
+ void (*done)(struct connection *);
+ unsigned char data[1];
+};
+
+static void write_select(struct connection *c)
+{
+ struct write_buffer *wb;
+ int wr;
+ if (!(wb = c->buffer)) {
+ internal("write socket has no buffer");
+ setcstate(c, S_INTERNAL);
+ abort_connection(c);
+ return;
+ }
+ set_timeout(c);
+ /*printf("ws: %d\n",wb->len-wb->pos);
+ for (wr = wb->pos; wr < wb->len; wr++) printf("%c", wb->data[wr]);
+ printf("-\n");*/
+
+#ifdef HAVE_SSL
+ if(c->ssl) {
+ if ((wr = SSL_write(c->ssl, wb->data + wb->pos, wb->len - wb->pos)) <= 0) {
+ int err;
+ if ((err = SSL_get_error(c->ssl, wr)) != SSL_ERROR_WANT_WRITE) {
+ setcstate(c, wr ? (err == SSL_ERROR_SYSCALL ? get_error_from_errno(errno) : S_SSL_ERROR) : S_CANT_WRITE);
+ if (!wr || err == SSL_ERROR_SYSCALL) retry_connection(c);
+ else abort_connection(c);
+ return;
+ }
+ else return;
+ }
+ } else
+#endif
+ {
+ EINTRLOOP(wr, write(wb->sock, wb->data + wb->pos, wb->len - wb->pos));
+ if (wr <= 0) {
+#ifdef ATHEOS
+ /* Workaround for a bug in Syllable */
+ if (wr && errno == EAGAIN) {
+ return;
+ }
+#endif
+ setcstate(c, wr ? get_error_from_errno(errno) : S_CANT_WRITE);
+ retry_connection(c);
+ return;
+ }
+ }
+
+ if ((wb->pos += wr) == wb->len) {
+ void (*f)(struct connection *) = wb->done;
+ c->buffer = NULL;
+ set_handlers(wb->sock, NULL, NULL, NULL, NULL);
+ mem_free(wb);
+ f(c);
+ }
+}
+
+void write_to_socket(struct connection *c, int s, unsigned char *data, int len, void (*write_func)(struct connection *))
+{
+ struct write_buffer *wb;
+ log_data(data, len);
+ if ((unsigned)len > MAXINT - sizeof(struct write_buffer)) overalloc();
+ wb = mem_alloc(sizeof(struct write_buffer) + len);
+ wb->sock = s;
+ wb->len = len;
+ wb->pos = 0;
+ wb->done = write_func;
+ memcpy(wb->data, data, len);
+ if (c->buffer) mem_free(c->buffer);
+ c->buffer = wb;
+ set_handlers(s, NULL, (void (*)(void *))write_select, (void (*)(void *))exception, c);
+}
+
+#define READ_SIZE 64240
+
+static void read_select(struct connection *c)
+{
+ struct read_buffer *rb;
+ int rd;
+ if (!(rb = c->buffer)) {
+ internal("read socket has no buffer");
+ setcstate(c, S_INTERNAL);
+ abort_connection(c);
+ return;
+ }
+ set_handlers(rb->sock, NULL, NULL, NULL, NULL);
+ if ((unsigned)rb->len > MAXINT - sizeof(struct read_buffer) - READ_SIZE) overalloc();
+ rb = mem_realloc(rb, sizeof(struct read_buffer) + rb->len + READ_SIZE);
+ c->buffer = rb;
+
+#ifdef HAVE_SSL
+ if(c->ssl) {
+ if ((rd = SSL_read(c->ssl, rb->data + rb->len, READ_SIZE)) <= 0) {
+ int err;
+ if ((err = SSL_get_error(c->ssl, rd)) == SSL_ERROR_WANT_READ) {
+ read_from_socket(c, rb->sock, rb, rb->done);
+ return;
+ }
+ if (rb->close && !rd) {
+ rb->close = 2;
+ rb->done(c, rb);
+ return;
+ }
+ setcstate(c, rd ? (err == SSL_ERROR_SYSCALL ? get_error_from_errno(errno) : S_SSL_ERROR) : S_CANT_READ);
+ /*mem_free(rb);*/
+ if (!rd || err == SSL_ERROR_SYSCALL) retry_connection(c);
+ else abort_connection(c);
+ return;
+ }
+ } else
+#endif
+ {
+ EINTRLOOP(rd, read(rb->sock, rb->data + rb->len, READ_SIZE));
+ if (rd <= 0) {
+ if (rb->close && !rd) {
+ rb->close = 2;
+ rb->done(c, rb);
+ return;
+ }
+ if (!rd) {
+/* Many servers supporting compression have a bug
+ --- they send the size of uncompressed data.
+ Turn off compression support once before the final retry.
+*/
+ unsigned char *prot, *h;
+ if (is_last_try(c) && (prot = get_protocol_name(c->url))) {
+ if (!strcasecmp(prot, "http")) {
+ if ((h = get_host_name(c->url))) {
+ add_blacklist_entry(h, BL_NO_COMPRESSION);
+ mem_free(h);
+ }
+ }
+ mem_free(prot);
+ }
+ }
+ setcstate(c, rd ? get_error_from_errno(errno) : S_CANT_READ);
+ /*mem_free(rb);*/
+ retry_connection(c);
+ return;
+ }
+ }
+ log_data(rb->data + rb->len, rd);
+ rb->len += rd;
+ rb->done(c, rb);
+}
+
+struct read_buffer *alloc_read_buffer(struct connection *c)
+{
+ struct read_buffer *rb;
+ rb = mem_alloc(sizeof(struct read_buffer) + READ_SIZE);
+ memset(rb, 0, sizeof(struct read_buffer));
+ return rb;
+}
+
+void read_from_socket(struct connection *c, int s, struct read_buffer *buf, void (*read_func)(struct connection *, struct read_buffer *))
+{
+ buf->done = read_func;
+ buf->sock = s;
+ if (c->buffer && buf != c->buffer) mem_free(c->buffer);
+ c->buffer = buf;
+ set_handlers(s, (void (*)(void *))read_select, NULL, (void (*)(void *))exception, c);
+}
+
+void kill_buffer_data(struct read_buffer *rb, int n)
+{
+ if (n > rb->len || n < 0) {
+ internal("called kill_buffer_data with bad value");
+ rb->len = 0;
+ return;
+ }
+ memmove(rb->data, rb->data + n, rb->len - n);
+ rb->len -= n;
+}