links/connect.c

1130 lines
27 KiB
C

/* 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;
}