1129 lines
27 KiB
C
1129 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;
|
|
}
|