/* 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); }
/* Returns a connection state. S_OK if all is well. */ static inline struct connection_state list_directory(struct connection *conn, unsigned char *dirpath, struct string *page) { int show_hidden_files = get_opt_bool((const unsigned char *)"protocol.file.show_hidden_files", NULL); struct directory_entry *entries; struct connection_state state; errno = 0; entries = get_directory_entries(dirpath, show_hidden_files); if (!entries) { if (errno) return connection_state_for_errno(errno); return connection_state(S_OUT_OF_MEM); } state = init_directory_listing(page, conn->uri); if (!is_in_state(state, S_OK)) return connection_state(S_OUT_OF_MEM); add_dir_entries(entries, dirpath, page); if (!add_to_string(page, (const unsigned char *)"</pre>\n<hr/>\n</body>\n</html>\n")) { done_string(page); return connection_state(S_OUT_OF_MEM); } return connection_state(S_OK); }
static void fsp_directory(FSP_SESSION *ses, struct uri *uri) { struct string buf; FSP_DIR *dir; unsigned char *data = get_uri_string(uri, URI_DATA); unsigned char dircolor[8] = ""; if (!data) fsp_error(connection_state(S_OUT_OF_MEM)); decode_uri(data); if (!is_in_state(init_directory_listing(&buf, uri), S_OK)) fsp_error(connection_state(S_OUT_OF_MEM)); dir = fsp_opendir(ses, data); if (!dir) fsp_error(connection_state_for_errno(errno)); fprintf(stderr, "text/html"); fclose(stderr); puts(buf.source); if (get_opt_bool("document.browse.links.color_dirs", NULL)) { color_to_string(get_opt_color("document.colors.dirs", NULL), dircolor); } sort_and_display_entries(dir, dircolor); fsp_closedir(dir); puts("</pre><hr/></body></html>"); fsp_close_session(ses); exit(0); }
static void smb_got_data(struct socket *socket, struct read_buffer *rb) { int len = rb->length; struct connection *conn = socket->conn; if (len < 0) { abort_connection(conn, connection_state_for_errno(errno)); return; } if (!len) { abort_connection(conn, connection_state(S_OK)); return; } socket->state = SOCKET_END_ONCLOSE; conn->received += len; if (add_fragment(conn->cached, conn->from, rb->data, len) == 1) conn->tries = 0; conn->from += len; kill_buffer_data(rb, len); read_from_socket(socket, rb, connection_state(S_TRANS), smb_got_data); }
/** @return -2 if no data was read but the caller should retry; * -1 if an error occurred and *@a error was set; 0 at end of data; * a positive number if that many bytes were read. * * @relates http_post */ static int read_http_post_fd(struct http_post *http_post, unsigned char buffer[], int max, struct connection_state *error) { const struct http_post_file *const file = &http_post->files[http_post->file_index]; int ret; /* safe_read() would set errno = EBADF anyway, but check this * explicitly to make any such bugs easier to detect. */ assert(http_post->post_fd >= 0); if_assert_failed { *error = connection_state(S_INTERNAL); return -1; } ret = safe_read(http_post->post_fd, buffer, max); if (ret <= 0) { const int errno_from_read = errno; close(http_post->post_fd); http_post->post_fd = -1; http_post->file_index++; /* http_post->file_read is used below so don't clear it here. * It will be cleared when the next file is opened. */ if (ret == -1) { *error = connection_state_for_errno(errno_from_read); return -1; } else if (http_post->file_read != file->size) { /* ELinks already sent a Content-Length header * based on the size of this file, but the * file has since been shrunk. Abort the * connection because ELinks can no longer get * enough data to fill the Content-Length. * (Well, it could pad with zeroes, but that * would be just weird.) */ *error = connection_state(S_HTTP_UPLOAD_RESIZED); return -1; } else { /* The upload file ended but there may still * be more data in uri.post. If not, * read_http_post_inline() will return 0 to * indicate the final end of file. */ return -2; } } http_post->file_read += ret; if (http_post->file_read > file->size) { /* ELinks already sent a Content-Length header based * on the size of this file, but the file has since * been extended. Abort the connection because ELinks * can no longer fit the entire file in the original * Content-Length. */ *error = connection_state(S_HTTP_UPLOAD_RESIZED); return -1; } return ret; }
static void smb_got_error(struct socket *socket, struct read_buffer *rb) { int len = rb->length; struct connection *conn = socket->conn; struct connection_state error; if (len < 0) { abort_connection(conn, connection_state_for_errno(errno)); return; } /* There should be free space in the buffer, because * @alloc_read_buffer allocated several kibibytes, and the * child process wrote only an integer and a newline to the * pipe. */ assert(rb->freespace >= 1); if_assert_failed { abort_connection(conn, connection_state(S_INTERNAL)); return; } rb->data[len] = '\0'; switch (rb->data[0]) { case 'S': error = connection_state_for_errno(atoi(rb->data + 1)); break; case 'I': error = connection_state(atoi(rb->data + 1)); break; default: ERROR("malformed error code: %s", rb->data); error = connection_state(S_INTERNAL); break; } kill_buffer_data(rb, len); if (is_system_error(error) && error.syserr == EACCES) prompt_username_pw(conn); else abort_connection(conn, error); }
/** @return -2 if no data was read but the caller should retry; * -1 if an error occurred and *@a error was set; 0 at end of data; * a positive number if that many bytes were read. * * @relates http_post */ static int read_http_post_inline(struct http_post *http_post, unsigned char buffer[], int max, struct connection_state *error) { const unsigned char *post = http_post->post_data; const unsigned char *end = (const unsigned char *)strchr((char *)post, FILE_CHAR); int total = 0; assert(http_post->post_fd < 0); if_assert_failed { *error = connection_state(S_INTERNAL); return -1; } if (!end) end = (const unsigned char *)strchr((char *)post, '\0'); while (post < end && total < max) { int h1, h2; h1 = unhx(post[0]); assertm(h1 >= 0 && h1 < 16, "h1 in the POST buffer is %d (%d/%c)", h1, post[0], post[0]); if_assert_failed h1 = 0; h2 = unhx(post[1]); assertm(h2 >= 0 && h2 < 16, "h2 in the POST buffer is %d (%d/%c)", h2, post[1], post[1]); if_assert_failed h2 = 0; buffer[total++] = (h1<<4) + h2; post += 2; } if (post != end || *end != FILE_CHAR) { http_post->post_data = post; return total; } http_post->file_read = 0; end = (const unsigned char *)strchr((char *)(post + 1), FILE_CHAR); assert(end); http_post->post_fd = open((const char *)http_post->files[http_post->file_index].name, O_RDONLY); /* Be careful not to change errno here. */ if (http_post->post_fd < 0) { http_post->post_data = post; if (total > 0) return total; /* retry the open on the next call */ else { *error = connection_state_for_errno(errno); return -1; } } http_post->post_data = end + 1; return total ? total : -2; }
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); } }
static void do_smb(struct connection *conn) { struct uri *uri = conn->uri; struct auth_entry *auth = find_auth(uri); struct string string; unsigned char *url; int dir; if ((uri->userlen && uri->passwordlen) || !auth) { url = get_uri_string(uri, URI_BASE); } else { unsigned char *uri_string = get_uri_string(uri, URI_HOST | URI_PORT | URI_DATA); if (!uri_string || !init_string(&string)) { smb_error(connection_state(S_OUT_OF_MEM)); } /* Must URI-encode the username and password to avoid * ambiguity if they contain "/:@" characters. * Libsmbclient then decodes them again, and the * server gets them as they were in auth->user and * auth->password, i.e. as the user typed them in the * auth dialog. This implies that, if the username or * password contains some characters or bytes that the * user cannot directly type, then she cannot enter * them. If that becomes an actual problem, it should * be fixed in the auth dialog, e.g. by providing a * hexadecimal input mode. */ add_to_string(&string, "smb://"); encode_uri_string(&string, auth->user, -1, 1); add_char_to_string(&string, ':'); encode_uri_string(&string, auth->password, -1, 1); add_char_to_string(&string, '@'); add_to_string(&string, uri_string); url = string.source; } if (!url) { smb_error(connection_state(S_OUT_OF_MEM)); } if (smbc_init(smb_auth, 0)) { smb_error(connection_state_for_errno(errno)); }; dir = smbc_opendir(url); if (dir >= 0) { struct string prefix; init_string(&prefix); add_to_string(&prefix, url); add_char_to_string(&prefix, '/'); smb_directory(dir, &prefix, conn->uri); done_string(&prefix); } else { const int errno_from_opendir = errno; char buf[READ_SIZE]; struct stat sb; int r, res, fdout; int file = smbc_open(url, O_RDONLY, 0); if (file < 0) { /* If we're opening the list of shares without * proper authentication, then smbc_opendir * fails with EACCES and smbc_open fails with * ENOENT. In this case, return the EACCES so * that the parent ELinks process will prompt * for credentials. */ if (errno == ENOENT && errno_from_opendir == EACCES) errno = errno_from_opendir; smb_error(connection_state_for_errno(errno)); } res = smbc_fstat(file, &sb); if (res) { smb_error(connection_state_for_errno(res)); } /* filesize */ fprintf(header_out, "%" OFF_PRINT_FORMAT, (off_print_T) sb.st_size); fclose(header_out); fdout = fileno(data_out); while ((r = smbc_read(file, buf, READ_SIZE)) > 0) { if (safe_write(fdout, buf, r) <= 0) break; } smbc_close(file); exit(0); } }
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 = 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); } }
static void write_select(struct socket *socket) { struct write_buffer *wb = 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 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); }
/** Prepare to read POST data from a URI and possibly to upload files. * * @param http_post * Must have been initialized with init_http_post(). * @param[in] post_data * The body of the POST request as formatted by get_form_uri(). * However, unlike uri.post, @a post_data must not contain any * Content-Type. The caller must ensure that the @a post_data * pointer remains valid until done_http_post(). * @param[out] error * If the function fails, it writes the error state here so that * the caller can pass that on to abort_connection(). If the * function succeeds, the value of *@a error is undefined. * * This function does not parse the Content-Type from uri.post; the * caller must do that. This is because in local CGI, the child * process handles the Content-Type (saving it to an environment * variable before exec) but the parent process handles the body of * the request (feeding it to the child process via a pipe). * * @return nonzero on success, zero on error. * * @relates http_post */ int open_http_post(struct http_post *http_post, const unsigned char *post_data, struct connection_state *error) { off_t size = 0; size_t length = strlen((const char *)post_data); const unsigned char *end = post_data; done_http_post(http_post); http_post->post_data = end; while (1) { struct stat sb; const unsigned char *begin; int res; struct http_post_file *new_files; unsigned char *filename; begin = (const unsigned char *)strchr((char *)end, FILE_CHAR); if (!begin) break; end = (const unsigned char *)strchr((char *)(begin + 1), FILE_CHAR); if (!end) break; filename = memacpy(begin + 1, end - begin - 1); /* adds '\0' */ if (!filename) { done_http_post(http_post); *error = connection_state(S_OUT_OF_MEM); return 0; } decode_uri(filename); res = stat((const char *)filename, &sb); if (res) { *error = connection_state_for_errno(errno); done_http_post(http_post); return 0; } /* This use of mem_realloc() in a loop consumes O(n^2) * time but how many files are you really going to * upload in one request? */ new_files = (struct http_post_file *)mem_realloc(http_post->files, (http_post->file_count + 1) * sizeof(*new_files)); if (new_files == NULL) { mem_free(filename); done_http_post(http_post); *error = connection_state(S_OUT_OF_MEM); return 0; } http_post->files = new_files; new_files[http_post->file_count].name = filename; new_files[http_post->file_count].size = sb.st_size; http_post->file_count++; size += sb.st_size; length -= (end - begin + 1); end++; } size += (length / 2); http_post->total_upload_length = size; return 1; }
static void do_fsp(struct connection *conn) { FSP_SESSION *ses; struct stat sb; struct uri *uri = conn->uri; struct auth_entry *auth; unsigned char *host = get_uri_string(uri, URI_HOST); unsigned char *data = get_uri_string(uri, URI_DATA); unsigned short port = (unsigned short)get_uri_port(uri); unsigned char *password = NULL; decode_uri(data); if (uri->passwordlen) { password = get_uri_string(uri, URI_PASSWORD); } else { auth = find_auth(uri); if (auth) password = auth->password; } /* fsp_open_session may not set errno if getaddrinfo fails * https://sourceforge.net/tracker/index.php?func=detail&aid=2036798&group_id=93841&atid=605738 * Try to detect this bug and use an ELinks-specific error * code instead, so that we can display a message anyway. */ errno = 0; ses = fsp_open_session(host, port, password); if (!ses) { if (errno) fsp_error(connection_state_for_errno(errno)); else fsp_error(connection_state(S_FSP_OPEN_SESSION_UNKN)); } /* fsplib 0.8 ABI depends on _FILE_OFFSET_BITS * https://sourceforge.net/tracker/index.php?func=detail&aid=1674729&group_id=93841&atid=605738 * If ELinks and fsplib are using different values of * _FILE_OFFSET_BITS, then they get different definitions of * struct stat, and the st_size stored by fsp_stat is * typically not the same as the st_size read by ELinks. * Fortunately, st_mode seems to have the same offset and size * in both versions of struct stat. * * If all the bytes used by the 32-bit st_size are also used * by the 64-bit st_size, then ELinks may be able to guess * which ones they are, because the current version 2 of FSP * supports only 32-bit file sizes in protocol packets. Begin * by filling struct stat with 0xAA so that it's easier to * detect which bytes fsp_stat has left unchanged. (Only * sb.st_size really needs to be filled, but filling the rest * too helps viewing the data with a debugger.) */ memset(&sb, 0xAA, sizeof(sb)); if (fsp_stat(ses, data, &sb)) fsp_error(connection_state_for_errno(errno)); if (S_ISDIR(sb.st_mode)) { fsp_directory(ses, uri); } else { /* regular file */ char buf[READ_SIZE]; FSP_FILE *file = fsp_fopen(ses, data, "r"); int r; if (!file) { fsp_error(connection_state_for_errno(errno)); } #if SIZEOF_OFF_T >= 8 if (sb.st_size < 0 || sb.st_size > 0xFFFFFFFF) { /* Probably a _FILE_OFFSET_BITS mismatch as * described above. Try to detect which half * of st_size is the real size. This may * depend on the endianness of the processor * and on the padding in struct stat. */ if ((sb.st_size & 0xFFFFFFFF00000000ULL) == 0xAAAAAAAA00000000ULL) sb.st_size = sb.st_size & 0xFFFFFFFF; else if ((sb.st_size & 0xFFFFFFFF) == 0xAAAAAAAA) sb.st_size = (sb.st_size >> 32) & 0xFFFFFFFF; else /* Can't figure it out. */ sb.st_size = 1; } #endif /* Send filesize */ fprintf(stderr, "%" OFF_PRINT_FORMAT "\n", (off_print_T) sb.st_size); fclose(stderr); while ((r = fsp_fread(buf, 1, READ_SIZE, file)) > 0) { int off = 0; while (r) { int w = safe_write(STDOUT_FILENO, buf + off, r); if (w == -1) goto out; off += w; r -= w; } } out: fsp_fclose(file); fsp_close_session(ses); exit(0); }