📄 ftp.c
字号:
/* Internal "ftp" protocol implementation *//* $Id: ftp.c,v 1.182.2.7 2005/06/10 14:09:24 jonas Exp $ */#ifdef HAVE_CONFIG_H#include "config.h"#endif#include <stdio.h>#include <ctype.h>#include <errno.h>#include <stdlib.h>#include <string.h>#include <sys/stat.h> /* For converting permissions to strings */#include <sys/types.h>#ifdef HAVE_SYS_SOCKET_H#include <sys/socket.h>#endif#ifdef HAVE_UNISTD_H#include <unistd.h>#endif#ifdef HAVE_FCNTL_H#include <fcntl.h> /* OS/2 needs this after sys/types.h */#endif/* We need to have it here. Stupid BSD. */#ifdef HAVE_NETINET_IN_H#include <netinet/in.h>#endif#ifdef HAVE_ARPA_INET_H#include <arpa/inet.h>#endif#include "elinks.h"#include "cache/cache.h"#include "config/options.h"#include "intl/gettext/libintl.h"#include "lowlevel/connect.h"#include "lowlevel/select.h"#include "modules/module.h"#include "osdep/osdep.h"#include "protocol/auth/auth.h"#include "protocol/ftp/ftp.h"#include "protocol/ftp/parse.h"#include "protocol/uri.h"#include "sched/connection.h"#include "util/conv.h"#include "util/error.h"#include "util/memory.h"#include "util/string.h"struct option_info ftp_options[] = { INIT_OPT_TREE("protocol", N_("FTP"), "ftp", 0, N_("FTP specific options.")), INIT_OPT_TREE("protocol.ftp", N_("Proxy configuration"), "proxy", 0, N_("FTP proxy configuration.")), INIT_OPT_STRING("protocol.ftp.proxy", N_("Host and port-number"), "host", 0, "", N_("Host and port-number (host:port) of the FTP proxy, or blank.\n" "If it's blank, FTP_PROXY environment variable is checked as well.")), INIT_OPT_STRING("protocol.ftp", N_("Anonymous password"), "anon_passwd", 0, "some@host.domain", N_("FTP anonymous password to be sent.")), INIT_OPT_BOOL("protocol.ftp", N_("Use passive mode (IPv4)"), "use_pasv", 0, 1, N_("Use PASV instead of PORT (passive vs active mode, IPv4 only).")),#ifdef CONFIG_IPV6 INIT_OPT_BOOL("protocol.ftp", N_("Use passive mode (IPv6)"), "use_epsv", 0, 0, N_("Use EPSV instead of EPRT (passive vs active mode, IPv6 only).")),#endif /* CONFIG_IPV6 */ NULL_OPTION_INFO,};struct module ftp_protocol_module = struct_module( /* name: */ N_("FTP"), /* options: */ ftp_options, /* hooks: */ NULL, /* submodules: */ NULL, /* data: */ NULL, /* init: */ NULL, /* done: */ NULL);/* Constants */#define FTP_BUF_SIZE 16384/* Types and structs */struct ftp_connection_info { int pending_commands; /* Num of commands queued */ int opc; /* Total num of commands queued */ int conn_state; int buf_pos; unsigned int dir:1; /* Directory listing in progress */ unsigned int rest_sent:1; /* Sent RESTore command */ unsigned int has_data:1; /* Do we have data socket? */ unsigned int use_pasv:1; /* Use PASV (yes or no) */#ifdef CONFIG_IPV6 unsigned int use_epsv:1; /* Use EPSV */#endif unsigned char ftp_buffer[FTP_BUF_SIZE]; unsigned char cmd_buffer[1]; /* Must be last field !! */};/* Prototypes */static void ftp_login(struct connection *);static void ftp_send_retr_req(struct connection *, int);static void ftp_got_info(struct connection *, struct read_buffer *);static void ftp_got_user_info(struct connection *, struct read_buffer *);static void ftp_pass(struct connection *);static void ftp_pass_info(struct connection *, struct read_buffer *);static void ftp_retr_file(struct connection *, struct read_buffer *);static void ftp_got_final_response(struct connection *, struct read_buffer *);static void got_something_from_data_connection(struct connection *);static void ftp_end_request(struct connection *, int);static struct ftp_connection_info *add_file_cmd_to_str(struct connection *);/* Parse EPSV or PASV response for address and/or port. * int *n should point to a sizeof(int) * 6 space. * It returns zero on error or count of parsed numbers. * It returns an error if: * - there's more than 6 or less than 1 numbers. * - a number is strictly greater than max. * * On success, array of integers *n is filled with numbers starting * from end of array (ie. if we found one number, you can access it using * n[5]). * * Important: * Negative numbers aren't handled so -123 is taken as 123. * We don't take care about separators.*/static intparse_psv_resp(unsigned char *data, int *n, int max_value){ unsigned char *p = data; int i = 5; memset(n, 0, 6 * sizeof(*n)); if (*p < ' ') return 0; /* Find the end. */ while (*p >= ' ') p++; /* Ignore non-numeric ending chars. */ while (p != data && !isdigit(*p)) p--; if (p == data) return 0; while (i >= 0) { int x = 1; /* Parse one number. */ while (p != data && isdigit(*p)) { n[i] += (*p - '0') * x; if (n[i] > max_value) return 0; x *= 10; p--; } /* Ignore non-numeric chars. */ while (p != data && !isdigit(*p)) p--; if (p == data) return (6 - i); /* Get the next one. */ i--; } return 0;}/* Returns 0 if there's no numeric response, -1 if error, the positive response * number otherwise. */static intget_ftp_response(struct connection *conn, struct read_buffer *rb, int part, struct sockaddr_storage *sa){ int pos; set_connection_timeout(conn);again: for (pos = 0; pos < rb->len; pos++) { unsigned char *num_end; int response; if (rb->data[pos] != ASCII_LF) continue; errno = 0; response = strtoul(rb->data, (char **) &num_end, 10); if (errno || num_end != rb->data + 3 || response < 100) return -1; if (sa && response == 227) { /* PASV response parsing. */ struct sockaddr_in *s = (struct sockaddr_in *) sa; int n[6]; if (parse_psv_resp(num_end, (int *) &n, 255) != 6) return -1; memset(s, 0, sizeof(*s)); s->sin_family = AF_INET; s->sin_addr.s_addr = htonl((n[0] << 24) + (n[1] << 16) + (n[2] << 8) + n[3]); s->sin_port = htons((n[4] << 8) + n[5]); }#ifdef CONFIG_IPV6 if (sa && response == 229) { /* EPSV response parsing. */ /* See RFC 2428 */ struct sockaddr_in6 *s = (struct sockaddr_in6 *) sa; int sal = sizeof(*s); int n[6]; if (parse_psv_resp(num_end, (int *) &n, 65535) != 1) { return -1; } memset(s, 0, sizeof(*s)); if (getpeername(conn->socket.fd, (struct sockaddr *) sa, &sal)) { return -1; } s->sin6_family = AF_INET6; s->sin6_port = htons(n[5]); }#endif if (*num_end == '-') { int i; for (i = 0; i < rb->len - 5; i++) if (rb->data[i] == ASCII_LF && !memcmp(rb->data+i+1, rb->data, 3) && rb->data[i+4] == ' ') { for (i++; i < rb->len; i++) if (rb->data[i] == ASCII_LF) goto ok; return 0; } return 0;ok: pos = i; } if (!part && response >= 100 && response < 200) { kill_buffer_data(rb, pos + 1); goto again; } if (part == 2) return response; kill_buffer_data(rb, pos + 1); return response; } return 0;}/* Initialize or continue ftp connection. */voidftp_protocol_handler(struct connection *conn){ set_connection_timeout(conn); if (!has_keepalive_connection(conn)) { make_connection(conn, &conn->socket, ftp_login); } else { ftp_send_retr_req(conn, S_SENT); }}/* Get connection response. */static voidget_resp(struct connection *conn){ struct read_buffer *rb = alloc_read_buffer(conn); if (!rb) return; read_from_socket(conn, &conn->socket, rb, conn->read_func);}/* Send command, set connection state and free cmd string. */static voidsend_cmd(struct connection *conn, struct string *cmd, void *callback, int state){ conn->read_func = callback; write_to_socket(conn, &conn->socket, cmd->source, cmd->length, get_resp); done_string(cmd); set_connection_state(conn, state);}/* Check if this auth token really belongs to this URI. */static intauth_user_matching_uri(struct auth_entry *auth, struct uri *uri){ if (!uri->userlen) /* Noone said it doesn't. */ return 1; return !strlcasecmp(auth->user, -1, uri->user, uri->userlen);}/* Kill the current connection and ask for a username/password for the next * try. */static voidprompt_username_pw(struct connection *conn){ if (!conn->cached) { conn->cached = get_cache_entry(conn->uri); if (!conn->cached) { abort_conn_with_state(conn, S_OUT_OF_MEM); return; } } mem_free_set(&conn->cached->content_type, stracpy("text/html")); if (!conn->cached->content_type) { abort_conn_with_state(conn, S_OUT_OF_MEM); return; } add_auth_entry(conn->uri, "FTP Login", NULL, NULL, 0); abort_conn_with_state(conn, S_OK);}/* Send USER command. */static voidftp_login(struct connection *conn){ struct string cmd; struct auth_entry* auth; auth = find_auth(conn->uri); if (!init_string(&cmd)) { abort_conn_with_state(conn, S_OUT_OF_MEM); return; } add_to_string(&cmd, "USER "); if (conn->uri->userlen) { struct uri *uri = conn->uri; add_bytes_to_string(&cmd, uri->user, uri->userlen); } else if (auth && auth->valid) { add_to_string(&cmd, auth->user); } else { add_to_string(&cmd, "anonymous"); } add_crlf_to_string(&cmd); send_cmd(conn, &cmd, (void *) ftp_got_info, S_SENT);}/* Parse connection response. */static voidftp_got_info(struct connection *conn, struct read_buffer *rb){ int response = get_ftp_response(conn, rb, 0, NULL); if (response == -1) { abort_conn_with_state(conn, S_FTP_ERROR); return; } if (!response) { read_from_socket(conn, &conn->socket, rb, ftp_got_info); return; } /* RFC959 says that possible response codes on connection are: * 120 Service ready in nnn minutes. * 220 Service ready for new user. * 421 Service not available, closing control connection. */ if (response != 220) { /* TODO? Retry in case of ... ?? */ retry_conn_with_state(conn, S_FTP_UNAVAIL); return; } ftp_got_user_info(conn, rb);}/* Parse USER response and send PASS command if needed. */static voidftp_got_user_info(struct connection *conn, struct read_buffer *rb){ int response = get_ftp_response(conn, rb, 0, NULL); if (response == -1) { abort_conn_with_state(conn, S_FTP_ERROR); return; } if (!response) { read_from_socket(conn, &conn->socket, rb, ftp_got_user_info); return; } /* RFC959 says that possible response codes for USER are: * 230 User logged in, proceed. * 331 User name okay, need password. * 332 Need account for login. * 421 Service not available, closing control connection. * 500 Syntax error, command unrecognized. * 501 Syntax error in parameters or arguments. * 530 Not logged in. */ /* FIXME? Since ACCT command isn't implemented, login to a ftp server * requiring it will fail (332). */ if (response == 332 || response >= 500) { prompt_username_pw(conn); return; } /* We don't require exact match here, as this is always error and some * non-RFC compliant servers may return even something other than 421. * --Zas */ if (response >= 400) { abort_conn_with_state(conn, S_FTP_UNAVAIL); return; } if (response == 230) { ftp_send_retr_req(conn, S_GETH); return; } ftp_pass(conn);}/* Send PASS command. */static voidftp_pass(struct connection *conn){ struct string cmd; struct auth_entry *auth; auth = find_auth(conn->uri); if (!init_string(&cmd)) { abort_conn_with_state(conn, S_OUT_OF_MEM); return; }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -