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