kernel: scheduling fix for ARM
[minix.git] / lib / libfetch / ftp.c
blobd3903b4fa1ff1f53f5df2bcb7d460d51d30ac177
1 /* $NetBSD: ftp.c,v 1.35 2010/03/21 16:48:43 joerg Exp $ */
2 /*-
3 * Copyright (c) 1998-2004 Dag-Erling Coïdan Smørgrav
4 * Copyright (c) 2008, 2009, 2010 Joerg Sonnenberger <joerg@NetBSD.org>
5 * All rights reserved.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer
12 * in this position and unchanged.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. The name of the author may not be used to endorse or promote products
17 * derived from this software without specific prior written permission
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 * $FreeBSD: ftp.c,v 1.101 2008/01/23 20:57:59 des Exp $
34 * Portions of this code were taken from or based on ftpio.c:
36 * ----------------------------------------------------------------------------
37 * "THE BEER-WARE LICENSE" (Revision 42):
38 * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you
39 * can do whatever you want with this stuff. If we meet some day, and you think
40 * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
41 * ----------------------------------------------------------------------------
43 * Major Changelog:
45 * Dag-Erling Coïdan Smørgrav
46 * 9 Jun 1998
48 * Incorporated into libfetch
50 * Jordan K. Hubbard
51 * 17 Jan 1996
53 * Turned inside out. Now returns xfers as new file ids, not as a special
54 * `state' of FTP_t
56 * $ftpioId: ftpio.c,v 1.30 1998/04/11 07:28:53 phk Exp $
60 #ifdef __linux__
61 /* Keep this down to Linux, it can create surprises else where. */
62 #define _GNU_SOURCE
63 #endif
65 #if HAVE_CONFIG_H
66 #include "config.h"
67 #endif
68 #if !defined(NETBSD) && !defined(__minix)
69 #include <nbcompat.h>
70 #endif
72 #include <sys/types.h>
73 #include <sys/socket.h>
75 #include <netinet/in.h>
76 #include <arpa/inet.h>
78 #include <ctype.h>
79 #include <errno.h>
80 #include <fcntl.h>
81 #if defined(HAVE_INTTYPES_H) || defined(NETBSD)
82 #include <inttypes.h>
83 #endif
84 #include <stdarg.h>
85 #if !defined(NETBSD) && !defined(__minix)
86 #include <nbcompat/netdb.h>
87 #include <nbcompat/stdio.h>
88 #else
89 #include <netdb.h>
90 #include <stdio.h>
91 #endif
92 #include <stdlib.h>
93 #include <string.h>
94 #include <time.h>
95 #include <unistd.h>
97 #include "common.h"
98 #include "ftperr.h"
100 #define FTP_ANONYMOUS_USER "anonymous"
102 #define FTP_CONNECTION_ALREADY_OPEN 125
103 #define FTP_OPEN_DATA_CONNECTION 150
104 #define FTP_OK 200
105 #define FTP_FILE_STATUS 213
106 #define FTP_SERVICE_READY 220
107 #define FTP_TRANSFER_COMPLETE 226
108 #define FTP_PASSIVE_MODE 227
109 #define FTP_LPASSIVE_MODE 228
110 #define FTP_EPASSIVE_MODE 229
111 #define FTP_LOGGED_IN 230
112 #define FTP_FILE_ACTION_OK 250
113 #define FTP_DIRECTORY_CREATED 257 /* multiple meanings */
114 #define FTP_FILE_CREATED 257 /* multiple meanings */
115 #define FTP_WORKING_DIRECTORY 257 /* multiple meanings */
116 #define FTP_NEED_PASSWORD 331
117 #define FTP_NEED_ACCOUNT 332
118 #define FTP_FILE_OK 350
119 #define FTP_SYNTAX_ERROR 500
120 #define FTP_PROTOCOL_ERROR 999
122 #define isftpreply(foo) \
123 (isdigit((unsigned char)foo[0]) && \
124 isdigit((unsigned char)foo[1]) && \
125 isdigit((unsigned char)foo[2]) && \
126 (foo[3] == ' ' || foo[3] == '\0'))
127 #define isftpinfo(foo) \
128 (isdigit((unsigned char)foo[0]) && \
129 isdigit((unsigned char)foo[1]) && \
130 isdigit((unsigned char)foo[2]) && \
131 foo[3] == '-')
133 #define MINBUFSIZE 4096
135 #ifdef INET6
137 * Translate IPv4 mapped IPv6 address to IPv4 address
139 static void
140 unmappedaddr(struct sockaddr_in6 *sin6, socklen_t *len)
142 struct sockaddr_in *sin4;
143 uint32_t addr;
144 int port;
146 if (sin6->sin6_family != AF_INET6 ||
147 !IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
148 return;
149 sin4 = (struct sockaddr_in *)sin6;
150 addr = *(uint32_t *)&sin6->sin6_addr.s6_addr[12];
151 port = sin6->sin6_port;
152 memset(sin4, 0, sizeof(struct sockaddr_in));
153 sin4->sin_addr.s_addr = addr;
154 sin4->sin_port = port;
155 sin4->sin_family = AF_INET;
156 *len = sizeof(struct sockaddr_in);
157 #ifdef HAVE_SA_LEN
158 sin4->sin_len = sizeof(struct sockaddr_in);
159 #endif
161 #endif
164 * Get server response
166 static int
167 ftp_chkerr(conn_t *conn)
169 if (fetch_getln(conn) == -1) {
170 fetch_syserr();
171 return (-1);
173 if (isftpinfo(conn->buf)) {
174 while (conn->buflen && !isftpreply(conn->buf)) {
175 if (fetch_getln(conn) == -1) {
176 fetch_syserr();
177 return (-1);
182 while (conn->buflen &&
183 isspace((unsigned char)conn->buf[conn->buflen - 1]))
184 conn->buflen--;
185 conn->buf[conn->buflen] = '\0';
187 if (!isftpreply(conn->buf)) {
188 ftp_seterr(FTP_PROTOCOL_ERROR);
189 return (-1);
192 conn->err = (conn->buf[0] - '0') * 100
193 + (conn->buf[1] - '0') * 10
194 + (conn->buf[2] - '0');
196 return (conn->err);
200 * Send a command and check reply
202 #ifndef __minix
203 static int
204 ftp_cmd(conn_t *conn, const char *fmt, ...)
206 va_list ap;
207 size_t len;
208 char *msg;
209 int r;
211 va_start(ap, fmt);
212 len = vasprintf(&msg, fmt, ap);
213 va_end(ap);
215 if (msg == NULL) {
216 errno = ENOMEM;
217 fetch_syserr();
218 return (-1);
221 r = fetch_write(conn, msg, len);
222 free(msg);
224 if (r == -1) {
225 fetch_syserr();
226 return (-1);
229 return (ftp_chkerr(conn));
231 #else
232 static int
233 ftp_cmd(conn_t *conn, const char *fmt, ...)
235 va_list ap;
236 size_t len;
237 char msg[MINBUFSIZE];
238 int r;
240 va_start(ap, fmt);
241 len = vsnprintf(&msg[0], MINBUFSIZE, fmt, ap);
242 va_end(ap);
244 if (len >= MINBUFSIZE) {
245 errno = ENOMEM;
246 fetch_syserr();
247 return (-1);
250 r = fetch_write(conn, msg, len);
252 if (r == -1) {
253 fetch_syserr();
254 return (-1);
257 return (ftp_chkerr(conn));
259 #endif
261 * Return a pointer to the filename part of a path
263 static const char *
264 ftp_filename(const char *file, int *len, int *type, int subdir)
266 const char *s;
268 if ((s = strrchr(file, '/')) == NULL || subdir)
269 s = file;
270 else
271 s = s + 1;
272 *len = strlen(s);
273 if (*len > 7 && strncmp(s + *len - 7, ";type=", 6) == 0) {
274 *type = s[*len - 1];
275 *len -= 7;
276 } else {
277 *type = '\0';
279 return (s);
283 * Get current working directory from the reply to a CWD, PWD or CDUP
284 * command.
286 static int
287 ftp_pwd(conn_t *conn, char **pwd)
289 char *src, *dst, *end;
290 int q;
292 if (conn->err != FTP_WORKING_DIRECTORY &&
293 conn->err != FTP_FILE_ACTION_OK)
294 return (FTP_PROTOCOL_ERROR);
295 end = conn->buf + conn->buflen;
296 src = conn->buf + 4;
297 if (src >= end || *src++ != '"')
298 return (FTP_PROTOCOL_ERROR);
299 *pwd = malloc(end - src + 1);
300 if (*pwd == NULL)
301 return (FTP_PROTOCOL_ERROR);
302 for (q = 0, dst = *pwd; src < end; ++src) {
303 if (!q && *src == '"')
304 q = 1;
305 else if (q && *src != '"')
306 break;
307 else if (q)
308 *dst++ = '"', q = 0;
309 else
310 *dst++ = *src;
312 *dst = '\0';
313 if (**pwd != '/') {
314 free(*pwd);
315 *pwd = NULL;
316 return (FTP_PROTOCOL_ERROR);
318 return (FTP_OK);
322 * Change working directory to the directory that contains the specified
323 * file.
325 static int
326 ftp_cwd(conn_t *conn, const char *path, int subdir)
328 const char *beg, *end;
329 char *pwd, *dst;
330 int e, i, len;
332 if (*path != '/') {
333 ftp_seterr(501);
334 return (-1);
336 ++path;
338 /* Simple case: still in the home directory and no directory change. */
339 if (conn->ftp_home == NULL && strchr(path, '/') == NULL &&
340 (!subdir || *path == '\0'))
341 return 0;
343 if ((e = ftp_cmd(conn, "PWD\r\n")) != FTP_WORKING_DIRECTORY ||
344 (e = ftp_pwd(conn, &pwd)) != FTP_OK) {
345 ftp_seterr(e);
346 return (-1);
348 if (conn->ftp_home == NULL && (conn->ftp_home = strdup(pwd)) == NULL) {
349 fetch_syserr();
350 free(pwd);
351 return (-1);
353 if (*path == '/') {
354 while (path[1] == '/')
355 ++path;
356 dst = strdup(path);
357 } else if (strcmp(conn->ftp_home, "/") == 0) {
358 dst = strdup(path - 1);
359 } else {
360 #ifndef __minix
361 asprintf(&dst, "%s/%s", conn->ftp_home, path);
362 #else
363 if((dst = malloc(sizeof(char)*MINBUFSIZE)) != NULL) {
364 len = snprintf(dst, MINBUFSIZE, "%s/%s", conn->ftp_home, path);
366 if(len >= MINBUFSIZE) {
367 free(dst);
368 dst = NULL;
371 #endif
373 if (dst == NULL) {
374 fetch_syserr();
375 free(pwd);
376 return (-1);
379 if (subdir)
380 end = dst + strlen(dst);
381 else
382 end = strrchr(dst, '/');
384 for (;;) {
385 len = strlen(pwd);
387 /* Look for a common prefix between PWD and dir to fetch. */
388 for (i = 0; i <= len && i <= end - dst; ++i)
389 if (pwd[i] != dst[i])
390 break;
391 /* Keep going up a dir until we have a matching prefix. */
392 if (strcmp(pwd, "/") == 0)
393 break;
394 if (pwd[i] == '\0' && (dst[i - 1] == '/' || dst[i] == '/'))
395 break;
396 free(pwd);
397 if ((e = ftp_cmd(conn, "CDUP\r\n")) != FTP_FILE_ACTION_OK ||
398 (e = ftp_cmd(conn, "PWD\r\n")) != FTP_WORKING_DIRECTORY ||
399 (e = ftp_pwd(conn, &pwd)) != FTP_OK) {
400 ftp_seterr(e);
401 free(dst);
402 return (-1);
405 free(pwd);
407 #ifdef FTP_COMBINE_CWDS
408 /* Skip leading slashes, even "////". */
409 for (beg = dst + i; beg < end && *beg == '/'; ++beg, ++i)
410 /* nothing */ ;
412 /* If there is no trailing dir, we're already there. */
413 if (beg >= end) {
414 free(dst);
415 return (0);
418 /* Change to the directory all in one chunk (e.g., foo/bar/baz). */
419 e = ftp_cmd(conn, "CWD %.*s\r\n", (int)(end - beg), beg);
420 if (e == FTP_FILE_ACTION_OK) {
421 free(dst);
422 return (0);
424 #endif /* FTP_COMBINE_CWDS */
426 /* That didn't work so go back to legacy behavior (multiple CWDs). */
427 for (beg = dst + i; beg < end; beg = dst + i + 1) {
428 while (*beg == '/')
429 ++beg, ++i;
430 for (++i; dst + i < end && dst[i] != '/'; ++i)
431 /* nothing */ ;
432 e = ftp_cmd(conn, "CWD %.*s\r\n", dst + i - beg, beg);
433 if (e != FTP_FILE_ACTION_OK) {
434 free(dst);
435 ftp_seterr(e);
436 return (-1);
439 free(dst);
440 return (0);
444 * Set transfer mode and data type
446 static int
447 ftp_mode_type(conn_t *conn, int mode, int type)
449 int e;
451 switch (mode) {
452 case 0:
453 case 's':
454 mode = 'S';
455 case 'S':
456 break;
457 default:
458 return (FTP_PROTOCOL_ERROR);
460 if ((e = ftp_cmd(conn, "MODE %c\r\n", mode)) != FTP_OK) {
461 if (mode == 'S') {
463 * Stream mode is supposed to be the default - so
464 * much so that some servers not only do not
465 * support any other mode, but do not support the
466 * MODE command at all.
468 * If "MODE S" fails, it is unlikely that we
469 * previously succeeded in setting a different
470 * mode. Therefore, we simply hope that the
471 * server is already in the correct mode, and
472 * silently ignore the failure.
474 } else {
475 return (e);
479 switch (type) {
480 case 0:
481 case 'i':
482 type = 'I';
483 case 'I':
484 break;
485 case 'a':
486 type = 'A';
487 case 'A':
488 break;
489 case 'd':
490 type = 'D';
491 case 'D':
492 /* can't handle yet */
493 default:
494 return (FTP_PROTOCOL_ERROR);
496 if ((e = ftp_cmd(conn, "TYPE %c\r\n", type)) != FTP_OK)
497 return (e);
499 return (FTP_OK);
503 * Request and parse file stats
505 static int
506 ftp_stat(conn_t *conn, const char *file, struct url_stat *us)
508 char *ln;
509 const char *filename;
510 int filenamelen, type;
511 struct tm tm;
512 time_t t;
513 int e;
515 us->size = -1;
516 us->atime = us->mtime = 0;
518 filename = ftp_filename(file, &filenamelen, &type, 0);
520 if ((e = ftp_mode_type(conn, 0, type)) != FTP_OK) {
521 ftp_seterr(e);
522 return (-1);
525 e = ftp_cmd(conn, "SIZE %.*s\r\n", filenamelen, filename);
526 if (e != FTP_FILE_STATUS) {
527 ftp_seterr(e);
528 return (-1);
530 for (ln = conn->buf + 4; *ln && isspace((unsigned char)*ln); ln++)
531 /* nothing */ ;
532 for (us->size = 0; *ln && isdigit((unsigned char)*ln); ln++)
533 us->size = us->size * 10 + *ln - '0';
534 if (*ln && !isspace((unsigned char)*ln)) {
535 ftp_seterr(FTP_PROTOCOL_ERROR);
536 us->size = -1;
537 return (-1);
539 if (us->size == 0)
540 us->size = -1;
542 e = ftp_cmd(conn, "MDTM %.*s\r\n", filenamelen, filename);
543 if (e != FTP_FILE_STATUS) {
544 ftp_seterr(e);
545 return (-1);
547 for (ln = conn->buf + 4; *ln && isspace((unsigned char)*ln); ln++)
548 /* nothing */ ;
549 switch (strspn(ln, "0123456789")) {
550 case 14:
551 break;
552 case 15:
553 ln++;
554 ln[0] = '2';
555 ln[1] = '0';
556 break;
557 default:
558 ftp_seterr(FTP_PROTOCOL_ERROR);
559 return (-1);
561 if (sscanf(ln, "%04d%02d%02d%02d%02d%02d",
562 &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
563 &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
564 ftp_seterr(FTP_PROTOCOL_ERROR);
565 return (-1);
567 tm.tm_mon--;
568 tm.tm_year -= 1900;
569 tm.tm_isdst = -1;
570 t = timegm(&tm);
571 if (t == (time_t)-1)
572 t = time(NULL);
573 us->mtime = t;
574 us->atime = t;
576 return (0);
580 * I/O functions for FTP
582 struct ftpio {
583 conn_t *cconn; /* Control connection */
584 conn_t *dconn; /* Data connection */
585 int dir; /* Direction */
586 int eof; /* EOF reached */
587 int err; /* Error code */
590 static ssize_t ftp_readfn(void *, void *, size_t);
591 static ssize_t ftp_writefn(void *, const void *, size_t);
592 static void ftp_closefn(void *);
594 static ssize_t
595 ftp_readfn(void *v, void *buf, size_t len)
597 struct ftpio *io;
598 int r;
600 io = (struct ftpio *)v;
601 if (io == NULL) {
602 errno = EBADF;
603 return (-1);
605 if (io->cconn == NULL || io->dconn == NULL || io->dir == O_WRONLY) {
606 errno = EBADF;
607 return (-1);
609 if (io->err) {
610 errno = io->err;
611 return (-1);
613 if (io->eof)
614 return (0);
615 r = fetch_read(io->dconn, buf, len);
616 if (r > 0)
617 return (r);
618 if (r == 0) {
619 io->eof = 1;
620 return (0);
622 if (errno != EINTR)
623 io->err = errno;
624 return (-1);
627 static ssize_t
628 ftp_writefn(void *v, const void *buf, size_t len)
630 struct ftpio *io;
631 int w;
633 io = (struct ftpio *)v;
634 if (io == NULL) {
635 errno = EBADF;
636 return (-1);
638 if (io->cconn == NULL || io->dconn == NULL || io->dir == O_RDONLY) {
639 errno = EBADF;
640 return (-1);
642 if (io->err) {
643 errno = io->err;
644 return (-1);
646 w = fetch_write(io->dconn, buf, len);
647 if (w >= 0)
648 return (w);
649 if (errno != EINTR)
650 io->err = errno;
651 return (-1);
654 static int
655 ftp_disconnect(conn_t *conn)
657 ftp_cmd(conn, "QUIT\r\n");
658 return fetch_close(conn);
661 static void
662 ftp_closefn(void *v)
664 struct ftpio *io;
665 int r;
667 io = (struct ftpio *)v;
668 if (io == NULL) {
669 errno = EBADF;
670 return;
672 if (io->dir == -1)
673 return;
674 if (io->cconn == NULL || io->dconn == NULL) {
675 errno = EBADF;
676 return;
678 fetch_close(io->dconn);
679 io->dconn = NULL;
680 io->dir = -1;
681 r = ftp_chkerr(io->cconn);
682 fetch_cache_put(io->cconn, ftp_disconnect);
683 free(io);
684 return;
687 static fetchIO *
688 ftp_setup(conn_t *cconn, conn_t *dconn, int mode)
690 struct ftpio *io;
691 fetchIO *f;
693 if (cconn == NULL || dconn == NULL)
694 return (NULL);
695 if ((io = malloc(sizeof(*io))) == NULL)
696 return (NULL);
697 io->cconn = cconn;
698 io->dconn = dconn;
699 io->dir = mode;
700 io->eof = io->err = 0;
701 f = fetchIO_unopen(io, ftp_readfn, ftp_writefn, ftp_closefn);
702 if (f == NULL)
703 free(io);
704 return (f);
708 * Transfer file
710 static fetchIO *
711 ftp_transfer(conn_t *conn, const char *oper, const char *file, const char *op_arg,
712 int mode, off_t offset, const char *flags)
714 union anonymous {
715 struct sockaddr_storage ss;
716 struct sockaddr sa;
717 struct sockaddr_in6 sin6;
718 struct sockaddr_in sin4;
719 } u;
720 const char *bindaddr;
721 const char *filename;
722 int filenamelen, type;
723 int low, pasv, verbose;
724 int e, sd = -1;
725 socklen_t l;
726 char *s;
727 fetchIO *df;
729 /* check flags */
730 low = CHECK_FLAG('l');
731 pasv = !CHECK_FLAG('a');
732 verbose = CHECK_FLAG('v');
734 /* passive mode */
735 if (!pasv)
736 pasv = ((s = getenv("FTP_PASSIVE_MODE")) != NULL &&
737 strncasecmp(s, "no", 2) != 0);
739 /* isolate filename */
740 filename = ftp_filename(file, &filenamelen, &type, op_arg != NULL);
742 /* set transfer mode and data type */
743 if ((e = ftp_mode_type(conn, 0, type)) != FTP_OK)
744 goto ouch;
746 /* find our own address, bind, and listen */
747 l = sizeof(u.ss);
748 if (getsockname(conn->sd, &u.sa, &l) == -1)
749 goto sysouch;
750 #ifdef INET6
751 if (u.ss.ss_family == AF_INET6)
752 unmappedaddr(&u.sin6, &l);
753 #endif
755 retry_mode:
757 /* open data socket */
758 if ((sd = socket(u.ss.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
759 fetch_syserr();
760 return (NULL);
763 if (pasv) {
764 unsigned char addr[64];
765 char *ln, *p;
766 unsigned int i;
767 int port;
769 /* send PASV command */
770 if (verbose)
771 fetch_info("setting passive mode");
772 switch (u.ss.ss_family) {
773 case AF_INET:
774 if ((e = ftp_cmd(conn, "PASV\r\n")) != FTP_PASSIVE_MODE)
775 goto ouch;
776 break;
777 #ifdef INET6
778 case AF_INET6:
779 if ((e = ftp_cmd(conn, "EPSV\r\n")) != FTP_EPASSIVE_MODE) {
780 if (e == -1)
781 goto ouch;
782 if ((e = ftp_cmd(conn, "LPSV\r\n")) !=
783 FTP_LPASSIVE_MODE)
784 goto ouch;
786 break;
787 #endif
788 default:
789 e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
790 goto ouch;
794 * Find address and port number. The reply to the PASV command
795 * is IMHO the one and only weak point in the FTP protocol.
797 ln = conn->buf;
798 switch (e) {
799 case FTP_PASSIVE_MODE:
800 case FTP_LPASSIVE_MODE:
801 for (p = ln + 3; *p && !isdigit((unsigned char)*p); p++)
802 /* nothing */ ;
803 if (!*p) {
804 e = FTP_PROTOCOL_ERROR;
805 goto ouch;
807 l = (e == FTP_PASSIVE_MODE ? 6 : 21);
808 for (i = 0; *p && i < l; i++, p++)
809 addr[i] = strtol(p, &p, 10);
810 if (i < l) {
811 e = FTP_PROTOCOL_ERROR;
812 goto ouch;
814 break;
815 case FTP_EPASSIVE_MODE:
816 for (p = ln + 3; *p && *p != '('; p++)
817 /* nothing */ ;
818 if (!*p) {
819 e = FTP_PROTOCOL_ERROR;
820 goto ouch;
822 ++p;
823 if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2],
824 &port, &addr[3]) != 5 ||
825 addr[0] != addr[1] ||
826 addr[0] != addr[2] || addr[0] != addr[3]) {
827 e = FTP_PROTOCOL_ERROR;
828 goto ouch;
830 break;
831 case FTP_SYNTAX_ERROR:
832 if (verbose)
833 fetch_info("passive mode failed");
834 /* Close socket and retry with passive mode. */
835 pasv = 0;
836 close(sd);
837 sd = -1;
838 goto retry_mode;
841 /* seek to required offset */
842 if (offset)
843 if (ftp_cmd(conn, "REST %lu\r\n", (unsigned long)offset) != FTP_FILE_OK)
844 goto sysouch;
846 /* construct sockaddr for data socket */
847 l = sizeof(u.ss);
848 if (getpeername(conn->sd, &u.sa, &l) == -1)
849 goto sysouch;
850 #ifdef INET6
851 if (u.ss.ss_family == AF_INET6)
852 unmappedaddr(&u.sin6, &l);
853 #endif
854 switch (u.ss.ss_family) {
855 #ifdef INET6
856 case AF_INET6:
857 if (e == FTP_EPASSIVE_MODE)
858 u.sin6.sin6_port = htons(port);
859 else {
860 memcpy(&u.sin6.sin6_addr, addr + 2, 16);
861 memcpy(&u.sin6.sin6_port, addr + 19, 2);
863 break;
864 #endif
865 case AF_INET:
866 if (e == FTP_EPASSIVE_MODE)
867 u.sin4.sin_port = htons(port);
868 else {
869 memcpy(&u.sin4.sin_addr, addr, 4);
870 memcpy(&u.sin4.sin_port, addr + 4, 2);
872 break;
873 default:
874 e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
875 break;
878 /* connect to data port */
879 if (verbose)
880 fetch_info("opening data connection");
881 bindaddr = getenv("FETCH_BIND_ADDRESS");
882 if (bindaddr != NULL && *bindaddr != '\0' &&
883 fetch_bind(sd, u.ss.ss_family, bindaddr) != 0)
884 goto sysouch;
885 if (connect(sd, &u.sa, l) == -1)
886 goto sysouch;
888 /* make the server initiate the transfer */
889 if (verbose)
890 fetch_info("initiating transfer");
891 if (op_arg)
892 e = ftp_cmd(conn, "%s%s%s\r\n", oper, *op_arg ? " " : "", op_arg);
893 else
894 e = ftp_cmd(conn, "%s %.*s\r\n", oper,
895 filenamelen, filename);
896 if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION)
897 goto ouch;
899 } else {
900 uint32_t a;
901 uint16_t p;
902 #if defined(IPV6_PORTRANGE) || defined(IP_PORTRANGE)
903 int arg;
904 #endif
905 int d;
906 #ifdef INET6
907 char *ap;
908 char hname[INET6_ADDRSTRLEN];
909 #endif
911 switch (u.ss.ss_family) {
912 #ifdef INET6
913 case AF_INET6:
914 u.sin6.sin6_port = 0;
915 #ifdef IPV6_PORTRANGE
916 arg = low ? IPV6_PORTRANGE_DEFAULT : IPV6_PORTRANGE_HIGH;
917 if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE,
918 (char *)&arg, sizeof(arg)) == -1)
919 goto sysouch;
920 #endif
921 break;
922 #endif
923 case AF_INET:
924 u.sin4.sin_port = 0;
925 #ifdef IP_PORTRANGE
926 arg = low ? IP_PORTRANGE_DEFAULT : IP_PORTRANGE_HIGH;
927 if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE,
928 (char *)&arg, sizeof(arg)) == -1)
929 goto sysouch;
930 #endif
931 break;
933 if (verbose)
934 fetch_info("binding data socket");
935 if (bind(sd, &u.sa, l) == -1)
936 goto sysouch;
937 if (listen(sd, 1) == -1)
938 goto sysouch;
940 /* find what port we're on and tell the server */
941 if (getsockname(sd, &u.sa, &l) == -1)
942 goto sysouch;
943 switch (u.ss.ss_family) {
944 case AF_INET:
945 a = ntohl(u.sin4.sin_addr.s_addr);
946 p = ntohs(u.sin4.sin_port);
947 e = ftp_cmd(conn, "PORT %d,%d,%d,%d,%d,%d\r\n",
948 (a >> 24) & 0xff, (a >> 16) & 0xff,
949 (a >> 8) & 0xff, a & 0xff,
950 (p >> 8) & 0xff, p & 0xff);
951 break;
952 #ifdef INET6
953 case AF_INET6:
954 #define UC(b) (((int)b)&0xff)
955 e = -1;
956 u.sin6.sin6_scope_id = 0;
957 if (getnameinfo(&u.sa, l,
958 hname, sizeof(hname),
959 NULL, 0, NI_NUMERICHOST) == 0) {
960 e = ftp_cmd(conn, "EPRT |%d|%s|%d|\r\n", 2, hname,
961 htons(u.sin6.sin6_port));
962 if (e == -1)
963 goto ouch;
965 if (e != FTP_OK) {
966 ap = (char *)&u.sin6.sin6_addr;
967 e = ftp_cmd(conn,
968 "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\r\n",
969 6, 16,
970 UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]),
971 UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]),
972 UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]),
973 UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]),
975 (ntohs(u.sin6.sin6_port) >> 8) & 0xff,
976 ntohs(u.sin6.sin6_port) & 0xff);
978 break;
979 #endif
980 default:
981 e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
982 goto ouch;
984 if (e != FTP_OK)
985 goto ouch;
987 #ifndef __minix
988 /* seek to required offset */
989 if (offset)
990 if (ftp_cmd(conn, "REST %llu\r\n", (unsigned long long)offset) != FTP_FILE_OK)
991 goto sysouch;
992 #else
993 /* seek to required offset */
994 if (offset)
995 if (ftp_cmd(conn, "REST %lu\r\n", (unsigned long)offset) != FTP_FILE_OK)
996 goto sysouch;
997 #endif
999 /* make the server initiate the transfer */
1000 if (verbose)
1001 fetch_info("initiating transfer");
1002 if (op_arg)
1003 e = ftp_cmd(conn, "%s%s%s\r\n", oper, *op_arg ? " " : "", op_arg);
1004 else
1005 e = ftp_cmd(conn, "%s %.*s\r\n", oper,
1006 filenamelen, filename);
1007 if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION)
1008 goto ouch;
1010 /* accept the incoming connection and go to town */
1011 if ((d = accept(sd, NULL, NULL)) == -1)
1012 goto sysouch;
1013 close(sd);
1014 sd = d;
1017 if ((df = ftp_setup(conn, fetch_reopen(sd), mode)) == NULL)
1018 goto sysouch;
1019 return (df);
1021 sysouch:
1022 fetch_syserr();
1023 if (sd >= 0)
1024 close(sd);
1025 return (NULL);
1027 ouch:
1028 if (e != -1)
1029 ftp_seterr(e);
1030 if (sd >= 0)
1031 close(sd);
1032 return (NULL);
1036 * Authenticate
1038 static int
1039 ftp_authenticate(conn_t *conn, struct url *url, struct url *purl)
1041 const char *user, *pwd, *login_name;
1042 char pbuf[URL_USERLEN + 1 + URL_HOSTLEN + 1];
1043 int e, len;
1045 /* XXX FTP_AUTH, and maybe .netrc */
1047 /* send user name and password */
1048 if (url->user[0] == '\0')
1049 fetch_netrc_auth(url);
1050 user = url->user;
1051 if (*user == '\0')
1052 user = getenv("FTP_LOGIN");
1053 if (user == NULL || *user == '\0')
1054 user = FTP_ANONYMOUS_USER;
1055 if (purl && url->port == fetch_default_port(url->scheme))
1056 e = ftp_cmd(conn, "USER %s@%s\r\n", user, url->host);
1057 else if (purl)
1058 e = ftp_cmd(conn, "USER %s@%s@%d\r\n", user, url->host, url->port);
1059 else
1060 e = ftp_cmd(conn, "USER %s\r\n", user);
1062 /* did the server request a password? */
1063 if (e == FTP_NEED_PASSWORD) {
1064 pwd = url->pwd;
1065 if (*pwd == '\0')
1066 pwd = getenv("FTP_PASSWORD");
1067 if (pwd == NULL || *pwd == '\0') {
1068 if ((login_name = getlogin()) == 0)
1069 login_name = FTP_ANONYMOUS_USER;
1070 if ((len = snprintf(pbuf, URL_USERLEN + 2, "%s@", login_name)) < 0)
1071 len = 0;
1072 else if (len > URL_USERLEN + 1)
1073 len = URL_USERLEN + 1;
1074 gethostname(pbuf + len, sizeof(pbuf) - len);
1075 /* MAXHOSTNAMELEN can differ from URL_HOSTLEN + 1 */
1076 pbuf[sizeof(pbuf) - 1] = '\0';
1077 pwd = pbuf;
1079 e = ftp_cmd(conn, "PASS %s\r\n", pwd);
1082 return (e);
1086 * Log on to FTP server
1088 static conn_t *
1089 ftp_connect(struct url *url, struct url *purl, const char *flags)
1091 conn_t *conn;
1092 int e, direct, verbose;
1093 #ifdef INET6
1094 int af = AF_UNSPEC;
1095 #else
1096 int af = AF_INET;
1097 #endif
1099 direct = CHECK_FLAG('d');
1100 verbose = CHECK_FLAG('v');
1101 if (CHECK_FLAG('4'))
1102 af = AF_INET;
1103 #ifdef INET6
1104 else if (CHECK_FLAG('6'))
1105 af = AF_INET6;
1106 #endif
1107 if (direct)
1108 purl = NULL;
1110 /* check for proxy */
1111 if (purl) {
1112 /* XXX proxy authentication! */
1113 /* XXX connetion caching */
1114 if (!purl->port)
1115 purl->port = fetch_default_port(purl->scheme);
1117 conn = fetch_connect(purl, af, verbose);
1118 } else {
1119 /* no proxy, go straight to target */
1120 if (!url->port)
1121 url->port = fetch_default_port(url->scheme);
1123 while ((conn = fetch_cache_get(url, af)) != NULL) {
1124 e = ftp_cmd(conn, "NOOP\r\n");
1125 if (e == FTP_OK)
1126 return conn;
1127 fetch_close(conn);
1129 conn = fetch_connect(url, af, verbose);
1130 purl = NULL;
1133 /* check connection */
1134 if (conn == NULL)
1135 /* fetch_connect() has already set an error code */
1136 return (NULL);
1138 /* expect welcome message */
1139 if ((e = ftp_chkerr(conn)) != FTP_SERVICE_READY)
1140 goto fouch;
1142 /* authenticate */
1143 if ((e = ftp_authenticate(conn, url, purl)) != FTP_LOGGED_IN)
1144 goto fouch;
1146 /* TODO: Request extended features supported, if any (RFC 3659). */
1148 /* done */
1149 return (conn);
1151 fouch:
1152 if (e != -1)
1153 ftp_seterr(e);
1154 fetch_close(conn);
1155 return (NULL);
1159 * Check the proxy settings
1161 static struct url *
1162 ftp_get_proxy(struct url * url, const char *flags)
1164 struct url *purl;
1165 char *p;
1167 if (flags != NULL && strchr(flags, 'd') != NULL)
1168 return (NULL);
1169 if (fetch_no_proxy_match(url->host))
1170 return (NULL);
1171 if (((p = getenv("FTP_PROXY")) || (p = getenv("ftp_proxy")) ||
1172 (p = getenv("HTTP_PROXY")) || (p = getenv("http_proxy"))) &&
1173 *p && (purl = fetchParseURL(p)) != NULL) {
1174 if (!*purl->scheme) {
1175 if (getenv("FTP_PROXY") || getenv("ftp_proxy"))
1176 strcpy(purl->scheme, SCHEME_FTP);
1177 else
1178 strcpy(purl->scheme, SCHEME_HTTP);
1180 if (!purl->port)
1181 purl->port = fetch_default_proxy_port(purl->scheme);
1182 if (strcasecmp(purl->scheme, SCHEME_FTP) == 0 ||
1183 strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
1184 return (purl);
1185 fetchFreeURL(purl);
1187 return (NULL);
1191 * Process an FTP request
1193 fetchIO *
1194 ftp_request(struct url *url, const char *op, const char *op_arg,
1195 struct url_stat *us, struct url *purl, const char *flags)
1197 fetchIO *f;
1198 char *path;
1199 conn_t *conn;
1200 int if_modified_since, oflag;
1201 struct url_stat local_us;
1203 /* check if we should use HTTP instead */
1204 if (purl && strcasecmp(purl->scheme, SCHEME_HTTP) == 0) {
1205 if (strcmp(op, "STAT") == 0)
1206 return (http_request(url, "HEAD", us, purl, flags));
1207 else if (strcmp(op, "RETR") == 0)
1208 return (http_request(url, "GET", us, purl, flags));
1210 * Our HTTP code doesn't support PUT requests yet, so try
1211 * a direct connection.
1215 /* connect to server */
1216 conn = ftp_connect(url, purl, flags);
1217 if (purl)
1218 fetchFreeURL(purl);
1219 if (conn == NULL)
1220 return (NULL);
1222 if ((path = fetchUnquotePath(url)) == NULL) {
1223 fetch_syserr();
1224 return NULL;
1227 /* change directory */
1228 if (ftp_cwd(conn, path, op_arg != NULL) == -1) {
1229 free(path);
1230 return (NULL);
1233 if_modified_since = CHECK_FLAG('i');
1234 if (if_modified_since && us == NULL)
1235 us = &local_us;
1237 /* stat file */
1238 if (us && ftp_stat(conn, path, us) == -1
1239 && fetchLastErrCode != FETCH_PROTO
1240 && fetchLastErrCode != FETCH_UNAVAIL) {
1241 free(path);
1242 return (NULL);
1245 if (if_modified_since && url->last_modified > 0 &&
1246 url->last_modified >= us->mtime) {
1247 free(path);
1248 fetchLastErrCode = FETCH_UNCHANGED;
1249 snprintf(fetchLastErrString, MAXERRSTRING, "Unchanged");
1250 return NULL;
1253 /* just a stat */
1254 if (strcmp(op, "STAT") == 0) {
1255 free(path);
1256 return fetchIO_unopen(NULL, NULL, NULL, NULL);
1258 if (strcmp(op, "STOR") == 0 || strcmp(op, "APPE") == 0)
1259 oflag = O_WRONLY;
1260 else
1261 oflag = O_RDONLY;
1263 /* initiate the transfer */
1264 f = (ftp_transfer(conn, op, path, op_arg, oflag, url->offset, flags));
1265 free(path);
1266 return f;
1270 * Get and stat file
1272 fetchIO *
1273 fetchXGetFTP(struct url *url, struct url_stat *us, const char *flags)
1275 return (ftp_request(url, "RETR", NULL, us, ftp_get_proxy(url, flags), flags));
1279 * Get file
1281 fetchIO *
1282 fetchGetFTP(struct url *url, const char *flags)
1284 return (fetchXGetFTP(url, NULL, flags));
1288 * Put file
1290 fetchIO *
1291 fetchPutFTP(struct url *url, const char *flags)
1293 return (ftp_request(url, CHECK_FLAG('a') ? "APPE" : "STOR", NULL, NULL,
1294 ftp_get_proxy(url, flags), flags));
1298 * Get file stats
1301 fetchStatFTP(struct url *url, struct url_stat *us, const char *flags)
1303 fetchIO *f;
1305 f = ftp_request(url, "STAT", NULL, us, ftp_get_proxy(url, flags), flags);
1306 if (f == NULL)
1307 return (-1);
1308 fetchIO_close(f);
1309 return (0);
1313 * List a directory
1316 fetchListFTP(struct url_list *ue, struct url *url, const char *pattern, const char *flags)
1318 fetchIO *f;
1319 char buf[2 * PATH_MAX], *eol, *eos;
1320 ssize_t len;
1321 size_t cur_off;
1322 int ret;
1324 /* XXX What about proxies? */
1325 if (pattern == NULL || strcmp(pattern, "*") == 0)
1326 pattern = "";
1327 f = ftp_request(url, "NLST", pattern, NULL, ftp_get_proxy(url, flags), flags);
1328 if (f == NULL)
1329 return -1;
1331 cur_off = 0;
1332 ret = 0;
1334 while ((len = fetchIO_read(f, buf + cur_off, sizeof(buf) - cur_off)) > 0) {
1335 cur_off += len;
1336 while ((eol = memchr(buf, '\n', cur_off)) != NULL) {
1337 if (len == eol - buf)
1338 break;
1339 if (eol != buf) {
1340 if (eol[-1] == '\r')
1341 eos = eol - 1;
1342 else
1343 eos = eol;
1344 *eos = '\0';
1345 ret = fetch_add_entry(ue, url, buf, 0);
1346 if (ret)
1347 break;
1348 cur_off -= eol - buf + 1;
1349 memmove(buf, eol + 1, cur_off);
1352 if (ret)
1353 break;
1355 if (cur_off != 0 || len < 0) {
1356 /* Not RFC conform, bail out. */
1357 fetchIO_close(f);
1358 return -1;
1360 fetchIO_close(f);
1361 return ret;