1041: Add ftp_add_unparsed_line: HTML entities and more error checks.
[elinks.git] / src / protocol / ftp / ftp.c
blob92de3a60050cb7094da6125d04a1d1484bebb73f
1 /* Internal "ftp" protocol implementation */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #include <stdio.h>
8 #include <ctype.h>
9 #include <errno.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <sys/stat.h> /* For converting permissions to strings */
13 #include <sys/types.h>
14 #ifdef HAVE_SYS_SOCKET_H
15 #include <sys/socket.h>
16 #endif
17 #ifdef HAVE_UNISTD_H
18 #include <unistd.h>
19 #endif
20 #ifdef HAVE_FCNTL_H
21 #include <fcntl.h> /* OS/2 needs this after sys/types.h */
22 #endif
24 /* We need to have it here. Stupid BSD. */
25 #ifdef HAVE_NETINET_IN_H
26 #include <netinet/in.h>
27 #endif
28 #ifdef HAVE_ARPA_INET_H
29 #include <arpa/inet.h>
30 #endif
32 #include "elinks.h"
34 #include "cache/cache.h"
35 #include "config/options.h"
36 #include "intl/gettext/libintl.h"
37 #include "main/select.h"
38 #include "main/module.h"
39 #include "network/connection.h"
40 #include "network/progress.h"
41 #include "network/socket.h"
42 #include "osdep/osdep.h"
43 #include "osdep/stat.h"
44 #include "protocol/auth/auth.h"
45 #include "protocol/common.h"
46 #include "protocol/ftp/ftp.h"
47 #include "protocol/ftp/parse.h"
48 #include "protocol/uri.h"
49 #include "util/conv.h"
50 #include "util/error.h"
51 #include "util/memory.h"
52 #include "util/string.h"
55 struct option_info ftp_options[] = {
56 INIT_OPT_TREE("protocol", N_("FTP"),
57 "ftp", 0,
58 N_("FTP specific options.")),
60 INIT_OPT_TREE("protocol.ftp", N_("Proxy configuration"),
61 "proxy", 0,
62 N_("FTP proxy configuration.")),
64 INIT_OPT_STRING("protocol.ftp.proxy", N_("Host and port-number"),
65 "host", 0, "",
66 N_("Host and port-number (host:port) of the FTP proxy, or blank.\n"
67 "If it's blank, FTP_PROXY environment variable is checked as well.")),
69 INIT_OPT_STRING("protocol.ftp", N_("Anonymous password"),
70 "anon_passwd", 0, "some@host.domain",
71 N_("FTP anonymous password to be sent.")),
73 INIT_OPT_BOOL("protocol.ftp", N_("Use passive mode (IPv4)"),
74 "use_pasv", 0, 1,
75 N_("Use PASV instead of PORT (passive vs active mode, IPv4 only).")),
76 #ifdef CONFIG_IPV6
77 INIT_OPT_BOOL("protocol.ftp", N_("Use passive mode (IPv6)"),
78 "use_epsv", 0, 0,
79 N_("Use EPSV instead of EPRT (passive vs active mode, IPv6 only).")),
80 #endif /* CONFIG_IPV6 */
81 NULL_OPTION_INFO,
85 struct module ftp_protocol_module = struct_module(
86 /* name: */ N_("FTP"),
87 /* options: */ ftp_options,
88 /* hooks: */ NULL,
89 /* submodules: */ NULL,
90 /* data: */ NULL,
91 /* init: */ NULL,
92 /* done: */ NULL
96 /* Constants */
98 #define FTP_BUF_SIZE 16384
101 /* Types and structs */
103 struct ftp_connection_info {
104 int pending_commands; /* Num of commands queued */
105 int opc; /* Total num of commands queued */
106 int conn_state;
107 int buf_pos;
109 unsigned int dir:1; /* Directory listing in progress */
110 unsigned int rest_sent:1; /* Sent RESTore command */
111 unsigned int use_pasv:1; /* Use PASV (yes or no) */
112 #ifdef CONFIG_IPV6
113 unsigned int use_epsv:1; /* Use EPSV */
114 #endif
115 unsigned char ftp_buffer[FTP_BUF_SIZE];
116 unsigned char cmd_buffer[1]; /* Must be last field !! */
120 /* Prototypes */
121 static void ftp_login(struct socket *);
122 static void ftp_send_retr_req(struct connection *, struct connection_state);
123 static void ftp_got_info(struct socket *, struct read_buffer *);
124 static void ftp_got_user_info(struct socket *, struct read_buffer *);
125 static void ftp_pass(struct connection *);
126 static void ftp_pass_info(struct socket *, struct read_buffer *);
127 static void ftp_retr_file(struct socket *, struct read_buffer *);
128 static void ftp_got_final_response(struct socket *, struct read_buffer *);
129 static void got_something_from_data_connection(struct connection *);
130 static void ftp_end_request(struct connection *, struct connection_state);
131 static struct ftp_connection_info *add_file_cmd_to_str(struct connection *);
132 static void ftp_data_accept(struct connection *conn);
134 /* Parse EPSV or PASV response for address and/or port.
135 * int *n should point to a sizeof(int) * 6 space.
136 * It returns zero on error or count of parsed numbers.
137 * It returns an error if:
138 * - there's more than 6 or less than 1 numbers.
139 * - a number is strictly greater than max.
141 * On success, array of integers *n is filled with numbers starting
142 * from end of array (ie. if we found one number, you can access it using
143 * n[5]).
145 * Important:
146 * Negative numbers aren't handled so -123 is taken as 123.
147 * We don't take care about separators.
149 static int
150 parse_psv_resp(unsigned char *data, int *n, int max_value)
152 unsigned char *p = data;
153 int i = 5;
155 memset(n, 0, 6 * sizeof(*n));
157 if (*p < ' ') return 0;
159 /* Find the end. */
160 while (*p >= ' ') p++;
162 /* Ignore non-numeric ending chars. */
163 while (p != data && !isdigit(*p)) p--;
164 if (p == data) return 0;
166 while (i >= 0) {
167 int x = 1;
169 /* Parse one number. */
170 while (p != data && isdigit(*p)) {
171 n[i] += (*p - '0') * x;
172 if (n[i] > max_value) return 0;
173 x *= 10;
174 p--;
176 /* Ignore non-numeric chars. */
177 while (p != data && !isdigit(*p)) p--;
178 if (p == data) return (6 - i);
179 /* Get the next one. */
180 i--;
183 return 0;
186 /* Returns 0 if there's no numeric response, -1 if error, the positive response
187 * number otherwise. */
188 static int
189 get_ftp_response(struct connection *conn, struct read_buffer *rb, int part,
190 struct sockaddr_storage *sa)
192 unsigned char *eol;
193 unsigned char *num_end;
194 int response;
195 int pos;
197 again:
198 eol = memchr(rb->data, ASCII_LF, rb->length);
199 if (!eol) return 0;
201 pos = eol - rb->data;
203 errno = 0;
204 response = strtoul(rb->data, (char **) &num_end, 10);
206 if (errno || num_end != rb->data + 3 || response < 100)
207 return -1;
209 if (sa && response == 227) { /* PASV response parsing. */
210 struct sockaddr_in *s = (struct sockaddr_in *) sa;
211 int n[6];
213 if (parse_psv_resp(num_end, (int *) &n, 255) != 6)
214 return -1;
216 memset(s, 0, sizeof(*s));
217 s->sin_family = AF_INET;
218 s->sin_addr.s_addr = htonl((n[0] << 24) + (n[1] << 16) + (n[2] << 8) + n[3]);
219 s->sin_port = htons((n[4] << 8) + n[5]);
222 #ifdef CONFIG_IPV6
223 if (sa && response == 229) { /* EPSV response parsing. */
224 /* See RFC 2428 */
225 struct sockaddr_in6 *s = (struct sockaddr_in6 *) sa;
226 int sal = sizeof(*s);
227 int n[6];
229 if (parse_psv_resp(num_end, (int *) &n, 65535) != 1) {
230 return -1;
233 memset(s, 0, sizeof(*s));
234 if (getpeername(conn->socket->fd, (struct sockaddr *) sa, &sal)) {
235 return -1;
237 s->sin6_family = AF_INET6;
238 s->sin6_port = htons(n[5]);
240 #endif
242 if (*num_end == '-') {
243 int i;
245 for (i = 0; i < rb->length - 5; i++)
246 if (rb->data[i] == ASCII_LF
247 && !memcmp(rb->data+i+1, rb->data, 3)
248 && rb->data[i+4] == ' ') {
249 for (i++; i < rb->length; i++)
250 if (rb->data[i] == ASCII_LF)
251 goto ok;
252 return 0;
254 return 0;
256 pos = i;
259 if (part != 2)
260 kill_buffer_data(rb, pos + 1);
262 if (!part && response >= 100 && response < 200) {
263 goto again;
266 return response;
270 /* Initialize or continue ftp connection. */
271 void
272 ftp_protocol_handler(struct connection *conn)
274 if (!has_keepalive_connection(conn)) {
275 make_connection(conn->socket, conn->uri, ftp_login,
276 conn->cache_mode >= CACHE_MODE_FORCE_RELOAD);
278 } else {
279 ftp_send_retr_req(conn, connection_state(S_SENT));
283 /* Send command, set connection state and free cmd string. */
284 static void
285 send_cmd(struct connection *conn, struct string *cmd, void *callback,
286 struct connection_state state)
288 request_from_socket(conn->socket, cmd->source, cmd->length, state,
289 SOCKET_RETRY_ONCLOSE, callback);
291 done_string(cmd);
294 /* Check if this auth token really belongs to this URI. */
295 static int
296 auth_user_matching_uri(struct auth_entry *auth, struct uri *uri)
298 if (!uri->userlen) /* Noone said it doesn't. */
299 return 1;
300 return !strlcasecmp(auth->user, -1, uri->user, uri->userlen);
304 /* Kill the current connection and ask for a username/password for the next
305 * try. */
306 static void
307 prompt_username_pw(struct connection *conn)
309 if (!conn->cached) {
310 conn->cached = get_cache_entry(conn->uri);
311 if (!conn->cached) {
312 abort_connection(conn, connection_state(S_OUT_OF_MEM));
313 return;
317 mem_free_set(&conn->cached->content_type, stracpy("text/html"));
318 if (!conn->cached->content_type) {
319 abort_connection(conn, connection_state(S_OUT_OF_MEM));
320 return;
323 add_auth_entry(conn->uri, "FTP Login", NULL, NULL, 0);
325 abort_connection(conn, connection_state(S_OK));
328 /* Send USER command. */
329 static void
330 ftp_login(struct socket *socket)
332 struct connection *conn = socket->conn;
333 struct string cmd;
334 struct auth_entry* auth;
336 auth = find_auth(conn->uri);
338 if (!init_string(&cmd)) {
339 abort_connection(conn, connection_state(S_OUT_OF_MEM));
340 return;
343 add_to_string(&cmd, "USER ");
344 if (conn->uri->userlen) {
345 struct uri *uri = conn->uri;
347 add_bytes_to_string(&cmd, uri->user, uri->userlen);
349 } else if (auth && auth->valid) {
350 add_to_string(&cmd, auth->user);
352 } else {
353 add_to_string(&cmd, "anonymous");
355 add_crlf_to_string(&cmd);
357 send_cmd(conn, &cmd, (void *) ftp_got_info, connection_state(S_SENT));
360 /* Parse connection response. */
361 static void
362 ftp_got_info(struct socket *socket, struct read_buffer *rb)
364 struct connection *conn = socket->conn;
365 int response = get_ftp_response(conn, rb, 0, NULL);
367 if (response == -1) {
368 abort_connection(conn, connection_state(S_FTP_ERROR));
369 return;
372 if (!response) {
373 read_from_socket(conn->socket, rb, conn->state, ftp_got_info);
374 return;
377 /* RFC959 says that possible response codes on connection are:
378 * 120 Service ready in nnn minutes.
379 * 220 Service ready for new user.
380 * 421 Service not available, closing control connection. */
382 if (response != 220) {
383 /* TODO? Retry in case of ... ?? */
384 retry_connection(conn, connection_state(S_FTP_UNAVAIL));
385 return;
388 ftp_got_user_info(socket, rb);
392 /* Parse USER response and send PASS command if needed. */
393 static void
394 ftp_got_user_info(struct socket *socket, struct read_buffer *rb)
396 struct connection *conn = socket->conn;
397 int response = get_ftp_response(conn, rb, 0, NULL);
399 if (response == -1) {
400 abort_connection(conn, connection_state(S_FTP_ERROR));
401 return;
404 if (!response) {
405 read_from_socket(conn->socket, rb, conn->state, ftp_got_user_info);
406 return;
409 /* RFC959 says that possible response codes for USER are:
410 * 230 User logged in, proceed.
411 * 331 User name okay, need password.
412 * 332 Need account for login.
413 * 421 Service not available, closing control connection.
414 * 500 Syntax error, command unrecognized.
415 * 501 Syntax error in parameters or arguments.
416 * 530 Not logged in. */
418 /* FIXME? Since ACCT command isn't implemented, login to a ftp server
419 * requiring it will fail (332). */
421 if (response == 332 || response >= 500) {
422 prompt_username_pw(conn);
423 return;
426 /* We don't require exact match here, as this is always error and some
427 * non-RFC compliant servers may return even something other than 421.
428 * --Zas */
429 if (response >= 400) {
430 abort_connection(conn, connection_state(S_FTP_UNAVAIL));
431 return;
434 if (response == 230) {
435 ftp_send_retr_req(conn, connection_state(S_GETH));
436 return;
439 ftp_pass(conn);
442 /* Send PASS command. */
443 static void
444 ftp_pass(struct connection *conn)
446 struct string cmd;
447 struct auth_entry *auth;
449 auth = find_auth(conn->uri);
451 if (!init_string(&cmd)) {
452 abort_connection(conn, connection_state(S_OUT_OF_MEM));
453 return;
456 add_to_string(&cmd, "PASS ");
457 if (conn->uri->passwordlen) {
458 struct uri *uri = conn->uri;
460 add_bytes_to_string(&cmd, uri->password, uri->passwordlen);
462 } else if (auth && auth->valid) {
463 if (!auth_user_matching_uri(auth, conn->uri)) {
464 prompt_username_pw(conn);
465 return;
467 add_to_string(&cmd, auth->password);
469 } else {
470 add_to_string(&cmd, get_opt_str("protocol.ftp.anon_passwd"));
472 add_crlf_to_string(&cmd);
474 send_cmd(conn, &cmd, (void *) ftp_pass_info, connection_state(S_LOGIN));
477 /* Parse PASS command response. */
478 static void
479 ftp_pass_info(struct socket *socket, struct read_buffer *rb)
481 struct connection *conn = socket->conn;
482 int response = get_ftp_response(conn, rb, 0, NULL);
484 if (response == -1) {
485 abort_connection(conn, connection_state(S_FTP_ERROR));
486 return;
489 if (!response) {
490 read_from_socket(conn->socket, rb, connection_state(S_LOGIN),
491 ftp_pass_info);
492 return;
495 /* RFC959 says that possible response codes for PASS are:
496 * 202 Command not implemented, superfluous at this site.
497 * 230 User logged in, proceed.
498 * 332 Need account for login.
499 * 421 Service not available, closing control connection.
500 * 500 Syntax error, command unrecognized.
501 * 501 Syntax error in parameters or arguments.
502 * 503 Bad sequence of commands.
503 * 530 Not logged in. */
505 if (response == 332 || response >= 500) {
506 /* If we didn't have a user, we tried anonymous. But it failed, so ask for a
507 * user and password */
508 prompt_username_pw(conn);
509 return;
512 if (response >= 400) {
513 abort_connection(conn, connection_state(S_FTP_UNAVAIL));
514 return;
517 ftp_send_retr_req(conn, connection_state(S_GETH));
520 /* Construct PORT command. */
521 static void
522 add_portcmd_to_string(struct string *string, unsigned char *pc)
524 /* From RFC 959: DATA PORT (PORT)
526 * The argument is a HOST-PORT specification for the data port
527 * to be used in data connection. There are defaults for both
528 * the user and server data ports, and under normal
529 * circumstances this command and its reply are not needed. If
530 * this command is used, the argument is the concatenation of a
531 * 32-bit internet host address and a 16-bit TCP port address.
532 * This address information is broken into 8-bit fields and the
533 * value of each field is transmitted as a decimal number (in
534 * character string representation). The fields are separated
535 * by commas. A port command would be:
537 * PORT h1,h2,h3,h4,p1,p2
539 * where h1 is the high order 8 bits of the internet host
540 * address. */
541 add_to_string(string, "PORT ");
542 add_long_to_string(string, pc[0]);
543 add_char_to_string(string, ',');
544 add_long_to_string(string, pc[1]);
545 add_char_to_string(string, ',');
546 add_long_to_string(string, pc[2]);
547 add_char_to_string(string, ',');
548 add_long_to_string(string, pc[3]);
549 add_char_to_string(string, ',');
550 add_long_to_string(string, pc[4]);
551 add_char_to_string(string, ',');
552 add_long_to_string(string, pc[5]);
555 #ifdef CONFIG_IPV6
556 /* Construct EPRT command. */
557 static void
558 add_eprtcmd_to_string(struct string *string, struct sockaddr_in6 *addr)
560 unsigned char addr_str[INET6_ADDRSTRLEN];
562 inet_ntop(AF_INET6, &addr->sin6_addr, addr_str, INET6_ADDRSTRLEN);
564 /* From RFC 2428: EPRT
566 * The format of EPRT is:
568 * EPRT<space><d><net-prt><d><net-addr><d><tcp-port><d>
570 * <net-prt>:
571 * AF Number Protocol
572 * --------- --------
573 * 1 Internet Protocol, Version 4 [Pos81a]
574 * 2 Internet Protocol, Version 6 [DH96] */
575 add_to_string(string, "EPRT |2|");
576 add_to_string(string, addr_str);
577 add_char_to_string(string, '|');
578 add_long_to_string(string, ntohs(addr->sin6_port));
579 add_char_to_string(string, '|');
581 #endif
583 /* Depending on options, get proper ftp data socket and command.
584 * It appends ftp command (either PASV,PORT,EPSV or EPRT) to @command
585 * string.
586 * When PORT or EPRT are used, related sockets are created.
587 * It returns 0 on error (data socket creation failure). */
588 static int
589 get_ftp_data_socket(struct connection *conn, struct string *command)
591 struct ftp_connection_info *ftp = conn->info;
593 ftp->use_pasv = get_opt_bool("protocol.ftp.use_pasv");
595 #ifdef CONFIG_IPV6
596 ftp->use_epsv = get_opt_bool("protocol.ftp.use_epsv");
598 if (conn->socket->protocol_family == EL_PF_INET6) {
599 if (ftp->use_epsv) {
600 add_to_string(command, "EPSV");
602 } else {
603 struct sockaddr_storage data_addr;
604 int data_sock;
606 memset(&data_addr, 0, sizeof(data_addr));
607 data_sock = get_pasv_socket(conn->socket, &data_addr);
608 if (data_sock < 0) return 0;
610 conn->data_socket->fd = data_sock;
611 add_eprtcmd_to_string(command,
612 (struct sockaddr_in6 *) &data_addr);
615 } else
616 #endif
618 if (ftp->use_pasv) {
619 add_to_string(command, "PASV");
621 } else {
622 struct sockaddr_in sa;
623 unsigned char pc[6];
624 int data_sock;
626 memset(pc, 0, sizeof(pc));
627 data_sock = get_pasv_socket(conn->socket,
628 (struct sockaddr_storage *) &sa);
629 if (data_sock < 0) return 0;
631 memcpy(pc, &sa.sin_addr.s_addr, 4);
632 memcpy(pc + 4, &sa.sin_port, 2);
633 conn->data_socket->fd = data_sock;
634 add_portcmd_to_string(command, pc);
638 add_crlf_to_string(command);
640 return 1;
643 /* Check if the file or directory name @s can be safely sent to the
644 * FTP server. To prevent command injection attacks, this function
645 * must reject CR LF sequences. */
646 static int
647 is_ftp_pathname_safe(const struct string *s)
649 int i;
651 /* RFC 959 says the argument of CWD and RETR is a <pathname>,
652 * which consists of <char>s, "any of the 128 ASCII characters
653 * except <CR> and <LF>". So other control characters, such
654 * as 0x00 and 0x7F, are allowed here. Bytes 0x80...0xFF
655 * should not be allowed, but if we reject them, users will
656 * probably complain. */
657 for (i = 0; i < s->length; i++) {
658 if (s->source[i] == 0x0A || s->source[i] == 0x0D)
659 return 0;
661 return 1;
664 /* Create passive socket and add appropriate announcing commands to str. Then
665 * go and retrieve appropriate object from server.
666 * Returns NULL if error. */
667 static struct ftp_connection_info *
668 add_file_cmd_to_str(struct connection *conn)
670 int ok = 0;
671 struct ftp_connection_info *ftp = NULL;
672 struct string command = NULL_STRING;
673 struct string ftp_data_command = NULL_STRING;
674 struct string pathname = NULL_STRING;
676 if (!conn->uri->data) {
677 INTERNAL("conn->uri->data empty");
678 abort_connection(conn, connection_state(S_INTERNAL));
679 goto ret;
682 /* This will be reallocated below when we know how long the
683 * command string should be. Error handling could be
684 * simplified a little by allocating this initial structure on
685 * the stack, but it's several kilobytes long so that might be
686 * risky. */
687 ftp = mem_calloc(1, sizeof(*ftp));
688 if (!ftp) {
689 abort_connection(conn, connection_state(S_OUT_OF_MEM));
690 goto ret;
693 conn->info = ftp; /* Freed when connection is destroyed. */
695 if (!init_string(&command)
696 || !init_string(&ftp_data_command)
697 || !init_string(&pathname)) {
698 abort_connection(conn, connection_state(S_OUT_OF_MEM));
699 goto ret;
702 if (!get_ftp_data_socket(conn, &ftp_data_command)) {
703 INTERNAL("Ftp data socket failure");
704 abort_connection(conn, connection_state(S_INTERNAL));
705 goto ret;
708 if (!add_uri_to_string(&pathname, conn->uri, URI_PATH)) {
709 abort_connection(conn, connection_state(S_OUT_OF_MEM));
710 goto ret;
713 decode_uri_string(&pathname);
714 if (!is_ftp_pathname_safe(&pathname)) {
715 abort_connection(conn, connection_state(S_BAD_URL));
716 goto ret;
719 if (!conn->uri->datalen
720 || conn->uri->data[conn->uri->datalen - 1] == '/') {
721 /* Commands to get directory listing. */
723 ftp->dir = 1;
724 ftp->pending_commands = 4;
726 if (!add_to_string(&command, "TYPE A") /* ASCII */
727 || !add_crlf_to_string(&command)
729 || !add_string_to_string(&command, &ftp_data_command)
731 || !add_to_string(&command, "CWD ")
732 || !add_string_to_string(&command, &pathname)
733 || !add_crlf_to_string(&command)
735 || !add_to_string(&command, "LIST")
736 || !add_crlf_to_string(&command)) {
737 abort_connection(conn, connection_state(S_OUT_OF_MEM));
738 goto ret;
741 conn->from = 0;
743 } else {
744 /* Commands to get a file. */
746 ftp->dir = 0;
747 ftp->pending_commands = 3;
749 if (!add_to_string(&command, "TYPE I") /* BINARY */
750 || !add_crlf_to_string(&command)
752 || !add_string_to_string(&command, &ftp_data_command)) {
753 abort_connection(conn, connection_state(S_OUT_OF_MEM));
754 goto ret;
757 if (conn->from || conn->progress->start > 0) {
758 const off_t offset = conn->from
759 ? conn->from
760 : conn->progress->start;
762 if (!add_to_string(&command, "REST ")
763 || !add_long_to_string(&command, offset)
764 || !add_crlf_to_string(&command)) {
765 abort_connection(conn, connection_state(S_OUT_OF_MEM));
766 goto ret;
769 ftp->rest_sent = 1;
770 ftp->pending_commands++;
773 if (!add_to_string(&command, "RETR ")
774 || !add_string_to_string(&command, &pathname)
775 || !add_crlf_to_string(&command)) {
776 abort_connection(conn, connection_state(S_OUT_OF_MEM));
777 goto ret;
781 ftp->opc = ftp->pending_commands;
783 /* 1 byte is already reserved for cmd_buffer in struct ftp_connection_info. */
784 ftp = mem_realloc(ftp, sizeof(*ftp) + command.length);
785 if (!ftp) {
786 abort_connection(conn, connection_state(S_OUT_OF_MEM));
787 goto ret;
789 conn->info = ftp; /* in case mem_realloc moved the buffer */
791 memcpy(ftp->cmd_buffer, command.source, command.length + 1);
792 ok = 1;
794 ret:
795 /* If @ok is false here, then abort_connection has already
796 * freed @ftp, which now is a dangling pointer. */
797 done_string(&pathname);
798 done_string(&ftp_data_command);
799 done_string(&command);
800 return ok ? ftp : NULL;
803 static void
804 send_it_line_by_line(struct connection *conn, struct string *cmd)
806 struct ftp_connection_info *ftp = conn->info;
807 unsigned char *nl = strchr(ftp->cmd_buffer, '\n');
809 if (!nl) {
810 add_to_string(cmd, ftp->cmd_buffer);
811 return;
814 nl++;
815 add_bytes_to_string(cmd, ftp->cmd_buffer, nl - ftp->cmd_buffer);
816 memmove(ftp->cmd_buffer, nl, strlen(nl) + 1);
819 /* Send commands to retrieve file or directory. */
820 static void
821 ftp_send_retr_req(struct connection *conn, struct connection_state state)
823 struct string cmd;
825 if (!init_string(&cmd)) {
826 abort_connection(conn, connection_state(S_OUT_OF_MEM));
827 return;
830 /* We don't save return value from add_file_cmd_to_str(), as it's saved
831 * in conn->info as well. */
832 if (!conn->info && !add_file_cmd_to_str(conn)) {
833 done_string(&cmd);
834 return;
837 /* Send it line-by-line. */
838 send_it_line_by_line(conn, &cmd);
840 send_cmd(conn, &cmd, (void *) ftp_retr_file, state);
843 /* Parse RETR response and return file size or -1 on error. */
844 static off_t
845 get_filesize_from_RETR(unsigned char *data, int data_len, int *resume)
847 off_t file_len;
848 int pos;
849 int pos_file_len = 0;
851 /* Getting file size from text response.. */
852 /* 150 Opening BINARY mode data connection for hello-1.0-1.1.diff.gz (16452 bytes). */
854 *resume = 0;
855 for (pos = 0; pos < data_len && data[pos] != ASCII_LF; pos++)
856 if (data[pos] == '(')
857 pos_file_len = pos;
859 if (!pos_file_len || pos_file_len == data_len - 1) {
860 /* Getting file size from ftp.task.gda.pl */
861 /* 150 5676.4 kbytes to download */
862 unsigned char tmp = data[data_len - 1];
863 unsigned char *kbytes;
864 char *endptr;
865 double size;
867 data[data_len - 1] = '\0';
868 kbytes = strstr(data, "kbytes");
869 data[data_len - 1] = tmp;
870 if (!kbytes) return -1;
872 for (kbytes -= 2; kbytes >= data; kbytes--) {
873 if (*kbytes == ' ') break;
875 if (*kbytes != ' ') return -1;
876 kbytes++;
877 size = strtod((const char *)kbytes, &endptr);
878 if (endptr == (char *)kbytes) return -1;
879 *resume = 1;
880 return (off_t)(size * 1024.0);
883 pos_file_len++;
884 if (!isdigit(data[pos_file_len]))
885 return -1;
887 for (pos = pos_file_len; pos < data_len; pos++)
888 if (!isdigit(data[pos]))
889 goto next;
891 return -1;
893 next:
894 for (; pos < data_len; pos++)
895 if (data[pos] != ' ')
896 break;
898 if (pos + 4 > data_len)
899 return -1;
901 if (strncasecmp(&data[pos], "byte", 4))
902 return -1;
904 errno = 0;
905 file_len = (off_t) strtol(&data[pos_file_len], NULL, 10);
906 if (errno) return -1;
908 return file_len;
911 /* Connect to the host and port specified by a passive FTP server. */
912 static int
913 ftp_data_connect(struct connection *conn, int pf, struct sockaddr_storage *sa,
914 int size_of_sockaddr)
916 int fd;
918 if (conn->data_socket->fd != -1) {
919 /* The server maliciously sent multiple 227 or 229
920 * responses. Do not leak the previous data_socket. */
921 abort_connection(conn, connection_state(S_FTP_ERROR));
922 return -1;
925 fd = socket(pf, SOCK_STREAM, 0);
926 if (fd < 0 || set_nonblocking_fd(fd) < 0) {
927 abort_connection(conn, connection_state(S_FTP_ERROR));
928 return -1;
931 set_ip_tos_throughput(fd);
933 conn->data_socket->fd = fd;
934 /* XXX: We ignore connect() errors here. */
935 connect(fd, (struct sockaddr *) sa, size_of_sockaddr);
936 return 0;
939 static void
940 ftp_retr_file(struct socket *socket, struct read_buffer *rb)
942 struct connection *conn = socket->conn;
943 struct ftp_connection_info *ftp = conn->info;
944 int response;
946 if (ftp->pending_commands > 1) {
947 struct sockaddr_storage sa;
949 response = get_ftp_response(conn, rb, 0, &sa);
951 if (response == -1) {
952 abort_connection(conn, connection_state(S_FTP_ERROR));
953 return;
956 if (!response) {
957 read_from_socket(conn->socket, rb,
958 connection_state(S_GETH),
959 ftp_retr_file);
960 return;
963 if (response == 227) {
964 if (ftp_data_connect(conn, PF_INET, &sa, sizeof(struct sockaddr_in)))
965 return;
968 #ifdef CONFIG_IPV6
969 if (response == 229) {
970 if (ftp_data_connect(conn, PF_INET6, &sa, sizeof(struct sockaddr_in6)))
971 return;
973 #endif
975 ftp->pending_commands--;
977 /* XXX: The case values are order numbers of commands. */
978 switch (ftp->opc - ftp->pending_commands) {
979 case 1: /* TYPE */
980 break;
982 case 2: /* PORT */
983 if (response >= 400) {
984 abort_connection(conn,
985 connection_state(S_FTP_PORT));
986 return;
988 break;
990 case 3: /* REST / CWD */
991 if (response >= 400) {
992 if (ftp->dir) {
993 abort_connection(conn,
994 connection_state(S_FTP_NO_FILE));
995 return;
997 conn->from = 0;
998 } else if (ftp->rest_sent) {
999 /* Following code is related to resume
1000 * feature. */
1001 if (response == 350)
1002 conn->from = conn->progress->start;
1003 /* Come on, don't be nervous ;-). */
1004 if (conn->progress->start >= 0) {
1005 /* Update to the real value
1006 * which we've got from
1007 * Content-Range. */
1008 conn->progress->seek = conn->from;
1010 conn->progress->start = conn->from;
1012 break;
1014 default:
1015 INTERNAL("WHAT???");
1018 ftp_send_retr_req(conn, connection_state(S_GETH));
1019 return;
1022 response = get_ftp_response(conn, rb, 2, NULL);
1024 if (response == -1) {
1025 abort_connection(conn, connection_state(S_FTP_ERROR));
1026 return;
1029 if (!response) {
1030 read_from_socket(conn->socket, rb, connection_state(S_GETH),
1031 ftp_retr_file);
1032 return;
1035 if (response >= 100 && response < 200) {
1036 /* We only need to parse response after RETR to
1037 * get filesize if needed. */
1038 if (!ftp->dir && conn->est_length == -1) {
1039 off_t file_len;
1040 int res;
1042 file_len = get_filesize_from_RETR(rb->data, rb->length, &res);
1043 if (file_len > 0) {
1044 /* FIXME: ..when downloads resuming
1045 * implemented.. */
1046 /* This is right for vsftpd.
1047 * Show me urls where this is wrong. --witekfl */
1048 conn->est_length = res ?
1049 file_len + conn->progress->start : file_len;
1054 if (conn->data_socket->fd == -1) {
1055 /* The passive FTP server did not send a 227 or 229
1056 * response. We check this down here, rather than
1057 * immediately after getting the response to the PASV
1058 * or EPSV command, to make sure that nothing can
1059 * close the socket between the check and the
1060 * following set_handlers call.
1062 * If we were using active FTP, then
1063 * get_ftp_data_socket would have created the
1064 * data_socket without waiting for anything from the
1065 * server. */
1066 abort_connection(conn, connection_state(S_FTP_ERROR));
1067 return;
1069 set_handlers(conn->data_socket->fd, (select_handler_T) ftp_data_accept,
1070 NULL, NULL, conn);
1072 /* read_from_socket(conn->socket, rb, ftp_got_final_response); */
1073 ftp_got_final_response(socket, rb);
1076 static void
1077 ftp_got_final_response(struct socket *socket, struct read_buffer *rb)
1079 struct connection *conn = socket->conn;
1080 struct ftp_connection_info *ftp = conn->info;
1081 int response = get_ftp_response(conn, rb, 0, NULL);
1083 if (response == -1) {
1084 abort_connection(conn, connection_state(S_FTP_ERROR));
1085 return;
1088 if (!response) {
1089 struct connection_state state = !is_in_state(conn->state, S_TRANS)
1090 ? connection_state(S_GETH) : conn->state;
1092 read_from_socket(conn->socket, rb, state, ftp_got_final_response);
1093 return;
1096 if (response >= 550 || response == 450) {
1097 /* Requested action not taken.
1098 * File unavailable (e.g., file not found, no access). */
1100 if (!conn->cached)
1101 conn->cached = get_cache_entry(conn->uri);
1103 if (!conn->cached
1104 || !redirect_cache(conn->cached, "/", 1, 0)) {
1105 abort_connection(conn, connection_state(S_OUT_OF_MEM));
1106 return;
1109 abort_connection(conn, connection_state(S_OK));
1110 return;
1113 if (response >= 400) {
1114 abort_connection(conn, connection_state(S_FTP_FILE_ERROR));
1115 return;
1118 if (ftp->conn_state == 2) {
1119 ftp_end_request(conn, connection_state(S_OK));
1120 } else {
1121 ftp->conn_state = 1;
1122 if (!is_in_state(conn->state, S_TRANS))
1123 set_connection_state(conn, connection_state(S_GETH));
1128 /** How to format an FTP directory listing in HTML. */
1129 struct ftp_dir_html_format {
1130 /** Codepage used by C library functions such as strftime().
1131 * If the FTP server sends non-ASCII bytes in file names or
1132 * such, ELinks normally passes them straight through to the
1133 * generated HTML, which will eventually be parsed using the
1134 * codepage specified in the document.codepage.assume option.
1135 * However, when ELinks itself generates strings with
1136 * strftime(), it turns non-ASCII bytes into entity references
1137 * based on libc_codepage, to make sure they become the right
1138 * characters again. */
1139 int libc_codepage;
1141 /** Nonzero if directories should be displayed in a different
1142 * color. From the document.browse.links.color_dirs option. */
1143 int colorize_dir;
1145 /** The color of directories, in "#rrggbb" format. This is
1146 * initialized and used only if colorize_dir is nonzero. */
1147 unsigned char dircolor[8];
1150 /* Display directory entry formatted in HTML. */
1151 static int
1152 display_dir_entry(struct cache_entry *cached, off_t *pos, int *tries,
1153 const struct ftp_dir_html_format *format,
1154 struct ftp_file_info *ftp_info)
1156 struct string string;
1157 unsigned char permissions[10] = "---------";
1159 if (!init_string(&string)) return -1;
1161 add_char_to_string(&string, ftp_info->type);
1163 if (ftp_info->permissions) {
1164 mode_t p = ftp_info->permissions;
1166 #define FTP_PERM(perms, buffer, flag, index, id) \
1167 if ((perms) & (flag)) (buffer)[(index)] = (id);
1169 FTP_PERM(p, permissions, S_IRUSR, 0, 'r');
1170 FTP_PERM(p, permissions, S_IWUSR, 1, 'w');
1171 FTP_PERM(p, permissions, S_IXUSR, 2, 'x');
1172 FTP_PERM(p, permissions, S_ISUID, 2, (p & S_IXUSR ? 's' : 'S'));
1174 FTP_PERM(p, permissions, S_IRGRP, 3, 'r');
1175 FTP_PERM(p, permissions, S_IWGRP, 4, 'w');
1176 FTP_PERM(p, permissions, S_IXGRP, 5, 'x');
1177 FTP_PERM(p, permissions, S_ISGID, 5, (p & S_IXGRP ? 's' : 'S'));
1179 FTP_PERM(p, permissions, S_IROTH, 6, 'r');
1180 FTP_PERM(p, permissions, S_IWOTH, 7, 'w');
1181 FTP_PERM(p, permissions, S_IXOTH, 8, 'x');
1182 FTP_PERM(p, permissions, S_ISVTX, 8, (p & 0001 ? 't' : 'T'));
1184 #undef FTP_PERM
1188 add_to_string(&string, permissions);
1189 add_char_to_string(&string, ' ');
1191 add_to_string(&string, " 1 ftp ftp ");
1193 if (ftp_info->size != FTP_SIZE_UNKNOWN) {
1194 add_format_to_string(&string, "%12" OFF_PRINT_FORMAT " ",
1195 (off_print_T) ftp_info->size);
1196 } else {
1197 add_to_string(&string, " - ");
1200 #ifdef HAVE_STRFTIME
1201 if (ftp_info->mtime > 0) {
1202 time_t current_time = time(NULL);
1203 time_t when = ftp_info->mtime;
1204 struct tm *when_tm;
1205 unsigned char *fmt;
1206 /* LC_TIME=fi_FI.UTF_8 can generate "elo___ 31 23:59"
1207 * where each _ denotes U+00A0 encoded as 0xC2 0xA0,
1208 * thus needing a 19-byte buffer. */
1209 unsigned char date[80];
1210 int wr;
1212 if (ftp_info->local_time_zone)
1213 when_tm = localtime(&when);
1214 else
1215 when_tm = gmtime(&when);
1217 if (current_time > when + 6L * 30L * 24L * 60L * 60L
1218 || current_time < when - 60L * 60L)
1219 fmt = "%b %e %Y";
1220 else
1221 fmt = "%b %e %H:%M";
1223 wr = strftime(date, sizeof(date), fmt, when_tm);
1224 add_cp_html_to_string(&string, format->libc_codepage,
1225 date, wr);
1226 } else
1227 #endif
1228 add_to_string(&string, " ");
1229 /* TODO: Above, the number of spaces might not match the width
1230 * of the string generated by strftime. It depends on the
1231 * locale. So if ELinks knows the timestamps of some FTP
1232 * files but not others, it may misalign the file names.
1233 * Potential solutions:
1234 * - Pad the strings to a compile-time fixed width.
1235 * Con: If we choose a width that suffices for all known
1236 * locales, then it will be stupidly large for most of them.
1237 * - Generate an HTML table.
1238 * Con: Bloats the HTML source.
1239 * Any solution chosen here should also be applied to the
1240 * file: protocol handler. */
1242 add_char_to_string(&string, ' ');
1244 if (ftp_info->type == FTP_FILE_DIRECTORY && format->colorize_dir) {
1245 add_to_string(&string, "<font color=\"");
1246 add_to_string(&string, format->dircolor);
1247 add_to_string(&string, "\"><b>");
1250 add_to_string(&string, "<a href=\"");
1251 encode_uri_string(&string, ftp_info->name.source, ftp_info->name.length, 0);
1252 if (ftp_info->type == FTP_FILE_DIRECTORY)
1253 add_char_to_string(&string, '/');
1254 add_to_string(&string, "\">");
1255 add_html_to_string(&string, ftp_info->name.source, ftp_info->name.length);
1256 add_to_string(&string, "</a>");
1258 if (ftp_info->type == FTP_FILE_DIRECTORY && format->colorize_dir) {
1259 add_to_string(&string, "</b></font>");
1262 if (ftp_info->symlink.length) {
1263 add_to_string(&string, " -&gt; ");
1264 add_html_to_string(&string, ftp_info->symlink.source,
1265 ftp_info->symlink.length);
1268 add_char_to_string(&string, '\n');
1270 if (add_fragment(cached, *pos, string.source, string.length)) *tries = 0;
1271 *pos += string.length;
1273 done_string(&string);
1274 return 0;
1277 /* Get the next line of input and set *@len to the length of the line.
1278 * Return the number of newline characters at the end of the line or -1
1279 * if we must wait for more input. */
1280 static int
1281 ftp_get_line(struct cache_entry *cached, unsigned char *buf, int bufl,
1282 int last, int *len)
1284 unsigned char *newline;
1286 if (!bufl) return -1;
1288 newline = memchr(buf, ASCII_LF, bufl);
1290 if (newline) {
1291 *len = newline - buf;
1292 if (*len && buf[*len - 1] == ASCII_CR) {
1293 --*len;
1294 return 2;
1295 } else {
1296 return 1;
1300 if (last || bufl >= FTP_BUF_SIZE) {
1301 *len = bufl;
1302 return 0;
1305 return -1;
1308 /** Generate HTML for a line that was received from the FTP server but
1309 * could not be parsed. The caller is supposed to have added a \<pre>
1310 * start tag. (At the time of writing, init_directory_listing() was
1311 * used for that.)
1313 * @return -1 if out of memory, or 0 if successful. */
1314 static int
1315 ftp_add_unparsed_line(struct cache_entry *cached, off_t *pos, int *tries,
1316 const unsigned char *line, int line_length)
1318 int our_ret;
1319 struct string string;
1320 int frag_ret;
1322 our_ret = -1; /* assume out of memory if returning early */
1323 if (!init_string(&string)) goto out;
1324 if (!add_html_to_string(&string, line, line_length)) goto out;
1325 if (!add_char_to_string(&string, '\n')) goto out;
1327 frag_ret = add_fragment(cached, *pos, string.source, string.length);
1328 if (frag_ret == -1) goto out;
1329 *pos += string.length;
1330 if (frag_ret == 1) *tries = 0;
1332 our_ret = 0; /* success */
1334 out:
1335 done_string(&string); /* safe even if init_string failed */
1336 return our_ret;
1339 /** List a directory in html format.
1341 * @return the number of bytes used from the beginning of @a buffer,
1342 * or -1 if out of memory. */
1343 static int
1344 ftp_process_dirlist(struct cache_entry *cached, off_t *pos,
1345 unsigned char *buffer, int buflen, int last,
1346 int *tries, const struct ftp_dir_html_format *format)
1348 int ret = 0;
1350 while (1) {
1351 struct ftp_file_info ftp_info = INIT_FTP_FILE_INFO;
1352 unsigned char *buf = buffer + ret;
1353 int bufl = buflen - ret;
1354 int line_length, eol;
1356 eol = ftp_get_line(cached, buf, bufl, last, &line_length);
1357 if (eol == -1)
1358 return ret;
1360 ret += line_length + eol;
1362 /* Process line whose end we've already found. */
1364 if (parse_ftp_file_info(&ftp_info, buf, line_length)) {
1365 int retv;
1367 if (ftp_info.name.source[0] == '.'
1368 && (ftp_info.name.length == 1
1369 || (ftp_info.name.length == 2
1370 && ftp_info.name.source[1] == '.')))
1371 continue;
1373 retv = display_dir_entry(cached, pos, tries,
1374 format, &ftp_info);
1375 if (retv < 0) {
1376 return ret;
1379 } else {
1380 int retv = ftp_add_unparsed_line(cached, pos, tries,
1381 buf, line_length);
1383 if (retv == -1) /* out of memory; propagate to caller */
1384 return retv;
1389 /* This is the initial read handler for conn->data_socket->fd,
1390 * which may be either trying to connect to a passive FTP server or
1391 * listening for a connection from an active FTP server. In active
1392 * FTP, this function then accepts the connection and replaces
1393 * conn->data_socket->fd with the resulting socket. In any case,
1394 * this function does not read any data from the FTP server, but
1395 * rather hands the socket over to got_something_from_data_connection,
1396 * which then does the reads. */
1397 static void
1398 ftp_data_accept(struct connection *conn)
1400 struct ftp_connection_info *ftp = conn->info;
1401 int newsock;
1403 /* Because this function is called only as a read handler of
1404 * conn->data_socket->fd, the socket must be valid if we get
1405 * here. */
1406 assert(conn->data_socket->fd >= 0);
1407 if_assert_failed return;
1409 set_connection_timeout(conn);
1410 clear_handlers(conn->data_socket->fd);
1412 if ((conn->socket->protocol_family != EL_PF_INET6 && ftp->use_pasv)
1413 #ifdef CONFIG_IPV6
1414 || (conn->socket->protocol_family == EL_PF_INET6 && ftp->use_epsv)
1415 #endif
1417 newsock = conn->data_socket->fd;
1418 } else {
1419 newsock = accept(conn->data_socket->fd, NULL, NULL);
1420 if (newsock < 0) {
1421 retry_connection(conn, connection_state_for_errno(errno));
1422 return;
1424 close(conn->data_socket->fd);
1427 conn->data_socket->fd = newsock;
1429 set_handlers(newsock,
1430 (select_handler_T) got_something_from_data_connection,
1431 NULL, NULL, conn);
1434 /* A read handler for conn->data_socket->fd. This function reads
1435 * data from the FTP server, reformats it to HTML if it's a directory
1436 * listing, and adds the result to the cache entry. */
1437 static void
1438 got_something_from_data_connection(struct connection *conn)
1440 struct ftp_connection_info *ftp = conn->info;
1441 struct ftp_dir_html_format format;
1442 ssize_t len;
1444 /* Because this function is called only as a read handler of
1445 * conn->data_socket->fd, the socket must be valid if we get
1446 * here. */
1447 assert(conn->data_socket->fd >= 0);
1448 if_assert_failed return;
1450 /* XXX: This probably belongs rather to connect.c ? */
1452 set_connection_timeout(conn);
1454 if (!conn->cached) conn->cached = get_cache_entry(conn->uri);
1455 if (!conn->cached) {
1456 out_of_mem:
1457 abort_connection(conn, connection_state(S_OUT_OF_MEM));
1458 return;
1461 if (ftp->dir) {
1462 format.libc_codepage = get_cp_index("System");
1464 format.colorize_dir = get_opt_bool("document.browse.links.color_dirs");
1466 if (format.colorize_dir) {
1467 color_to_string(get_opt_color("document.colors.dirs"),
1468 format.dircolor);
1472 if (ftp->dir && !conn->from) {
1473 struct string string;
1474 struct connection_state state;
1476 if (!conn->uri->data) {
1477 abort_connection(conn, connection_state(S_FTP_ERROR));
1478 return;
1481 state = init_directory_listing(&string, conn->uri);
1482 if (!is_in_state(state, S_OK)) {
1483 abort_connection(conn, state);
1484 return;
1487 add_fragment(conn->cached, conn->from, string.source, string.length);
1488 conn->from += string.length;
1490 done_string(&string);
1492 if (conn->uri->datalen) {
1493 struct ftp_file_info ftp_info = INIT_FTP_FILE_INFO_ROOT;
1495 display_dir_entry(conn->cached, &conn->from, &conn->tries,
1496 &format, &ftp_info);
1499 mem_free_set(&conn->cached->content_type, stracpy("text/html"));
1502 len = safe_read(conn->data_socket->fd, ftp->ftp_buffer + ftp->buf_pos,
1503 FTP_BUF_SIZE - ftp->buf_pos);
1504 if (len < 0) {
1505 retry_connection(conn, connection_state_for_errno(errno));
1506 return;
1509 if (len > 0) {
1510 conn->received += len;
1512 if (!ftp->dir) {
1513 if (add_fragment(conn->cached, conn->from,
1514 ftp->ftp_buffer, len) == 1)
1515 conn->tries = 0;
1516 conn->from += len;
1518 } else {
1519 int proceeded;
1521 proceeded = ftp_process_dirlist(conn->cached,
1522 &conn->from,
1523 ftp->ftp_buffer,
1524 len + ftp->buf_pos,
1525 0, &conn->tries,
1526 &format);
1528 if (proceeded == -1) goto out_of_mem;
1530 ftp->buf_pos += len - proceeded;
1532 memmove(ftp->ftp_buffer, ftp->ftp_buffer + proceeded,
1533 ftp->buf_pos);
1537 set_connection_state(conn, connection_state(S_TRANS));
1538 return;
1541 if (ftp_process_dirlist(conn->cached, &conn->from,
1542 ftp->ftp_buffer, ftp->buf_pos, 1,
1543 &conn->tries, &format) == -1)
1544 goto out_of_mem;
1546 #define ADD_CONST(str) { \
1547 add_fragment(conn->cached, conn->from, str, sizeof(str) - 1); \
1548 conn->from += (sizeof(str) - 1); }
1550 if (ftp->dir) ADD_CONST("</pre>\n<hr/>\n</body>\n</html>");
1552 close_socket(conn->data_socket);
1554 if (ftp->conn_state == 1) {
1555 ftp_end_request(conn, connection_state(S_OK));
1556 } else {
1557 ftp->conn_state = 2;
1558 set_connection_state(conn, connection_state(S_TRANS));
1562 static void
1563 ftp_end_request(struct connection *conn, struct connection_state state)
1565 if (is_in_state(state, S_OK) && conn->cached) {
1566 normalize_cache_entry(conn->cached, conn->from);
1569 set_connection_state(conn, state);
1570 add_keepalive_connection(conn, FTP_KEEPALIVE_TIMEOUT, NULL);