diff options
Diffstat (limited to 'connect.c')
-rw-r--r-- | connect.c | 581 |
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; +} |