1
0
mirror of https://github.com/rkd77/elinks.git synced 2024-12-04 14:46:47 -05:00
elinks/src/network/socket.c
2006-01-05 18:36:22 +01:00

947 lines
22 KiB
C

/* Sockets-o-matic */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h> /* OS/2 needs this after sys/types.h */
#endif
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h> /* OS/2 needs this after sys/types.h */
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h> /* OS/2 needs this after sys/types.h */
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_GETIFADDRS
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#ifdef HAVE_NET_IF_H
#include <net/if.h>
#endif
#ifdef HAVE_IFADDRS_H
#include <ifaddrs.h> /* getifaddrs() */
#endif
#endif /* HAVE_GETIFADDRS */
#include "elinks.h"
#include "config/options.h"
#include "main/select.h"
#include "network/connection.h"
#include "network/dns.h"
#include "network/socket.h"
#include "network/ssl/socket.h"
#include "osdep/osdep.h"
#include "osdep/getifaddrs.h"
#include "protocol/protocol.h"
#include "protocol/uri.h"
#include "util/error.h"
#include "util/memory.h"
#include "util/string.h"
/* Holds information used during the connection establishing phase. */
struct connect_info {
struct sockaddr_storage *addr; /* Array of found addresses. */
int addrno; /* Number of found addresses. */
int triedno; /* Index of last tried address */
socket_connect_T done; /* Callback signaled when connected. */
void *dnsquery; /* Pointer to DNS query info. */
int port; /* Which port to bind to. */
int ip_family; /* If non-zero, force to IP version. */
};
/* To enable logging of tranfers, for debugging purposes. */
#if 0
#define DEBUG_TRANSFER_LOGFILE "/tmp/log"
static void
debug_transfer_log(unsigned char *data, int len)
{
int fd = open(DEBUG_TRANSFER_LOGFILE, O_WRONLY | O_APPEND | O_CREAT, 0622);
if (fd == -1) return;
set_bin(fd);
write(fd, data, len < 0 ? strlen(data) : len);
close(fd);
}
#undef DEBUG_TRANSFER_LOGFILE
#else
#define debug_transfer_log(data, len)
#endif
static struct connect_info *
init_connection_info(struct uri *uri, struct socket *socket,
socket_connect_T connect_done)
{
struct connect_info *connect_info = mem_calloc(1, sizeof(*connect_info));
if (!connect_info) return NULL;
connect_info->done = connect_done;
connect_info->port = get_uri_port(uri);
connect_info->ip_family = uri->ip_family;
connect_info->triedno = -1;
connect_info->addr = NULL;
return connect_info;
}
static void
done_connection_info(struct socket *socket)
{
struct connect_info *connect_info = socket->connect_info;
assert(socket->connect_info);
if (connect_info->dnsquery) kill_dns_request(&connect_info->dnsquery);
mem_free_if(connect_info->addr);
mem_free_set(&socket->connect_info, NULL);
}
struct socket *
init_socket(void *conn, struct socket_operations *ops)
{
struct socket *socket;
socket = mem_calloc(1, sizeof(*socket));
if (!socket) return NULL;
socket->fd = -1;
socket->conn = conn;
socket->ops = ops;
return socket;
}
void
done_socket(struct socket *socket)
{
close_socket(socket);
if (socket->connect_info)
done_connection_info(socket);
mem_free_set(&socket->read_buffer, NULL);
mem_free_set(&socket->write_buffer, NULL);
}
void
close_socket(struct socket *socket)
{
if (socket->fd == -1) return;
#ifdef CONFIG_SSL
if (socket->ssl) ssl_close(socket);
#endif
close(socket->fd);
clear_handlers(socket->fd);
socket->fd = -1;
}
void
dns_exception(struct socket *socket)
{
connect_socket(socket, S_EXCEPT);
}
static void
exception(struct socket *socket)
{
socket->ops->retry(socket, S_EXCEPT);
}
void
timeout_socket(struct socket *socket)
{
if (!socket->connect_info) {
socket->ops->retry(socket, S_TIMEOUT);
return;
}
/* Is the DNS resolving still in progress? */
if (socket->connect_info->dnsquery) {
socket->ops->done(socket, S_TIMEOUT);
return;
}
/* Try the next address, */
connect_socket(socket, S_TIMEOUT);
/* Reset the timeout if connect_socket() started a new attempt
* to connect. */
if (socket->connect_info)
socket->ops->set_timeout(socket, 0);
}
/* DNS callback. */
static void
dns_found(struct socket *socket, struct sockaddr_storage *addr, int addrlen)
{
struct connect_info *connect_info = socket->connect_info;
int size;
if (!addr) {
socket->ops->done(socket, S_NO_DNS);
return;
}
assert(connect_info);
size = sizeof(*addr) * addrlen;
connect_info->addr = mem_alloc(size);
if (!connect_info->addr) {
socket->ops->done(socket, S_OUT_OF_MEM);
return;
}
memcpy(connect_info->addr, addr, size);
connect_info->addrno = addrlen;
/* XXX: Passing non-result state here is bad but a lack of alternatives
* makes it so. Well adding get_state() socket operation could maybe fix
* it but the returned state would most likely be a non-result one at
* this point in the connection lifecycle. This will, however, only be a
* problem if connect_socket() fails without doing any system calls
* which is only the case when forcing the IP family. So it is better to
* handle it in connect_socket(). */
connect_socket(socket, S_CONN);
}
void
make_connection(struct socket *socket, struct uri *uri,
socket_connect_T connect_done, int no_cache)
{
unsigned char *host = get_uri_string(uri, URI_DNS_HOST);
struct connect_info *connect_info;
enum dns_result result;
socket->ops->set_timeout(socket, 0);
if (!host) {
socket->ops->retry(socket, S_OUT_OF_MEM);
return;
}
connect_info = init_connection_info(uri, socket, connect_done);
if (!connect_info) {
mem_free(host);
socket->ops->retry(socket, S_OUT_OF_MEM);
return;
}
socket->connect_info = connect_info;
/* XXX: Keep here and not in init_connection_info() to make
* complete_connect_socket() work from the HTTP implementation. */
socket->need_ssl = get_protocol_need_ssl(uri->protocol);
debug_transfer_log("\nCONNECTION: ", -1);
debug_transfer_log(host, -1);
debug_transfer_log("\n", -1);
result = find_host(host, &connect_info->dnsquery, (dns_callback_T) dns_found,
socket, no_cache);
mem_free(host);
if (result == DNS_ASYNC)
socket->ops->set_state(socket, S_DNS);
}
/* Returns negative if error, otherwise pasv socket's fd. */
int
get_pasv_socket(struct socket *ctrl_socket, struct sockaddr_storage *addr)
{
struct sockaddr_in bind_addr4;
struct sockaddr *bind_addr;
struct sockaddr *pasv_addr = (struct sockaddr *) addr;
size_t addrlen;
int sock = -1;
int len;
#ifdef CONFIG_IPV6
struct sockaddr_in6 bind_addr6;
if (ctrl_socket->protocol_family == EL_PF_INET6) {
bind_addr = (struct sockaddr *) &bind_addr6;
addrlen = sizeof(bind_addr6);
} else
#endif
{
bind_addr = (struct sockaddr *) &bind_addr4;
addrlen = sizeof(bind_addr4);
}
memset(pasv_addr, 0, sizeof(addrlen));
memset(bind_addr, 0, sizeof(addrlen));
/* Get our endpoint of the control socket */
len = addrlen;
if (getsockname(ctrl_socket->fd, pasv_addr, &len)) {
sock_error:
if (sock != -1) close(sock);
ctrl_socket->ops->retry(ctrl_socket, -errno);
return -1;
}
/* Get a passive socket */
sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock < 0)
goto sock_error;
/* Set it non-blocking */
if (set_nonblocking_fd(sock) < 0)
goto sock_error;
/* Bind it to some port */
memcpy(bind_addr, pasv_addr, addrlen);
#ifdef CONFIG_IPV6
if (ctrl_socket->protocol_family == EL_PF_INET6)
bind_addr6.sin6_port = 0;
else
#endif
bind_addr4.sin_port = 0;
if (bind(sock, bind_addr, addrlen))
goto sock_error;
/* Get our endpoint of the passive socket and save it to port */
len = addrlen;
if (getsockname(sock, pasv_addr, &len))
goto sock_error;
/* Go listen */
if (listen(sock, 1))
goto sock_error;
set_ip_tos_throughput(sock);
return sock;
}
#ifdef CONFIG_IPV6
static inline int
check_if_local_address6(struct sockaddr_in6 *addr)
{
struct ifaddrs *ifaddrs;
int local = IN6_IS_ADDR_LOOPBACK(&(addr->sin6_addr));
if (!local && !getifaddrs(&ifaddrs)) {
struct ifaddrs *ifa;
for (ifa = ifaddrs; ifa; ifa = ifa->ifa_next) {
if (!ifa->ifa_addr)
continue;
if (ifa->ifa_addr->sa_family == AF_INET6
&& !memcmp(&addr->sin6_addr.s6_addr,
&((struct sockaddr_in6 *) ifa->ifa_addr)->sin6_addr.s6_addr,
sizeof(addr->sin6_addr.s6_addr))) {
local = 1;
break;
}
if (ifa->ifa_addr->sa_family == AF_INET
&& !memcmp(&((struct sockaddr_in *) &addr)->sin_addr.s_addr,
&((struct sockaddr_in *) ifa->ifa_addr)->sin_addr.s_addr,
sizeof(((struct sockaddr_in *) &addr)->sin_addr.s_addr))) {
local = 1;
break;
}
}
freeifaddrs(ifaddrs);
}
return local;
}
#endif /* CONFIG_IPV6 */
static inline int
check_if_local_address4(struct sockaddr_in *addr)
{
struct ifaddrs *ifaddrs;
int local = (ntohl(addr->sin_addr.s_addr) >> 24) == IN_LOOPBACKNET;
if (!local && !getifaddrs(&ifaddrs)) {
struct ifaddrs *ifa;
for (ifa = ifaddrs; ifa; ifa = ifa->ifa_next) {
if (!ifa->ifa_addr)
continue;
if (ifa->ifa_addr->sa_family != AF_INET) continue;
if (!memcmp(&addr->sin_addr.s_addr,
&((struct sockaddr_in *) ifa->ifa_addr)->sin_addr.s_addr,
sizeof(addr->sin_addr.s_addr))) {
local = 1;
break;
}
}
freeifaddrs(ifaddrs);
}
return local;
}
void
complete_connect_socket(struct socket *socket, struct uri *uri,
socket_connect_T done)
{
struct connect_info *connect_info = socket->connect_info;
/* This is a special case used by the HTTP implementation to acquire an
* SSL link for handling CONNECT requests. */
if (!connect_info) {
assert(uri && socket);
connect_info = init_connection_info(uri, socket, done);
if (!connect_info) {
socket->ops->done(socket, S_OUT_OF_MEM);
return;
}
socket->connect_info = connect_info;
}
#ifdef CONFIG_SSL
/* Check if the connection should run over an encrypted link */
if (socket->need_ssl
&& !socket->ssl
&& ssl_connect(socket) < 0)
return;
#endif
if (connect_info->done)
connect_info->done(socket);
done_connection_info(socket);
}
/* Select handler which is set for the socket descriptor when connect() has
* indicated (via errno) that it is in progress. On completion this handler gets
* called. */
static void
connected(struct socket *socket)
{
int err = 0;
int len = sizeof(err);
assertm(socket->connect_info, "Lost connect_info!");
if_assert_failed return;
if (getsockopt(socket->fd, SOL_SOCKET, SO_ERROR, (void *) &err, &len) == 0) {
/* Why does EMX return so large values? */
if (err >= 10000) err -= 10000;
} else {
/* getsockopt() failed */
if (errno > 0)
err = errno;
else
err = -(S_STATE);
}
if (err > 0) {
/* There are maybe still some more candidates. */
connect_socket(socket, -err);
return;
}
complete_connect_socket(socket, NULL, NULL);
}
void
connect_socket(struct socket *csocket, enum connection_state state)
{
int sock = -1;
struct connect_info *connect_info = csocket->connect_info;
int i;
int trno = connect_info->triedno;
int only_local = get_cmd_opt_bool("localhost");
int saved_errno = 0;
int at_least_one_remote_ip = 0;
#ifdef CONFIG_IPV6
int try_ipv6 = get_opt_bool("connection.try_ipv6");
#endif
int try_ipv4 = get_opt_bool("connection.try_ipv4");
/* We tried something but we failed in such a way that we would rather
* prefer the connection to retain the information about previous
* failures. That is, we i.e. decided we are forbidden to even think
* about such a connection attempt.
* XXX: Unify with @local_only handling? --pasky */
int silent_fail = 0;
csocket->ops->set_state(csocket, state);
/* Clear handlers, the connection to the previous RR really timed
* out and doesn't interest us anymore. */
if (csocket->fd >= 0)
close_socket(csocket);
for (i = connect_info->triedno + 1; i < connect_info->addrno; i++) {
#ifdef CONFIG_IPV6
struct sockaddr_in6 addr = *((struct sockaddr_in6 *) &connect_info->addr[i]);
int family = addr.sin6_family;
#else
struct sockaddr_in addr = *((struct sockaddr_in *) &connect_info->addr[i]);
int family = addr.sin_family;
#endif
int pf;
int force_family = connect_info->ip_family;
connect_info->triedno++;
if (only_local) {
int local = 0;
#ifdef CONFIG_IPV6
if (family == AF_INET6)
local = check_if_local_address6((struct sockaddr_in6 *) &addr);
else
#endif
local = check_if_local_address4((struct sockaddr_in *) &addr);
/* This forbids connections to anything but local, if option is set. */
if (!local) {
at_least_one_remote_ip = 1;
continue;
}
}
#ifdef CONFIG_IPV6
if (family == AF_INET6) {
if (!try_ipv6 || (force_family && force_family != 6)) {
silent_fail = 1;
continue;
}
pf = PF_INET6;
} else
#endif
if (family == AF_INET) {
if (!try_ipv4 || (force_family && force_family != 4)) {
silent_fail = 1;
continue;
}
pf = PF_INET;
} else {
continue;
}
silent_fail = 0;
sock = socket(pf, SOCK_STREAM, IPPROTO_TCP);
if (sock == -1) {
if (errno && !saved_errno) saved_errno = errno;
continue;
}
if (set_nonblocking_fd(sock) < 0) {
if (errno && !saved_errno) saved_errno = errno;
close(sock);
continue;
}
csocket->fd = sock;
#ifdef CONFIG_IPV6
addr.sin6_port = htons(connect_info->port);
#else
addr.sin_port = htons(connect_info->port);
#endif
/* We can set csocket->protocol_family here even if the connection
* will fail, as we will use it only when it will be successfully
* established. At least I hope that noone else will want to do
* something else ;-). --pasky */
#ifdef CONFIG_IPV6
if (family == AF_INET6) {
if (connect(sock, (struct sockaddr *) &addr,
sizeof(struct sockaddr_in6)) == 0) {
/* Success */
csocket->protocol_family = EL_PF_INET6;
complete_connect_socket(csocket, NULL, NULL);
return;
}
} else
#endif
{
if (connect(sock, (struct sockaddr *) &addr,
sizeof(struct sockaddr_in)) == 0) {
/* Success */
csocket->protocol_family = EL_PF_INET;
complete_connect_socket(csocket, NULL, NULL);
return;
}
}
if (errno == EALREADY
#ifdef EWOULDBLOCK
|| errno == EWOULDBLOCK
#endif
|| errno == EINPROGRESS) {
/* It will take some more time... */
set_handlers(sock, NULL, (select_handler_T) connected,
(select_handler_T) dns_exception, csocket);
csocket->ops->set_state(csocket, S_CONN);
return;
}
if (errno && !saved_errno) saved_errno = errno;
close(sock);
}
assert(i >= connect_info->addrno);
/* Tried everything, but it didn't help :(. */
if (only_local && !saved_errno && at_least_one_remote_ip) {
/* Yes we might hit a local address and fail in the process, but
* what matters is the last one because we do not know the
* previous one's errno, and the added complexity wouldn't
* really be worth it. */
csocket->ops->done(csocket, S_LOCAL_ONLY);
return;
}
/* Retry reporting the errno state only if we already tried something
* new. Else use the S_DNS _progress_ state to make sure that no
* download callbacks will report any errors. */
if (trno != connect_info->triedno && !silent_fail)
state = -errno;
else if (trno == -1 && silent_fail)
/* All failed. */
state = S_NO_FORCED_DNS;
csocket->ops->retry(csocket, state);
}
struct write_buffer {
/* A routine called when all the data is sent (therefore this is
* _different_ from read_buffer.done !). */
socket_write_T done;
int length;
int pos;
unsigned char data[1]; /* must be at end of struct */
};
static int
generic_write(struct socket *socket, unsigned char *data, int len)
{
int wr = safe_write(socket->fd, data, len);
if (!wr) return SOCKET_CANT_WRITE;
return wr < 0 ? SOCKET_SYSCALL_ERROR : wr;
}
static void
write_select(struct socket *socket)
{
struct write_buffer *wb = socket->write_buffer;
int wr;
assertm(wb, "write socket has no buffer");
if_assert_failed {
socket->ops->done(socket, S_INTERNAL);
return;
}
/* We are making some progress, therefore reset the timeout; ie. when
* uploading large files the time needed for all the data to be sent can
* easily exceed the timeout. */
socket->ops->set_timeout(socket, 0);
#if 0
printf("ws: %d\n",wb->length-wb->pos);
for (wr = wb->pos; wr < wb->length; wr++) printf("%c", wb->data[wr]);
printf("-\n");
#endif
#ifdef CONFIG_SSL
if (socket->ssl) {
wr = ssl_write(socket, wb->data + wb->pos, wb->length - wb->pos);
} else
#endif
{
assert(wb->length - wb->pos > 0);
wr = generic_write(socket, wb->data + wb->pos, wb->length - wb->pos);
}
switch (wr) {
case SOCKET_CANT_WRITE:
socket->ops->retry(socket, S_CANT_WRITE);
break;
case SOCKET_SYSCALL_ERROR:
socket->ops->retry(socket, -errno);
break;
case SOCKET_INTERNAL_ERROR:
/* The global errno variable is used for passing
* internal connection_state error value. */
socket->ops->done(socket, -errno);
break;
default:
if (wr < 0) break;
/*printf("wr: %d\n", wr);*/
wb->pos += wr;
if (wb->pos == wb->length) {
socket_write_T done = wb->done;
if (!socket->duplex) {
clear_handlers(socket->fd);
} else {
select_handler_T read_handler;
select_handler_T error_handler;
read_handler = get_handler(socket->fd, SELECT_HANDLER_READ);
error_handler = read_handler
? (select_handler_T) exception
: NULL;
set_handlers(socket->fd, read_handler, NULL,
error_handler, socket);
}
mem_free_set(&socket->write_buffer, NULL);
done(socket);
}
}
}
void
write_to_socket(struct socket *socket, unsigned char *data, int len,
enum connection_state state, socket_write_T write_done)
{
select_handler_T read_handler;
struct write_buffer *wb;
debug_transfer_log(data, len);
assert(len > 0);
if_assert_failed return;
socket->ops->set_timeout(socket, 0);
wb = mem_alloc(sizeof(*wb) + len);
if (!wb) {
socket->ops->done(socket, S_OUT_OF_MEM);
return;
}
wb->length = len;
wb->pos = 0;
wb->done = write_done;
memcpy(wb->data, data, len);
mem_free_set(&socket->write_buffer, wb);
if (socket->duplex) {
read_handler = get_handler(socket->fd, SELECT_HANDLER_READ);
} else {
read_handler = NULL;
}
set_handlers(socket->fd, read_handler, (select_handler_T) write_select,
(select_handler_T) exception, socket);
socket->ops->set_state(socket, state);
}
#define RD_ALLOC_GR (2<<11) /* 4096 */
#define RD_MEM(rb) (sizeof(*(rb)) + 4 * RD_ALLOC_GR + RD_ALLOC_GR)
#define RD_SIZE(rb, len) ((RD_MEM(rb) + (len)) & ~(RD_ALLOC_GR - 1))
static ssize_t
generic_read(struct socket *socket, unsigned char *data, int len)
{
ssize_t rd = safe_read(socket->fd, data, len);
if (!rd) return SOCKET_CANT_READ;
return rd < 0 ? SOCKET_SYSCALL_ERROR : rd;
}
static void
read_select(struct socket *socket)
{
struct read_buffer *rb = socket->read_buffer;
ssize_t rd;
assertm(rb, "read socket has no buffer");
if_assert_failed {
socket->ops->done(socket, S_INTERNAL);
return;
}
/* We are making some progress, therefore reset the timeout; we do this
* for read_select() to avoid that the periodic calls to user handlers
* has to do it. */
socket->ops->set_timeout(socket, 0);
if (!socket->duplex)
clear_handlers(socket->fd);
if (!rb->freespace) {
int size = RD_SIZE(rb, rb->length);
rb = mem_realloc(rb, size);
if (!rb) {
socket->ops->done(socket, S_OUT_OF_MEM);
return;
}
rb->freespace = size - sizeof(*rb) - rb->length;
assert(rb->freespace > 0);
socket->read_buffer = rb;
}
#ifdef CONFIG_SSL
if (socket->ssl) {
rd = ssl_read(socket, rb->data + rb->length, rb->freespace);
} else
#endif
{
rd = generic_read(socket, rb->data + rb->length, rb->freespace);
}
switch (rd) {
#ifdef CONFIG_SSL
case SOCKET_SSL_WANT_READ:
read_from_socket(socket, rb, S_TRANS, rb->done);
break;
#endif
case SOCKET_CANT_READ:
if (socket->state != SOCKET_RETRY_ONCLOSE) {
socket->state = SOCKET_CLOSED;
rb->done(socket, rb);
break;
}
errno = -S_CANT_READ;
/* Fall-through */
case SOCKET_SYSCALL_ERROR:
socket->ops->retry(socket, -errno);
break;
case SOCKET_INTERNAL_ERROR:
socket->ops->done(socket, -errno);
break;
default:
debug_transfer_log(rb->data + rb->length, rd);
rb->length += rd;
rb->freespace -= rd;
assert(rb->freespace >= 0);
rb->done(socket, rb);
}
}
struct read_buffer *
alloc_read_buffer(struct socket *socket)
{
struct read_buffer *rb;
rb = mem_calloc(1, RD_SIZE(rb, 0));
if (!rb) {
socket->ops->done(socket, S_OUT_OF_MEM);
return NULL;
}
rb->freespace = RD_SIZE(rb, 0) - sizeof(*rb);
return rb;
}
#undef RD_ALLOC_GR
#undef RD_MEM
#undef RD_SIZE
void
read_from_socket(struct socket *socket, struct read_buffer *buffer,
enum connection_state state, socket_read_T done)
{
select_handler_T write_handler;
buffer->done = done;
socket->ops->set_timeout(socket, 0);
socket->ops->set_state(socket, state);
if (socket->read_buffer && buffer != socket->read_buffer)
mem_free(socket->read_buffer);
socket->read_buffer = buffer;
if (socket->duplex) {
write_handler = get_handler(socket->fd, SELECT_HANDLER_WRITE);
} else {
write_handler = NULL;
}
set_handlers(socket->fd, (select_handler_T) read_select, write_handler,
(select_handler_T) exception, socket);
}
static void
read_response_from_socket(struct socket *socket)
{
struct read_buffer *rb = alloc_read_buffer(socket);
if (rb) read_from_socket(socket, rb, S_SENT, socket->read_done);
}
void
request_from_socket(struct socket *socket, unsigned char *data, int datalen,
enum connection_state state, enum socket_state sock_state,
socket_read_T read_done)
{
socket->read_done = read_done;
socket->state = sock_state;
write_to_socket(socket, data, datalen, state,
read_response_from_socket);
}
void
kill_buffer_data(struct read_buffer *rb, int n)
{
assertm(n >= 0 && n <= rb->length, "bad number of bytes: %d", n);
if_assert_failed { rb->length = 0; return; }
if (!n) return; /* FIXME: We accept to kill 0 bytes... */
rb->length -= n;
memmove(rb->data, rb->data + n, rb->length);
rb->freespace += n;
}