/* Sockets-o-matic */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #ifdef HAVE_NETINET_IN_H #include /* OS/2 needs this after sys/types.h */ #endif #ifdef HAVE_SYS_SOCKET_H #include /* OS/2 needs this after sys/types.h */ #endif #ifdef HAVE_FCNTL_H #include /* OS/2 needs this after sys/types.h */ #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_WS2TCPIP_H #include /* socklen_t for MinGW */ #endif #ifdef HAVE_GETIFADDRS #ifdef HAVE_NETDB_H #include #endif #ifdef HAVE_NET_IF_H #include #endif #ifdef HAVE_IFADDRS_H #include /* getifaddrs() */ #endif #endif /* HAVE_GETIFADDRS */ #ifdef HAVE_ARPA_INET_H #include #endif #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/http/blacklist.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. */ struct uri *uri; /* For updating the blacklist. */ }; /** For detecting whether a struct socket has been deleted while a * function was using it. */ struct socket_weak_ref { LIST_HEAD(struct socket_weak_ref); /** done_socket() resets this to NULL. */ struct socket *socket; }; static INIT_LIST_OF(struct socket_weak_ref, socket_weak_refs); /* To enable logging of tranfers, for debugging purposes. */ #if 0 #define DEBUG_TRANSFER_LOGFILE "/tmp/log" static void debug_transfer_log(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 = (struct 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; connect_info->uri = get_uri_reference(uri); 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); done_uri(connect_info->uri); mem_free_set(&socket->connect_info, NULL); } struct socket * init_socket(void *conn, struct socket_operations *ops) { struct socket *socket; socket = (struct 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) { struct socket_weak_ref *ref; 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); foreach(ref, socket_weak_refs) { if (ref->socket == socket) ref->socket = 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, connection_state(S_EXCEPT)); } static void exception(struct socket *socket) { socket->ops->retry(socket, connection_state(S_EXCEPT)); } void timeout_socket(struct socket *socket) { if (!socket->connect_info) { socket->ops->retry(socket, connection_state(S_TIMEOUT)); return; } /* Is the DNS resolving still in progress? */ if (socket->connect_info->dnsquery) { socket->ops->done(socket, connection_state(S_TIMEOUT)); return; } /* Try the next address, */ connect_socket(socket, connection_state(S_TIMEOUT)); /* Reset the timeout if connect_socket() started a new attempt * to connect. */ if (socket->connect_info) socket->ops->set_timeout(socket, connection_state(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, connection_state(S_NO_DNS)); return; } assert(connect_info); size = sizeof(*addr) * addrlen; connect_info->addr = (struct sockaddr_storage *)mem_alloc(size); if (!connect_info->addr) { socket->ops->done(socket, connection_state(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, connection_state(S_CONN)); } void make_connection(struct socket *socket, struct uri *uri, socket_connect_T connect_done, int no_cache) { char *host = get_uri_string(uri, URI_DNS_HOST); struct connect_info *connect_info; enum dns_result result; blacklist_flags_T verify; socket->ops->set_timeout(socket, connection_state(0)); if (!host) { socket->ops->retry(socket, connection_state(S_OUT_OF_MEM)); return; } connect_info = init_connection_info(uri, socket, connect_done); if (!connect_info) { mem_free(host); socket->ops->retry(socket, connection_state(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); if (!socket->set_no_tls) { blacklist_flags_T flags = get_blacklist_flags(uri); socket->no_tls = ((flags & SERVER_BLACKLIST_NO_TLS) != 0); socket->set_no_tls = 1; } verify = get_blacklist_flags(uri); socket->verify = ((verify & SERVER_BLACKLIST_NO_CERT_VERIFY) == 0); 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, connection_state(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 syspf; /* Protocol Family given to system, not EL_PF_... */ socklen_t 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); syspf = PF_INET6; } else #endif { bind_addr = (struct sockaddr *) &bind_addr4; addrlen = sizeof(bind_addr4); syspf = PF_INET; } memset(pasv_addr, 0, addrlen); memset(bind_addr, 0, 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, connection_state_for_errno(errno)); return -1; } /* Get a passive socket */ sock = socket(syspf, 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; if (connect_info && connect_info->uri) { /* Remember whether the server supported TLS or not. * Then the next request can immediately use the right * protocol. This is important for HTTP POST requests * because it is not safe to silently retry them. The * uri parameter is normally NULL here so don't use it. */ if (socket->no_tls) add_blacklist_entry(connect_info->uri, SERVER_BLACKLIST_NO_TLS); else del_blacklist_entry(connect_info->uri, SERVER_BLACKLIST_NO_TLS); } /* 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, connection_state(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; struct connection_state state = connection_state(0); socklen_t len = sizeof(err); assertm(socket->connect_info != NULL, "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; if (err != 0) state = connection_state_for_errno(err); else state = connection_state(0); } else { /* getsockopt() failed */ if (errno != 0) state = connection_state_for_errno(errno); else state = connection_state(S_STATE); } if (!is_in_state(state, 0)) { /* There are maybe still some more candidates. */ connect_socket(socket, state); return; } complete_connect_socket(socket, NULL, NULL); } static int to_bind; static struct sockaddr_in sa_bind; #ifdef CONFIG_IPV6 static int to_bind_ipv6; static struct sockaddr_in6 sa6_bind; #endif static void init_bind_address(void) { char *bind_address = get_cmd_opt_str("bind-address"); #ifdef CONFIG_IPV6 char *bind_address_ipv6 = get_cmd_opt_str("bind-address-ipv6"); #endif #ifdef HAVE_INET_PTON to_bind = (bind_address && *bind_address); if (to_bind) { memset(&sa_bind, 0, sizeof sa_bind); sa_bind.sin_family = AF_INET; inet_pton(AF_INET, bind_address, &(sa_bind.sin_addr)); sa_bind.sin_port = htons(0); } #ifdef CONFIG_IPV6 to_bind_ipv6 = (bind_address_ipv6 && *bind_address_ipv6); if (to_bind_ipv6) { memset(&sa6_bind, 0, sizeof sa6_bind); sa6_bind.sin6_family = AF_INET6; inet_pton(AF_INET6, bind_address_ipv6, &(sa6_bind.sin6_addr)); sa6_bind.sin6_port = htons(0); } #endif #endif } void connect_socket(struct socket *csocket, struct connection_state state) { static int initialized; 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", NULL); #endif int try_ipv4 = get_opt_bool("connection.try_ipv4", NULL); /* 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; if (!initialized) { init_bind_address(); initialized = 1; } 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; } #ifdef HAVE_INET_PTON if (pf == PF_INET && to_bind) { struct sockaddr_in sa; int res; memcpy(&sa, &sa_bind, sizeof sa); res = bind(sock, (struct sockaddr *)(void *)&sa, sizeof sa); if (res < 0) { if (errno && !saved_errno) saved_errno = errno; close(sock); continue; } } #ifdef CONFIG_IPV6 if (pf == PF_INET6 && to_bind_ipv6) { struct sockaddr_in6 sa; int res; memcpy(&sa, &sa6_bind, sizeof sa); res = bind(sock, (struct sockaddr *)(void *)&sa, sizeof sa); if (res < 0) { if (errno && !saved_errno) saved_errno = errno; close(sock); continue; } } #endif #endif 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 */ /* And in fact we must set it early, because of EINPROGRESS. */ #ifdef CONFIG_IPV6 if (family == AF_INET6) { csocket->protocol_family = EL_PF_INET6; if (connect(sock, (struct sockaddr *) &addr, sizeof(struct sockaddr_in6)) == 0) { /* Success */ complete_connect_socket(csocket, NULL, NULL); return; } } else #endif { csocket->protocol_family = EL_PF_INET; if (connect(sock, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) == 0) { /* Success */ 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, connection_state(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, connection_state(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 = connection_state_for_errno(errno); else if (trno == -1 && silent_fail) /* All failed. */ state = connection_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; char data[1]; /* must be at end of struct */ }; static int generic_write(struct socket *socket, char *data, int len) { int wr = safe_write(socket->fd, data, len); if (!wr) return SOCKET_CANT_WRITE; if (wr < 0) { #ifdef EWOULDBLOCK if (errno == EWOULDBLOCK) return SOCKET_CANT_WRITE; #endif return SOCKET_SYSCALL_ERROR; } return wr; } static void write_select(struct socket *socket) { struct write_buffer *wb = (struct write_buffer *)socket->write_buffer; int wr; assertm(wb != NULL, "write socket has no buffer"); if_assert_failed { socket->ops->done(socket, connection_state(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, connection_state(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, connection_state(S_CANT_WRITE)); break; case SOCKET_SYSCALL_ERROR: socket->ops->retry(socket, connection_state_for_errno(errno)); break; case SOCKET_INTERNAL_ERROR: /* The global errno variable is used for passing * internal connection_state error value. */ socket->ops->done(socket, connection_state(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, char *data, int len, struct 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, connection_state(0)); wb = (struct write_buffer *)mem_alloc(sizeof(*wb) + len); if (!wb) { socket->ops->done(socket, connection_state(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, char *data, int len) { ssize_t rd = safe_read(socket->fd, data, len); if (!rd) return SOCKET_CANT_READ; if (rd < 0) { #ifdef EWOULDBLOCK if (errno == EWOULDBLOCK) return SOCKET_CANT_READ; #endif return SOCKET_SYSCALL_ERROR; } return rd; } static void read_select(struct socket *socket) { struct read_buffer *rb = socket->read_buffer; ssize_t rd; assertm(rb != NULL, "read socket has no buffer"); if_assert_failed { socket->ops->done(socket, connection_state(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, connection_state(0)); if (!socket->duplex) clear_handlers(socket->fd); if (!rb->freespace) { int size = RD_SIZE(rb, rb->length); rb = (struct read_buffer *)mem_realloc(rb, size); if (!rb) { socket->ops->done(socket, connection_state(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, connection_state(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; } socket->ops->retry(socket, connection_state(S_CANT_READ)); break; case SOCKET_SYSCALL_ERROR: socket->ops->retry(socket, connection_state_for_errno(errno)); break; case SOCKET_INTERNAL_ERROR: /* The global errno variable is used for passing * internal connection_state error value. */ socket->ops->done(socket, connection_state(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 = (struct read_buffer *)mem_calloc(1, RD_SIZE(rb, 0)); if (!rb) { socket->ops->done(socket, connection_state(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, struct connection_state state, socket_read_T done) { const int is_buffer_new = (buffer != socket->read_buffer); struct socket_weak_ref ref; select_handler_T write_handler; ref.socket = socket; add_to_list(socket_weak_refs, &ref); buffer->done = done; socket->ops->set_timeout(socket, connection_state(0)); socket->ops->set_state(socket, state); del_from_list(&ref); if (ref.socket == NULL) { /* socket->ops->set_state deleted the socket. */ if (is_buffer_new) mem_free(buffer); return; } 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, connection_state(S_SENT), socket->read_done); } void request_from_socket(struct socket *socket, char *data, int datalen, struct 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; }