/* 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" #define LOG_SSL #define LOG_TO_STDERR */ #if defined(LOG_TRANSFER) || defined(LOG_TO_STDERR) static void log_data(unsigned char *data, int len) { #ifndef LOG_TO_STDERR static int hlaseno = 0; int fd; if (!hlaseno) { printf("\n"ANSI_SET_BOLD"WARNING -- LOGGING NETWORK TRANSFERS !!!"ANSI_CLEAR_BOLD ANSI_BELL"\n"); fflush(stdout); portable_sleep(1000); hlaseno = 1; } fd = c_open3(cast_uchar LOG_TRANSFER, O_WRONLY | O_APPEND | O_CREAT, 0600); if (fd != -1) { int rw; EINTRLOOP(rw, write(fd, data, len)); EINTRLOOP(rw, close(fd)); } #else int rw; EINTRLOOP(rw, write(2, data, len)); #endif } static void log_string(unsigned char *data) { log_data(data, (int)strlen(cast_const_char data)); } static void log_number(int number) { unsigned char n[64]; snprintf(cast_char n, sizeof n, "%d", number); log_string(n); } #else #define log_data(x, y) do { } while (0) #define log_string(x) do { } while (0) #define log_number(x) do { } while (0) #endif #ifdef HAVE_SSL static void log_ssl_error(unsigned char *url, int line, int ret1, int ret2) { #ifndef LOG_SSL unsigned long err; while ((err = ERR_get_error())) ; #else unsigned char *u, *uu; u = stracpy(url); if ((uu = cast_uchar strchr(cast_const_char u, POST_CHAR))) *uu = 0; #if defined(HAVE_SSL_LOAD_ERROR_STRINGS) || defined(SSL_load_error_strings) SSL_load_error_strings(); #endif #if defined(HAVE_OPENSSL) && !defined(OPENSSL_NO_STDIO) ERR_print_errors_fp(stderr); #else { unsigned long err; while ((err = ERR_get_error())) { unsigned char buf[1024]; ERR_error_string_n(err, cast_char buf, sizeof buf); fprintf(stderr, "%s\n", buf); } } #endif fprintf(stderr, "ssl error at %d: %d, %d, %d (%s), url (%s)\n", line, ret1, ret2, errno, strerror(errno), u); mem_free(u); #endif } void clear_ssl_errors(int line) { if (ERR_peek_error()) log_ssl_error(cast_uchar "", line, 0, 0); } #endif static void connected(void *); static void update_dns_priority(struct connection *); static void connected_callback(struct connection *); static void dns_found(void *, int); static void try_connect(struct connection *); static void handle_socks_reply(void *); int socket_and_bind(int pf, unsigned char *address) { int s; int rs; s = c_socket(pf, SOCK_STREAM, IPPROTO_TCP); if (s == -1) return -1; if (address && *address) { switch (pf) { case PF_INET: { struct sockaddr_in sa; unsigned char addr[4]; if (numeric_ip_address(address, addr) == -1) { goto bind_to_iface; } memset(&sa, 0, sizeof sa); sa.sin_family = AF_INET; memcpy(&sa.sin_addr.s_addr, addr, 4); 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; } break; } #ifdef SUPPORT_IPV6 case PF_INET6: { struct sockaddr_in6 sa; unsigned char addr[16]; unsigned scope; if (numeric_ipv6_address(address, addr, &scope) == -1) { goto bind_to_iface; } memset(&sa, 0, sizeof sa); sa.sin6_family = AF_INET6; memcpy(&sa.sin6_addr, addr, 16); sa.sin6_port = htons(0); #ifdef SUPPORT_IPV6_SCOPE sa.sin6_scope_id = scope; #endif 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; } break; } #endif default: { EINTRLOOP(rs, close(s)); errno = EINVAL; return -1; } } } return s; bind_to_iface: errno = EINVAL; #ifdef SO_BINDTODEVICE EINTRLOOP(rs, setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, address, (socklen_t)(strlen(cast_const_char address) + 1))); if (!rs) { return s; } #endif EINTRLOOP(rs, close(s)); return -1; } void close_socket(int *s) { int rs; if (*s == -1) return; set_handlers(*s, NULL, NULL, NULL); EINTRLOOP(rs, close(*s)); *s = -1; } struct conn_info { void (*func)(struct connection *); struct lookup_state l; int first_error; int *sock; int socks_byte_count; int socks_handled; unsigned char socks_reply[8]; unsigned char host[1]; }; void make_connection(struct connection *c, int port, int *sock, void (*func)(struct connection *)) { int socks_port = -1; unsigned char *host; size_t sl; struct conn_info *b; if (*c->socks_proxy) { /* pasing duplicated in proxy_ok_dialog */ unsigned char *p = cast_uchar strchr(cast_const_char c->socks_proxy, '@'); if (p) p++; else p = c->socks_proxy; host = stracpy(p); socks_port = 1080; if ((p = cast_uchar strchr(cast_const_char host, ':'))) { long lp; char *end; *p++ = 0; if (!*p) goto badu; lp = strtol(cast_const_char p, &end, 10); if (*end || lp <= 0 || lp >= 65536) { badu: mem_free(host); setcstate(c, S_BAD_URL); abort_connection(c); return; } socks_port = (int)lp; } } else if (!(host = get_host_name(c->url))) { setcstate(c, S_INTERNAL); abort_connection(c); return; } if (c->newconn) internal_error("already making a connection"); sl = strlen(cast_const_char host); if (sl > MAXINT - sizeof(struct conn_info)) overalloc(); b = mem_calloc(sizeof(struct conn_info) + sl); b->func = func; b->sock = sock; b->l.socks_port = socks_port; b->l.target_port = port; strcpy(cast_char b->host, cast_const_char host); c->newconn = b; log_string(cast_uchar "\nCONNECTION: "); log_data(host, strlen(cast_const_char host)); log_string(cast_uchar ":"); log_number(socks_port != -1 ? socks_port : port); log_string(cast_uchar "\n"); if (c->last_lookup_state.addr_index < c->last_lookup_state.addr.n) { b->l.addr = c->last_lookup_state.addr; b->l.addr_index = c->last_lookup_state.addr_index; b->l.dont_try_more_servers = 1; dns_found(c, 0); } else { int q = -1; if (c->no_cache < NC_RELOAD) { q = find_host_in_cache(host, &b->l.addr, &c->dnsquery, dns_found, c); } if (q) { setcstate(c, S_DNS); find_host_no_cache(host, c->doh, &b->l.addr, &c->dnsquery, dns_found, c); } } mem_free(host); } int is_ipv6(int h) { #ifdef SUPPORT_IPV6 union { struct sockaddr sa; struct sockaddr_in sin; struct sockaddr_in6 sin6; char pad[128]; } u; socklen_t len = sizeof(u); int rs; EINTRLOOP(rs, getsockname(h, &u.sa, &len)); if (rs) return 0; return u.sa.sa_family == AF_INET6; #else return 0; #endif } 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) goto e; if (sa.sin_family != AF_INET) { errno = EINVAL; goto e; } s = c_socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (s == -1) goto e; *sock = s; set_nonblock(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; e: setcstate(c, get_error_from_errno(errno)); retry_connection(c); return -1; } #ifdef SUPPORT_IPV6 int get_pasv_socket_ipv6(struct connection *c, int cc, int *sock, unsigned char *result) { int s; int rs; struct sockaddr_in6 sa; struct sockaddr_in6 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) goto e; if (sa.sin6_family != AF_INET6) { errno = EINVAL; goto e; } s = c_socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP); if (s == -1) goto e; *sock = s; set_nonblock(s); memcpy(&sb, &sa, sizeof(struct sockaddr_in6)); sb.sin6_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; sprintf(cast_char result, "|2|%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x|%d|", sa.sin6_addr.s6_addr[0], sa.sin6_addr.s6_addr[1], sa.sin6_addr.s6_addr[2], sa.sin6_addr.s6_addr[3], sa.sin6_addr.s6_addr[4], sa.sin6_addr.s6_addr[5], sa.sin6_addr.s6_addr[6], sa.sin6_addr.s6_addr[7], sa.sin6_addr.s6_addr[8], sa.sin6_addr.s6_addr[9], sa.sin6_addr.s6_addr[10], sa.sin6_addr.s6_addr[11], sa.sin6_addr.s6_addr[12], sa.sin6_addr.s6_addr[13], sa.sin6_addr.s6_addr[14], sa.sin6_addr.s6_addr[15], htons(sa.sin6_port) & 0xffff); return 0; e: setcstate(c, get_error_from_errno(errno)); retry_connection(c); return -1; } #endif #ifdef HAVE_SSL static void ssl_setup_downgrade(struct connection *c) { #if !defined(HAVE_NSS) int send_scsv = 0; int dd = c->no_tls; /* avoid warnings */ send_scsv++; send_scsv--; dd++; dd--; /*debug("no tls: %d", dd);*/ #ifdef HAVE_SSL_SET_SECURITY_LEVEL if (dd) { SSL_set_security_level(c->ssl->ssl, 0); dd--; } #endif #ifdef SSL_OP_NO_TLSv1_3 if (dd) { SSL_set_options(c->ssl->ssl, SSL_OP_NO_TLSv1_3); send_scsv = 1; dd--; } #endif #ifdef SSL_OP_NO_TLSv1_2 if (dd) { SSL_set_options(c->ssl->ssl, SSL_OP_NO_TLSv1_2); send_scsv = 1; dd--; } #endif #ifdef SSL_OP_NO_TLSv1_1 if (dd) { SSL_set_options(c->ssl->ssl, SSL_OP_NO_TLSv1_1); send_scsv = 1; dd--; } #endif #if defined(SSL_OP_NO_TLSv1) && !defined(OPENSSL_NO_SSL3) if (dd) { SSL_set_options(c->ssl->ssl, SSL_OP_NO_TLSv1); send_scsv = 1; dd--; } #endif #ifdef SSL_MODE_SEND_FALLBACK_SCSV if (send_scsv) { SSL_set_mode(c->ssl->ssl, SSL_MODE_SEND_FALLBACK_SCSV); } #endif if (SCRUB_HEADERS) { #ifdef SSL_OP_NO_SSLv2 SSL_set_options(c->ssl->ssl, SSL_OP_NO_SSLv2); #endif #ifdef SSL_OP_NO_SSLv3 SSL_set_options(c->ssl->ssl, SSL_OP_NO_SSLv3); #endif } #endif } static void ssl_downgrade_dance(struct connection *c) { #if !defined(HAVE_NSS) int downgrades = 0; c->no_ssl_session = 1; if (c->ssl && c->ssl->session_set) { retry_connect(c, S_SSL_ERROR, 1); return; } #ifdef HAVE_SSL_SET_SECURITY_LEVEL downgrades++; #endif #ifdef SSL_OP_NO_TLSv1_3 downgrades++; #endif #ifdef SSL_OP_NO_TLSv1_2 downgrades++; #endif #ifdef SSL_OP_NO_TLSv1_1 downgrades++; #endif #if defined(SSL_OP_NO_TLSv1) && !defined(OPENSSL_NO_SSL3) downgrades++; #endif if (++c->no_tls <= downgrades) { retry_connect(c, S_SSL_ERROR, 1); } else { c->no_tls = 0; retry_connect(c, S_SSL_ERROR, 0); } #else retry_connect(c, S_SSL_ERROR, 0); #endif } static void ssl_want_io(void *c_) { struct connection *c = (struct connection *)c_; int ret1, ret2; struct conn_info *b = c->newconn; set_connection_timeout(c); switch ((ret2 = SSL_get_error(c->ssl->ssl, ret1 = SSL_connect(c->ssl->ssl)))) { case SSL_ERROR_NONE: connected_callback(c); break; case SSL_ERROR_WANT_READ: set_handlers(*b->sock, ssl_want_io, NULL, c); break; case SSL_ERROR_WANT_WRITE: set_handlers(*b->sock, NULL, ssl_want_io, c); break; default: log_ssl_error(c->url, __LINE__, ret1, ret2); ssl_downgrade_dance(c); break; } } #endif static void handle_socks(void *c_) { struct connection *c = (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_connection_timeout(c); add_bytes_to_str(&command, &len, cast_uchar "\004\001", 2); add_chr_to_str(&command, &len, b->l.target_port >> 8); add_chr_to_str(&command, &len, b->l.target_port); add_bytes_to_str(&command, &len, cast_uchar "\000\000\000\001", 4); if (strchr(cast_const_char c->socks_proxy, '@')) add_bytes_to_str(&command, &len, c->socks_proxy, strcspn(cast_const_char 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, c->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; } if (!b->socks_byte_count) { log_data(command, len); log_string(cast_uchar "\n"); } EINTRLOOP(wr, (int)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, handle_socks, c); return; } else { b->socks_byte_count = 0; set_handlers(*b->sock, handle_socks_reply, NULL, c); return; } } static void handle_socks_reply(void *c_) { struct connection *c = (struct connection *)c_; struct conn_info *b = c->newconn; int rd; set_connection_timeout(c); EINTRLOOP(rd, (int)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; } connected(c); } static void dns_found(void *c_, int state) { struct connection *c = (struct connection *)c_; if (state) { setcstate(c, *c->socks_proxy || is_proxy_url(c->url) ? S_NO_PROXY_DNS : S_NO_DNS); abort_connection(c); return; } try_connect(c); } void retry_connect(struct connection *c, int err, int ssl_downgrade) { struct conn_info *b = c->newconn; if (!b->l.addr_index) b->first_error = err; #ifdef HAVE_SSL if (c->ssl) { freeSSL(c->ssl); if (is_proxy_url(c->url)) c->ssl = NULL; else c->ssl = DUMMY; } #endif if (ssl_downgrade) { log_string(cast_uchar "\nSSL DOWNGRADE\n"); close_socket(b->sock); try_connect(c); return; } b->l.addr_index++; if (b->l.addr_index < b->l.addr.n && !b->l.dont_try_more_servers) { if (b->l.addr_index == 1) rotate_addresses(&b->l.addr); log_string(cast_uchar "\nNEXT ADDRESS\n"); close_socket(b->sock); try_connect(c); } else { dns_clear_host(b->host); setcstate(c, b->first_error); retry_connection(c); } } static void try_connect(struct connection *c) { int i; int s; int rs; unsigned short p; struct conn_info *b = c->newconn; struct host_address *addr = &b->l.addr.a[b->l.addr_index]; /*debug("%p: %p %d %d\n", b, addr, b->l.addr_index, addr->af);*/ if (addr->af == AF_INET) { s = socket_and_bind(PF_INET, bind_ip_address); #ifdef SUPPORT_IPV6 } else if (addr->af == AF_INET6) { s = socket_and_bind(PF_INET6, bind_ipv6_address); #endif } else { setcstate(c, S_INTERNAL); abort_connection(c); return; } if (s == -1) { retry_connect(c, get_error_from_errno(errno), 0); return; } set_nonblock(s); *b->sock = s; b->socks_handled = 0; b->socks_byte_count = 0; p = b->l.socks_port != -1 ? b->l.socks_port : b->l.target_port; log_string(cast_uchar "\nADDRESS: "); for (i = 0; i < (addr->af == AF_INET ? 4 : 16); i++) { if (i) log_string(cast_uchar "."); log_number(addr->addr[i]); } log_string(cast_uchar ":"); log_number(p); log_string(cast_uchar "\n"); if (addr->af == AF_INET) { struct sockaddr_in sa; memset(&sa, 0, sizeof sa); sa.sin_family = AF_INET; memcpy(&sa.sin_addr.s_addr, addr->addr, 4); sa.sin_port = htons(p); #if defined(__aarch64__) && defined(__ILP32__) errno = EINPROGRESS; /* arm64 ilp32 bug */ #endif EINTRLOOP(rs, connect(s, (struct sockaddr *)(void *)&sa, sizeof sa)); #ifdef SUPPORT_IPV6 } else if (addr->af == AF_INET6) { struct sockaddr_in6 sa; memset(&sa, 0, sizeof sa); sa.sin6_family = AF_INET6; memcpy(&sa.sin6_addr, addr->addr, 16); #ifdef SUPPORT_IPV6_SCOPE sa.sin6_scope_id = addr->scope_id; #endif sa.sin6_port = htons(p); #if defined(__aarch64__) && defined(__ILP32__) errno = EINPROGRESS; /* arm64 ilp32 bug */ #endif EINTRLOOP(rs, connect(s, (struct sockaddr *)(void *)&sa, sizeof sa)); #endif } else { rs = -1; errno = EINVAL; } if (rs) { if (errno != EALREADY && errno != EINPROGRESS) { #ifdef BEOS if (errno == EWOULDBLOCK) errno = ETIMEDOUT; #endif retry_connect(c, get_error_from_errno(errno), 0); return; } set_handlers(s, NULL, connected, c); setcstate(c, !b->l.addr_index ? S_CONN : S_CONN_ANOTHER); if (b->l.addr.n > 1 && (is_connection_restartable(c) || max_tries == 1)) { set_connection_timeout(c); } } else { connected(c); } } #ifdef HAVE_SSL void continue_connection(struct connection *c, int *sock, void (*func)(struct connection *)) { struct conn_info *b; if (c->newconn) internal_error("already making a connection"); b = mem_calloc(sizeof(struct conn_info)); b->func = func; b->sock = sock; b->l = c->last_lookup_state; b->socks_handled = 1; c->newconn = b; log_string(cast_uchar "\nCONTINUE CONNECTION\n"); connected(c); } #endif static void connected(void *c_) { struct connection *c = (struct connection *)c_; struct conn_info *b = c->newconn; #ifdef SO_ERROR int err = 0; socklen_t len = sizeof(int); int rs; #endif clear_connection_timeout(c); #ifdef SO_ERROR errno = 0; EINTRLOOP(rs, getsockopt(*b->sock, SOL_SOCKET, SO_ERROR, (void *)&err, &len)); if (!rs) { #ifdef OS2 if (err >= 10000) err -= 10000; /* Why does EMX return so large values? */ #endif } else { if (!(err = errno)) { retry_connect(c, S_STATE, 0); return; } } if (err > 0 #ifdef EISCONN && err != EISCONN #endif ) { #ifdef DOS if (err == EALREADY) err = ETIMEDOUT; #endif retry_connect(c, get_error_from_errno(err), 0); return; } #endif set_connection_timeout(c); if (b->l.socks_port != -1 && !b->socks_handled) { b->socks_handled = 1; update_dns_priority(c); handle_socks(c); return; } log_string(cast_uchar "\nCONNECTED\n"); #ifdef HAVE_SSL if (c->ssl) { int ret1, ret2; unsigned char *orig_url = remove_proxy_prefix(c->url); unsigned char *h = get_host_name(orig_url); log_string(cast_uchar "\nSSL\n"); if (*h && h[strlen(cast_const_char h) - 1] == '.') { h[strlen(cast_const_char h) - 1] = 0; } c->ssl = getSSL(); if (!c->ssl) { c->ssl = DUMMY; ret1 = ret2 = 0; mem_free(h); goto ssl_error; } #ifdef SSL_SESSION_RESUME if (!proxies.only_proxies && !c->no_ssl_session) { unsigned char *h = get_host_name(orig_url); int p = get_port(orig_url); SSL_SESSION *ses = get_session_cache_entry(c->ssl->ctx, h, p); if (ses) { if (SSL_set_session(c->ssl->ssl, ses) == 1) c->ssl->session_set = 1; } mem_free(h); } #endif #if defined(HAVE_SSL_CERTIFICATES) && !defined(OPENSSL_NO_STDIO) if (!proxies.only_proxies) { if (ssl_options.client_cert_key[0]) { SSL_use_PrivateKey_file(c->ssl->ssl, cast_const_char ssl_options.client_cert_key, SSL_FILETYPE_PEM); } if (ssl_options.client_cert_crt[0]) { SSL_use_certificate_file(c->ssl->ssl, cast_const_char ssl_options.client_cert_crt, SSL_FILETYPE_PEM); } } #endif SSL_set_fd(c->ssl->ssl, *b->sock); ssl_setup_downgrade(c); #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME if (h[0] == '[' || !numeric_ip_address(h, NULL) #ifdef SUPPORT_IPV6 || !numeric_ipv6_address(h, NULL, NULL) #endif ) goto skip_numeric_address; SSL_set_tlsext_host_name(c->ssl->ssl, cast_const_char h); skip_numeric_address: #endif mem_free(h); switch ((ret2 = SSL_get_error(c->ssl->ssl, ret1 = SSL_connect(c->ssl->ssl)))) { case SSL_ERROR_WANT_READ: setcstate(c, S_SSL_NEG); set_handlers(*b->sock, ssl_want_io, NULL, c); return; case SSL_ERROR_WANT_WRITE: setcstate(c, S_SSL_NEG); set_handlers(*b->sock, NULL, ssl_want_io, c); return; case SSL_ERROR_NONE: break; default: ssl_error: log_ssl_error(c->url, __LINE__, ret1, ret2); ssl_downgrade_dance(c); return; } } #endif connected_callback(c); } static void update_dns_priority(struct connection *c) { struct conn_info *b = c->newconn; if (!b->l.dont_try_more_servers && b->host[0]) { if (b->l.addr_index) { int i; for (i = 0; i < b->l.addr_index; i++) dns_set_priority(b->host, &b->l.addr.a[i], 0); dns_set_priority(b->host, &b->l.addr.a[i], 1); } b->l.dont_try_more_servers = 1; } } static void connected_callback(struct connection *c) { struct conn_info *b = c->newconn; update_dns_priority(c); #ifdef HAVE_SSL_CERTIFICATES if (c->ssl) { if (ssl_options.certificates != SSL_ACCEPT_INVALID_CERTIFICATE) { unsigned char *h = get_host_name(remove_proxy_prefix(c->url)); int err = verify_ssl_certificate(c->ssl, h); if (err) { if (ssl_options.certificates == SSL_WARN_ON_INVALID_CERTIFICATE) { int flags = get_blacklist_flags(h); if (flags & BL_IGNORE_CERTIFICATE) goto ignore_cert; } mem_free(h); setcstate(c, err); abort_connection(c); return; } ignore_cert: if (c->no_tls) { #ifdef HAVE_SSL_SET_SECURITY_LEVEL if (c->no_tls == 1) { err = S_INSECURE_CIPHER; goto weak_cipher; } #endif if (ssl_options.certificates == SSL_WARN_ON_INVALID_CERTIFICATE) { int flags = get_blacklist_flags(h); if (flags & BL_IGNORE_DOWNGRADE) goto ignore_downgrade; } mem_free(h); setcstate(c, S_DOWNGRADED_METHOD); abort_connection(c); return; } ignore_downgrade: err = verify_ssl_cipher(c->ssl); if (err) { #ifdef HAVE_SSL_SET_SECURITY_LEVEL weak_cipher: #endif if (ssl_options.certificates == SSL_WARN_ON_INVALID_CERTIFICATE) { int flags = get_blacklist_flags(h); if (flags & BL_IGNORE_CIPHER) goto ignore_cipher; } mem_free(h); setcstate(c, err); abort_connection(c); return; } ignore_cipher: mem_free(h); } } #endif retrieve_ssl_session(c); c->last_lookup_state = b->l; 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(void *c_) { struct connection *c = (struct connection *)c_; struct write_buffer *wb; int wr; if (!(wb = c->buffer)) { internal_error("write socket has no buffer"); setcstate(c, S_INTERNAL); abort_connection(c); return; } if (wb->pos) set_connection_timeout(c); else set_connection_timeout_keepal(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) { set_handlers(wb->sock, NULL, write_select, c); if ((wr = SSL_write(c->ssl->ssl, (void *)(wb->data + wb->pos), wb->len - wb->pos)) <= 0) { int err; err = SSL_get_error(c->ssl->ssl, wr); if (err == SSL_ERROR_WANT_WRITE) { return; } if (err == SSL_ERROR_WANT_READ) { set_handlers(wb->sock, write_select, NULL, c); return; } setcstate(c, wr ? (err == SSL_ERROR_SYSCALL ? get_error_from_errno(errno) : S_SSL_ERROR) : S_CANT_WRITE); log_ssl_error(c->url, __LINE__, wr, err); if (!wr || err == SSL_ERROR_SYSCALL) retry_connection(c); else abort_connection(c); return; } c->ssl->bytes_written += wr; } else #endif { EINTRLOOP(wr, (int)write(wb->sock, wb->data + wb->pos, wb->len - wb->pos)); if (wr <= 0) { #if defined(ATHEOS) || defined(DOS) /* Workaround for a bug in Syllable */ if (wr && (errno == EAGAIN || errno == EWOULDBLOCK)) { 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); 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, write_select, c); } #define READ_SIZE 64240 #define TOTAL_READ (4193008 - READ_SIZE) static void read_select(void *c_) { struct connection *c = (struct connection *)c_; int total_read = 0; struct read_buffer *rb; int rd; if (!(rb = c->buffer)) { internal_error("read socket has no buffer"); setcstate(c, S_INTERNAL); abort_connection(c); return; } set_handlers(rb->sock, NULL, NULL, NULL); read_more: 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->ssl, (void *)(rb->data + rb->len), READ_SIZE)) <= 0) { int err; if (total_read) goto success; err = SSL_get_error(c->ssl->ssl, rd); if (err == SSL_ERROR_WANT_READ) { set_handlers(rb->sock, read_select, NULL, c); return; } if (err == SSL_ERROR_WANT_WRITE) { set_handlers(rb->sock, NULL, read_select, c); 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); log_ssl_error(c->url, __LINE__, rd, err); if (!rd || err == SSL_ERROR_SYSCALL) retry_connection(c); else abort_connection(c); return; } c->ssl->bytes_read += rd; } else #endif { EINTRLOOP(rd, (int)read(rb->sock, rb->data + rb->len, READ_SIZE)); if (rd <= 0) { if (total_read) goto success; if (rb->close && !rd) { rb->close = 2; rb->done(c, rb); return; } setcstate(c, rd ? get_error_from_errno(errno) : S_CANT_READ); retry_connection(c); return; } } log_data(rb->data + rb->len, rd); rb->len += rd; total_read += rd; if ((rd == READ_SIZE #ifdef HAVE_SSL || c->ssl #endif ) && total_read <= TOTAL_READ) { if (can_read(rb->sock)) goto read_more; } success: retrieve_ssl_session(c); 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, read_select, NULL, c); } void kill_buffer_data(struct read_buffer *rb, int n) { if (n > rb->len || n < 0) { internal_error("called kill_buffer_data with bad value"); rb->len = 0; return; } memmove(rb->data, rb->data + n, rb->len - n); rb->len -= n; }