/* Called when we receive a connection on the listening socket. */ static void accept_bittorrent_peer_connection(void *____) { struct sockaddr_in addr; int peer_sock; int addrlen = sizeof(addr); struct bittorrent_peer_connection *peer; struct read_buffer *buffer; peer_sock = accept(bittorrent_socket, (struct sockaddr *) &addr, &addrlen); if (peer_sock < 0) return; if (set_nonblocking_fd(peer_sock) < 0) { close(peer_sock); return; } peer = init_bittorrent_peer_connection(peer_sock); if (!peer) { close(peer_sock); return; } peer->remote.initiater = 1; /* Just return. Failure is handled by alloc_read_buffer(). */ buffer = alloc_read_buffer(peer->socket); if (!buffer) return; read_from_socket(peer->socket, buffer, connection_state(S_TRANS), read_bittorrent_peer_handshake); add_to_list(bittorrent_peer_connections, peer); }
int start_thread(void (*fn)(void *, int), void *ptr, int l) { int p[2]; pid_t pid; if (c_pipe(p) < 0) return -1; if (set_nonblocking_fd(p[0]) < 0) return -1; if (set_nonblocking_fd(p[1]) < 0) return -1; pid = fork(); if (!pid) { struct terminal *term; /* Close input in this thread; otherwise, if it will live * longer than its parent, it'll block the terminal until it'll * quit as well; this way it will hopefully just die unseen and * in background, causing no trouble. */ /* Particularly, when async dns resolving was in progress and * someone quitted ELinks, it could make a delay before the * terminal would be really freed and returned to shell. */ foreach (term, terminals) if (term->fdin > 0) close(term->fdin); close(p[0]); fn(ptr, p[1]); write(p[1], "x", 1); close(p[1]); /* We use _exit() here instead of exit(), see * http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC6 for * reasons. Fixed by Sven Neumann <*****@*****.**>. */ _exit(0); } if (pid == -1) { close(p[0]); close(p[1]); return -1; } close(p[1]); return p[0]; }
void mailcap_protocol_handler(struct connection *conn) { #ifdef HAVE_FORK unsigned char *script, *ref; pid_t pid; struct connection_state state = connection_state(S_OK); int pipe_read[2], check; /* security checks */ if (!conn->referrer || conn->referrer->protocol != PROTOCOL_MAILCAP) { goto bad; } ref = get_uri_string(conn->referrer, URI_DATA); if (!ref) { goto bad; } check = strcmp(ref, "elmailcap"); mem_free(ref); if (check) goto bad; script = get_uri_string(conn->uri, URI_DATA); if (!script) { state = connection_state(S_OUT_OF_MEM); goto end2; } if (c_pipe(pipe_read)) { state = connection_state_for_errno(errno); goto end1; } pid = fork(); if (pid < 0) { state = connection_state_for_errno(errno); goto end0; } if (!pid) { if (dup2(pipe_read[1], STDOUT_FILENO) < 0) { _exit(2); } /* We implicitly chain stderr to ELinks' stderr. */ close_all_non_term_fd(); if (execl("/bin/sh", "/bin/sh", "-c", script, (char *) NULL)) { _exit(3); } } else { /* ELinks */ mem_free(script); if (!init_http_connection_info(conn, 1, 0, 1)) { close(pipe_read[0]); close(pipe_read[1]); return; } close(pipe_read[1]); conn->socket->fd = pipe_read[0]; conn->data_socket->fd = -1; conn->cgi = 1; set_nonblocking_fd(conn->socket->fd); get_request(conn); return; } end0: close(pipe_read[0]); close(pipe_read[1]); end1: mem_free(script); end2: abort_connection(conn, state); return; #endif bad: abort_connection(conn, connection_state(S_BAD_URL)); }
void smb_protocol_handler(struct connection *conn) { int smb_pipe[2] = { -1, -1 }; int header_pipe[2] = { -1, -1 }; pid_t cpid; if (c_pipe(smb_pipe) || c_pipe(header_pipe)) { int s_errno = errno; if (smb_pipe[0] >= 0) close(smb_pipe[0]); if (smb_pipe[1] >= 0) close(smb_pipe[1]); if (header_pipe[0] >= 0) close(header_pipe[0]); if (header_pipe[1] >= 0) close(header_pipe[1]); abort_connection(conn, connection_state_for_errno(s_errno)); return; } conn->from = 0; conn->unrestartable = 1; find_auth(conn->uri); /* remember username and password */ cpid = fork(); if (cpid == -1) { int s_errno = errno; close(smb_pipe[0]); close(smb_pipe[1]); close(header_pipe[0]); close(header_pipe[1]); retry_connection(conn, connection_state_for_errno(s_errno)); return; } if (!cpid) { dup2(open("/dev/null", O_RDONLY), 0); close(1); close(2); data_out = fdopen(smb_pipe[1], "w"); header_out = fdopen(header_pipe[1], "w"); if (!data_out || !header_out) exit(1); close(smb_pipe[0]); close(header_pipe[0]); /* There may be outgoing data in stdio buffers * inherited from the parent process. The parent * process is going to write this data, so the child * process must not do that. Closing the file * descriptors ensures this. * * FIXME: If something opens more files and gets the * same file descriptors and does not close them * before exit(), then stdio may attempt to write the * buffers to the wrong files. This might happen for * example if libsmbclient calls syslog(). */ close_all_fds_but_two(smb_pipe[1], header_pipe[1]); do_smb(conn); } else { struct read_buffer *buf2; conn->data_socket->fd = smb_pipe[0]; conn->socket->fd = header_pipe[0]; set_nonblocking_fd(conn->data_socket->fd); set_nonblocking_fd(conn->socket->fd); close(smb_pipe[1]); close(header_pipe[1]); buf2 = alloc_read_buffer(conn->socket); if (!buf2) { close_socket(conn->data_socket); close_socket(conn->socket); abort_connection(conn, connection_state(S_OUT_OF_MEM)); return; } read_from_socket(conn->socket, buf2, connection_state(S_CONN), smb_got_header); } }
void connect_socket(struct socket *csocket, struct 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", 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; 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 */ /* 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); }
/* 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; }
/* Based on network/socket.c:get_pasv_socket() but modified to try and bind to a * port range instead of any port. */ struct connection_state init_bittorrent_listening_socket(struct connection *conn) { struct bittorrent_connection *bittorrent = conn->info; struct sockaddr_in addr, addr2; uint16_t port, max_port; int len; /* XXX: Always add the connection to the list even if we fail so we can * safely assume it is in done_bittorrent_listening_socket(). */ add_to_list(bittorrent_connections, bittorrent); /* Has the socket already been initialized? */ if (!list_is_singleton(bittorrent_connections)) return connection_state(S_OK); /* We could have bailed out from an earlier attempt. */ if (bittorrent_socket != -1) close(bittorrent_socket); bittorrent_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (bittorrent_socket < 0) return connection_state_for_errno(errno); /* Set it non-blocking */ if (set_nonblocking_fd(bittorrent_socket) < 0) return connection_state_for_errno(errno); /* Bind it to some port */ port = get_opt_int("protocol.bittorrent.ports.min", NULL); max_port = get_opt_int("protocol.bittorrent.ports.max", NULL); memset(&addr, 0, sizeof(addr)); addr.sin_port = htons(port); /* Repeatedly try the configured port range. */ while (bind(bittorrent_socket, (struct sockaddr *) &addr, sizeof(addr))) { if (errno != EADDRINUSE) return connection_state_for_errno(errno); /* If all ports was in use fail with EADDRINUSE. */ if (++port > max_port) return connection_state_for_errno(errno); memset(&addr, 0, sizeof(addr)); addr.sin_port = htons(port); } /* Get the endpoint info about the new socket and save it */ memset(&addr2, 0, sizeof(addr2)); len = sizeof(addr2); if (getsockname(bittorrent_socket, (struct sockaddr *) &addr2, &len)) return connection_state_for_errno(errno); bittorrent->port = ntohs(addr2.sin_port); /* Go listen */ if (listen(bittorrent_socket, LISTEN_BACKLOG)) return connection_state_for_errno(errno); set_ip_tos_throughput(bittorrent_socket); set_handlers(bittorrent_socket, accept_bittorrent_peer_connection, NULL, NULL, NULL); return connection_state(S_OK); }
/* To reduce redundant error handling code [calls to abort_connection()] * most of the function is build around conditions that will assign the error * code to @state if anything goes wrong. The rest of the function will then just * do the necessary cleanups. If all works out we end up with @state being S_OK * resulting in a cache entry being created with the fragment data generated by * either reading the file content or listing a directory. */ void file_protocol_handler(struct connection *connection) { unsigned char *redirect_location = NULL; struct string page, name; struct connection_state state; int set_dir_content_type = 0; if (get_cmd_opt_bool((const unsigned char *)"anonymous")) { if (strcmp((const char *)connection->uri->string, "file:///dev/stdin") || isatty(STDIN_FILENO)) { abort_connection(connection, connection_state(S_FILE_ANONYMOUS)); return; } } #ifdef CONFIG_CGI if (!execute_cgi(connection)) return; #endif /* CONFIG_CGI */ /* Treat /dev/stdin in special way */ if (!strcmp((const char *)connection->uri->string, "file:///dev/stdin")) { int fd = open("/dev/stdin", O_RDONLY); if (fd == -1) { abort_connection(connection, connection_state(-errno)); return; } set_nonblocking_fd(fd); if (!init_http_connection_info(connection, 1, 0, 1)) { abort_connection(connection, connection_state(S_OUT_OF_MEM)); close(fd); return; } connection->socket->fd = fd; connection->data_socket->fd = -1; read_from_stdin(connection); return; } /* This function works on already simplified file-scheme URI pre-chewed * by transform_file_url(). By now, the function contains no hostname * part anymore, possibly relative path is converted to an absolute one * and uri->data is just the final path to file/dir we should try to * show. */ if (!init_string(&name) || !add_uri_to_string(&name, connection->uri, URI_PATH)) { done_string(&name); abort_connection(connection, connection_state(S_OUT_OF_MEM)); return; } decode_uri_string(&name); /* In Win32, file_is_dir seems to always return 0 if the name * ends with a directory separator. */ if ((name.length > 0 && dir_sep(name.source[name.length - 1])) || file_is_dir(name.source)) { /* In order for global history and directory listing to * function properly the directory url must end with a * directory separator. */ if (name.source[0] && !dir_sep(name.source[name.length - 1])) { redirect_location = (unsigned char *)STRING_DIR_SEP; state = connection_state(S_OK); } else { state = list_directory(connection, name.source, &page); set_dir_content_type = 1; } } else { state = read_encoded_file(&name, &page); /* FIXME: If state is now S_ENCODE_ERROR we should try loading * the file undecoded. --jonas */ } done_string(&name); if (is_in_state(state, S_OK)) { struct cache_entry *cached; /* Try to add fragment data to the connection cache if either * file reading or directory listing worked out ok. */ cached = connection->cached = get_cache_entry(connection->uri); if (!connection->cached) { if (!redirect_location) done_string(&page); state = connection_state(S_OUT_OF_MEM); } else if (redirect_location) { if (!redirect_cache(cached, redirect_location, 1, 0)) state = connection_state(S_OUT_OF_MEM); } else { add_fragment(cached, 0, page.source, page.length); connection->from += page.length; if (!cached->head && set_dir_content_type) { unsigned char *head; /* If the system charset somehow * changes after the directory listing * has been generated, it should be * parsed with the original charset. */ head = straconcat((const unsigned char *)"\r\nContent-Type: text/html; charset=", get_cp_mime_name(get_cp_index((const unsigned char *)"System")), "\r\n", (unsigned char *) NULL); /* Not so gracefully handle failed memory * allocation. */ if (!head) state = connection_state(S_OUT_OF_MEM); /* Setup directory listing for viewing. */ mem_free_set(&cached->head, head); } done_string(&page); } } abort_connection(connection, state); }