Add the DragonFly cvs id and perform general cleanups on cvs/rcs/sccs ids. Most
[dragonfly.git] / lib / libfetch / ftp.c
1 /*-
2  * Copyright (c) 1998 Dag-Erling Coïdan Smørgrav
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer
10  *    in this position and unchanged.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. The name of the author may not be used to endorse or promote products
15  *    derived from this software without specific prior written permission
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  *
28  * $FreeBSD: src/lib/libfetch/ftp.c,v 1.16.2.31 2003/06/06 06:45:25 des Exp $
29  * $DragonFly: src/lib/libfetch/ftp.c,v 1.2 2003/06/17 04:26:49 dillon Exp $
30  */
31
32 /*
33  * Portions of this code were taken from or based on ftpio.c:
34  *
35  * ----------------------------------------------------------------------------
36  * "THE BEER-WARE LICENSE" (Revision 42):
37  * <phk@FreeBSD.org> wrote this file.  As long as you retain this notice you
38  * can do whatever you want with this stuff. If we meet some day, and you think
39  * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
40  * ----------------------------------------------------------------------------
41  *
42  * Major Changelog:
43  *
44  * Dag-Erling Coïdan Smørgrav
45  * 9 Jun 1998
46  *
47  * Incorporated into libfetch
48  *
49  * Jordan K. Hubbard
50  * 17 Jan 1996
51  *
52  * Turned inside out. Now returns xfers as new file ids, not as a special
53  * `state' of FTP_t
54  *
55  * $ftpioId: ftpio.c,v 1.30 1998/04/11 07:28:53 phk Exp $
56  *
57  */
58
59 #include <sys/param.h>
60 #include <sys/socket.h>
61 #include <netinet/in.h>
62
63 #include <ctype.h>
64 #include <err.h>
65 #include <errno.h>
66 #include <fcntl.h>
67 #include <netdb.h>
68 #include <stdarg.h>
69 #include <stdio.h>
70 #include <stdlib.h>
71 #include <string.h>
72 #include <time.h>
73 #include <unistd.h>
74
75 #include "fetch.h"
76 #include "common.h"
77 #include "ftperr.h"
78
79 #define FTP_ANONYMOUS_USER      "anonymous"
80
81 #define FTP_CONNECTION_ALREADY_OPEN     125
82 #define FTP_OPEN_DATA_CONNECTION        150
83 #define FTP_OK                          200
84 #define FTP_FILE_STATUS                 213
85 #define FTP_SERVICE_READY               220
86 #define FTP_TRANSFER_COMPLETE           226
87 #define FTP_PASSIVE_MODE                227
88 #define FTP_LPASSIVE_MODE               228
89 #define FTP_EPASSIVE_MODE               229
90 #define FTP_LOGGED_IN                   230
91 #define FTP_FILE_ACTION_OK              250
92 #define FTP_NEED_PASSWORD               331
93 #define FTP_NEED_ACCOUNT                332
94 #define FTP_FILE_OK                     350
95 #define FTP_SYNTAX_ERROR                500
96 #define FTP_PROTOCOL_ERROR              999
97
98 static struct url cached_host;
99 static conn_t   *cached_connection;
100
101 #define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
102                          && isdigit(foo[2]) \
103                          && (foo[3] == ' ' || foo[3] == '\0'))
104 #define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
105                         && isdigit(foo[2]) && foo[3] == '-')
106
107 /*
108  * Translate IPv4 mapped IPv6 address to IPv4 address
109  */
110 static void
111 unmappedaddr(struct sockaddr_in6 *sin6)
112 {
113         struct sockaddr_in *sin4;
114         u_int32_t addr;
115         int port;
116
117         if (sin6->sin6_family != AF_INET6 ||
118             !IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
119                 return;
120         sin4 = (struct sockaddr_in *)sin6;
121         addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12];
122         port = sin6->sin6_port;
123         memset(sin4, 0, sizeof(struct sockaddr_in));
124         sin4->sin_addr.s_addr = addr;
125         sin4->sin_port = port;
126         sin4->sin_family = AF_INET;
127         sin4->sin_len = sizeof(struct sockaddr_in);
128 }
129
130 /*
131  * Get server response
132  */
133 static int
134 _ftp_chkerr(conn_t *conn)
135 {
136         if (_fetch_getln(conn) == -1) {
137                 _fetch_syserr();
138                 return (-1);
139         }
140         if (isftpinfo(conn->buf)) {
141                 while (conn->buflen && !isftpreply(conn->buf)) {
142                         if (_fetch_getln(conn) == -1) {
143                                 _fetch_syserr();
144                                 return (-1);
145                         }
146                 }
147         }
148
149         while (conn->buflen && isspace(conn->buf[conn->buflen - 1]))
150                 conn->buflen--;
151         conn->buf[conn->buflen] = '\0';
152
153         if (!isftpreply(conn->buf)) {
154                 _ftp_seterr(FTP_PROTOCOL_ERROR);
155                 return (-1);
156         }
157
158         conn->err = (conn->buf[0] - '0') * 100
159             + (conn->buf[1] - '0') * 10
160             + (conn->buf[2] - '0');
161
162         return (conn->err);
163 }
164
165 /*
166  * Send a command and check reply
167  */
168 static int
169 _ftp_cmd(conn_t *conn, const char *fmt, ...)
170 {
171         va_list ap;
172         size_t len;
173         char *msg;
174         int r;
175
176         va_start(ap, fmt);
177         len = vasprintf(&msg, fmt, ap);
178         va_end(ap);
179
180         if (msg == NULL) {
181                 errno = ENOMEM;
182                 _fetch_syserr();
183                 return (-1);
184         }
185
186         r = _fetch_putln(conn, msg, len);
187         free(msg);
188
189         if (r == -1) {
190                 _fetch_syserr();
191                 return (-1);
192         }
193
194         return (_ftp_chkerr(conn));
195 }
196
197 /*
198  * Return a pointer to the filename part of a path
199  */
200 static const char *
201 _ftp_filename(const char *file)
202 {
203         char *s;
204
205         if ((s = strrchr(file, '/')) == NULL)
206                 return (file);
207         else
208                 return (s + 1);
209 }
210
211 /*
212  * Change working directory to the directory that contains the specified
213  * file.
214  */
215 static int
216 _ftp_cwd(conn_t *conn, const char *file)
217 {
218         char *s;
219         int e;
220
221         if ((s = strrchr(file, '/')) == NULL || s == file) {
222                 e = _ftp_cmd(conn, "CWD /");
223         } else {
224                 e = _ftp_cmd(conn, "CWD %.*s", s - file, file);
225         }
226         if (e != FTP_FILE_ACTION_OK) {
227                 _ftp_seterr(e);
228                 return (-1);
229         }
230         return (0);
231 }
232
233 /*
234  * Request and parse file stats
235  */
236 static int
237 _ftp_stat(conn_t *conn, const char *file, struct url_stat *us)
238 {
239         char *ln;
240         const char *s;
241         struct tm tm;
242         time_t t;
243         int e;
244
245         us->size = -1;
246         us->atime = us->mtime = 0;
247
248         if ((s = strrchr(file, '/')) == NULL)
249                 s = file;
250         else
251                 ++s;
252
253         if ((e = _ftp_cmd(conn, "SIZE %s", s)) != FTP_FILE_STATUS) {
254                 _ftp_seterr(e);
255                 return (-1);
256         }
257         for (ln = conn->buf + 4; *ln && isspace(*ln); ln++)
258                 /* nothing */ ;
259         for (us->size = 0; *ln && isdigit(*ln); ln++)
260                 us->size = us->size * 10 + *ln - '0';
261         if (*ln && !isspace(*ln)) {
262                 _ftp_seterr(FTP_PROTOCOL_ERROR);
263                 us->size = -1;
264                 return (-1);
265         }
266         if (us->size == 0)
267                 us->size = -1;
268         DEBUG(fprintf(stderr, "size: [%lld]\n", (long long)us->size));
269
270         if ((e = _ftp_cmd(conn, "MDTM %s", s)) != FTP_FILE_STATUS) {
271                 _ftp_seterr(e);
272                 return (-1);
273         }
274         for (ln = conn->buf + 4; *ln && isspace(*ln); ln++)
275                 /* nothing */ ;
276         switch (strspn(ln, "0123456789")) {
277         case 14:
278                 break;
279         case 15:
280                 ln++;
281                 ln[0] = '2';
282                 ln[1] = '0';
283                 break;
284         default:
285                 _ftp_seterr(FTP_PROTOCOL_ERROR);
286                 return (-1);
287         }
288         if (sscanf(ln, "%04d%02d%02d%02d%02d%02d",
289             &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
290             &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
291                 _ftp_seterr(FTP_PROTOCOL_ERROR);
292                 return (-1);
293         }
294         tm.tm_mon--;
295         tm.tm_year -= 1900;
296         tm.tm_isdst = -1;
297         t = timegm(&tm);
298         if (t == (time_t)-1)
299                 t = time(NULL);
300         us->mtime = t;
301         us->atime = t;
302         DEBUG(fprintf(stderr,
303             "last modified: [%04d-%02d-%02d %02d:%02d:%02d]\n",
304             tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
305             tm.tm_hour, tm.tm_min, tm.tm_sec));
306         return (0);
307 }
308
309 /*
310  * I/O functions for FTP
311  */
312 struct ftpio {
313         conn_t  *cconn;         /* Control connection */
314         conn_t  *dconn;         /* Data connection */
315         int      dir;           /* Direction */
316         int      eof;           /* EOF reached */
317         int      err;           /* Error code */
318 };
319
320 static int       _ftp_readfn(void *, char *, int);
321 static int       _ftp_writefn(void *, const char *, int);
322 static fpos_t    _ftp_seekfn(void *, fpos_t, int);
323 static int       _ftp_closefn(void *);
324
325 static int
326 _ftp_readfn(void *v, char *buf, int len)
327 {
328         struct ftpio *io;
329         int r;
330
331         io = (struct ftpio *)v;
332         if (io == NULL) {
333                 errno = EBADF;
334                 return (-1);
335         }
336         if (io->cconn == NULL || io->dconn == NULL || io->dir == O_WRONLY) {
337                 errno = EBADF;
338                 return (-1);
339         }
340         if (io->err) {
341                 errno = io->err;
342                 return (-1);
343         }
344         if (io->eof)
345                 return (0);
346         r = _fetch_read(io->dconn, buf, len);
347         if (r > 0)
348                 return (r);
349         if (r == 0) {
350                 io->eof = 1;
351                 return (0);
352         }
353         if (errno != EINTR)
354                 io->err = errno;
355         return (-1);
356 }
357
358 static int
359 _ftp_writefn(void *v, const char *buf, int len)
360 {
361         struct ftpio *io;
362         int w;
363
364         io = (struct ftpio *)v;
365         if (io == NULL) {
366                 errno = EBADF;
367                 return (-1);
368         }
369         if (io->cconn == NULL || io->dconn == NULL || io->dir == O_RDONLY) {
370                 errno = EBADF;
371                 return (-1);
372         }
373         if (io->err) {
374                 errno = io->err;
375                 return (-1);
376         }
377         w = _fetch_write(io->dconn, buf, len);
378         if (w >= 0)
379                 return (w);
380         if (errno != EINTR)
381                 io->err = errno;
382         return (-1);
383 }
384
385 static fpos_t
386 _ftp_seekfn(void *v, fpos_t pos __unused, int whence __unused)
387 {
388         struct ftpio *io;
389
390         io = (struct ftpio *)v;
391         if (io == NULL) {
392                 errno = EBADF;
393                 return (-1);
394         }
395         errno = ESPIPE;
396         return (-1);
397 }
398
399 static int
400 _ftp_closefn(void *v)
401 {
402         struct ftpio *io;
403         int r;
404
405         io = (struct ftpio *)v;
406         if (io == NULL) {
407                 errno = EBADF;
408                 return (-1);
409         }
410         if (io->dir == -1)
411                 return (0);
412         if (io->cconn == NULL || io->dconn == NULL) {
413                 errno = EBADF;
414                 return (-1);
415         }
416         _fetch_close(io->dconn);
417         io->dir = -1;
418         io->dconn = NULL;
419         DEBUG(fprintf(stderr, "Waiting for final status\n"));
420         r = _ftp_chkerr(io->cconn);
421         if (io->cconn == cached_connection && io->cconn->ref == 1)
422                 cached_connection = NULL;
423         _fetch_close(io->cconn);
424         free(io);
425         return (r == FTP_TRANSFER_COMPLETE) ? 0 : -1;
426 }
427
428 static FILE *
429 _ftp_setup(conn_t *cconn, conn_t *dconn, int mode)
430 {
431         struct ftpio *io;
432         FILE *f;
433
434         if (cconn == NULL || dconn == NULL)
435                 return (NULL);
436         if ((io = malloc(sizeof(*io))) == NULL)
437                 return (NULL);
438         io->cconn = cconn;
439         io->dconn = dconn;
440         io->dir = mode;
441         io->eof = io->err = 0;
442         f = funopen(io, _ftp_readfn, _ftp_writefn, _ftp_seekfn, _ftp_closefn);
443         if (f == NULL)
444                 free(io);
445         return (f);
446 }
447
448 /*
449  * Transfer file
450  */
451 static FILE *
452 _ftp_transfer(conn_t *conn, const char *oper, const char *file,
453     int mode, off_t offset, const char *flags)
454 {
455         struct sockaddr_storage sa;
456         struct sockaddr_in6 *sin6;
457         struct sockaddr_in *sin4;
458         int low, pasv, verbose;
459         int e, sd = -1;
460         socklen_t l;
461         char *s;
462         FILE *df;
463
464         /* check flags */
465         low = CHECK_FLAG('l');
466         pasv = CHECK_FLAG('p');
467         verbose = CHECK_FLAG('v');
468
469         /* passive mode */
470         if (!pasv)
471                 pasv = ((s = getenv("FTP_PASSIVE_MODE")) != NULL &&
472                     strncasecmp(s, "no", 2) != 0);
473
474         /* find our own address, bind, and listen */
475         l = sizeof(sa);
476         if (getsockname(conn->sd, (struct sockaddr *)&sa, &l) == -1)
477                 goto sysouch;
478         if (sa.ss_family == AF_INET6)
479                 unmappedaddr((struct sockaddr_in6 *)&sa);
480
481         /* open data socket */
482         if ((sd = socket(sa.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
483                 _fetch_syserr();
484                 return (NULL);
485         }
486
487         if (pasv) {
488                 u_char addr[64];
489                 char *ln, *p;
490                 unsigned int i;
491                 int port;
492
493                 /* send PASV command */
494                 if (verbose)
495                         _fetch_info("setting passive mode");
496                 switch (sa.ss_family) {
497                 case AF_INET:
498                         if ((e = _ftp_cmd(conn, "PASV")) != FTP_PASSIVE_MODE)
499                                 goto ouch;
500                         break;
501                 case AF_INET6:
502                         if ((e = _ftp_cmd(conn, "EPSV")) != FTP_EPASSIVE_MODE) {
503                                 if (e == -1)
504                                         goto ouch;
505                                 if ((e = _ftp_cmd(conn, "LPSV")) !=
506                                     FTP_LPASSIVE_MODE)
507                                         goto ouch;
508                         }
509                         break;
510                 default:
511                         e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
512                         goto ouch;
513                 }
514
515                 /*
516                  * Find address and port number. The reply to the PASV command
517                  * is IMHO the one and only weak point in the FTP protocol.
518                  */
519                 ln = conn->buf;
520                 switch (e) {
521                 case FTP_PASSIVE_MODE:
522                 case FTP_LPASSIVE_MODE:
523                         for (p = ln + 3; *p && !isdigit(*p); p++)
524                                 /* nothing */ ;
525                         if (!*p) {
526                                 e = FTP_PROTOCOL_ERROR;
527                                 goto ouch;
528                         }
529                         l = (e == FTP_PASSIVE_MODE ? 6 : 21);
530                         for (i = 0; *p && i < l; i++, p++)
531                                 addr[i] = strtol(p, &p, 10);
532                         if (i < l) {
533                                 e = FTP_PROTOCOL_ERROR;
534                                 goto ouch;
535                         }
536                         break;
537                 case FTP_EPASSIVE_MODE:
538                         for (p = ln + 3; *p && *p != '('; p++)
539                                 /* nothing */ ;
540                         if (!*p) {
541                                 e = FTP_PROTOCOL_ERROR;
542                                 goto ouch;
543                         }
544                         ++p;
545                         if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2],
546                                 &port, &addr[3]) != 5 ||
547                             addr[0] != addr[1] ||
548                             addr[0] != addr[2] || addr[0] != addr[3]) {
549                                 e = FTP_PROTOCOL_ERROR;
550                                 goto ouch;
551                         }
552                         break;
553                 }
554
555                 /* seek to required offset */
556                 if (offset)
557                         if (_ftp_cmd(conn, "REST %lu", (u_long)offset) != FTP_FILE_OK)
558                                 goto sysouch;
559
560                 /* construct sockaddr for data socket */
561                 l = sizeof(sa);
562                 if (getpeername(conn->sd, (struct sockaddr *)&sa, &l) == -1)
563                         goto sysouch;
564                 if (sa.ss_family == AF_INET6)
565                         unmappedaddr((struct sockaddr_in6 *)&sa);
566                 switch (sa.ss_family) {
567                 case AF_INET6:
568                         sin6 = (struct sockaddr_in6 *)&sa;
569                         if (e == FTP_EPASSIVE_MODE)
570                                 sin6->sin6_port = htons(port);
571                         else {
572                                 bcopy(addr + 2, (char *)&sin6->sin6_addr, 16);
573                                 bcopy(addr + 19, (char *)&sin6->sin6_port, 2);
574                         }
575                         break;
576                 case AF_INET:
577                         sin4 = (struct sockaddr_in *)&sa;
578                         if (e == FTP_EPASSIVE_MODE)
579                                 sin4->sin_port = htons(port);
580                         else {
581                                 bcopy(addr, (char *)&sin4->sin_addr, 4);
582                                 bcopy(addr + 4, (char *)&sin4->sin_port, 2);
583                         }
584                         break;
585                 default:
586                         e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
587                         break;
588                 }
589
590                 /* connect to data port */
591                 if (verbose)
592                         _fetch_info("opening data connection");
593                 if (connect(sd, (struct sockaddr *)&sa, sa.ss_len) == -1)
594                         goto sysouch;
595
596                 /* make the server initiate the transfer */
597                 if (verbose)
598                         _fetch_info("initiating transfer");
599                 e = _ftp_cmd(conn, "%s %s", oper, _ftp_filename(file));
600                 if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION)
601                         goto ouch;
602
603         } else {
604                 u_int32_t a;
605                 u_short p;
606                 int arg, d;
607                 char *ap;
608                 char hname[INET6_ADDRSTRLEN];
609
610                 switch (sa.ss_family) {
611                 case AF_INET6:
612                         ((struct sockaddr_in6 *)&sa)->sin6_port = 0;
613 #ifdef IPV6_PORTRANGE
614                         arg = low ? IPV6_PORTRANGE_DEFAULT : IPV6_PORTRANGE_HIGH;
615                         if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE,
616                                 (char *)&arg, sizeof(arg)) == -1)
617                                 goto sysouch;
618 #endif
619                         break;
620                 case AF_INET:
621                         ((struct sockaddr_in *)&sa)->sin_port = 0;
622                         arg = low ? IP_PORTRANGE_DEFAULT : IP_PORTRANGE_HIGH;
623                         if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE,
624                                 (char *)&arg, sizeof(arg)) == -1)
625                                 goto sysouch;
626                         break;
627                 }
628                 if (verbose)
629                         _fetch_info("binding data socket");
630                 if (bind(sd, (struct sockaddr *)&sa, sa.ss_len) == -1)
631                         goto sysouch;
632                 if (listen(sd, 1) == -1)
633                         goto sysouch;
634
635                 /* find what port we're on and tell the server */
636                 if (getsockname(sd, (struct sockaddr *)&sa, &l) == -1)
637                         goto sysouch;
638                 switch (sa.ss_family) {
639                 case AF_INET:
640                         sin4 = (struct sockaddr_in *)&sa;
641                         a = ntohl(sin4->sin_addr.s_addr);
642                         p = ntohs(sin4->sin_port);
643                         e = _ftp_cmd(conn, "PORT %d,%d,%d,%d,%d,%d",
644                             (a >> 24) & 0xff, (a >> 16) & 0xff,
645                             (a >> 8) & 0xff, a & 0xff,
646                             (p >> 8) & 0xff, p & 0xff);
647                         break;
648                 case AF_INET6:
649 #define UC(b)   (((int)b)&0xff)
650                         e = -1;
651                         sin6 = (struct sockaddr_in6 *)&sa;
652                         sin6->sin6_scope_id = 0;
653                         if (getnameinfo((struct sockaddr *)&sa, sa.ss_len,
654                                 hname, sizeof(hname),
655                                 NULL, 0, NI_NUMERICHOST) == 0) {
656                                 e = _ftp_cmd(conn, "EPRT |%d|%s|%d|", 2, hname,
657                                     htons(sin6->sin6_port));
658                                 if (e == -1)
659                                         goto ouch;
660                         }
661                         if (e != FTP_OK) {
662                                 ap = (char *)&sin6->sin6_addr;
663                                 e = _ftp_cmd(conn,
664                                     "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
665                                     6, 16,
666                                     UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]),
667                                     UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]),
668                                     UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]),
669                                     UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]),
670                                     2,
671                                     (ntohs(sin6->sin6_port) >> 8) & 0xff,
672                                     ntohs(sin6->sin6_port)        & 0xff);
673                         }
674                         break;
675                 default:
676                         e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
677                         goto ouch;
678                 }
679                 if (e != FTP_OK)
680                         goto ouch;
681
682                 /* seek to required offset */
683                 if (offset)
684                         if (_ftp_cmd(conn, "REST %llu",
685                             (unsigned long long)offset) != FTP_FILE_OK)
686                                 goto sysouch;
687
688                 /* make the server initiate the transfer */
689                 if (verbose)
690                         _fetch_info("initiating transfer");
691                 e = _ftp_cmd(conn, "%s %s", oper, _ftp_filename(file));
692                 if (e != FTP_OPEN_DATA_CONNECTION)
693                         goto ouch;
694
695                 /* accept the incoming connection and go to town */
696                 if ((d = accept(sd, NULL, NULL)) == -1)
697                         goto sysouch;
698                 close(sd);
699                 sd = d;
700         }
701
702         if ((df = _ftp_setup(conn, _fetch_reopen(sd), mode)) == NULL)
703                 goto sysouch;
704         return (df);
705
706 sysouch:
707         _fetch_syserr();
708         if (sd >= 0)
709                 close(sd);
710         return (NULL);
711
712 ouch:
713         if (e != -1)
714                 _ftp_seterr(e);
715         if (sd >= 0)
716                 close(sd);
717         return (NULL);
718 }
719
720 /*
721  * Authenticate
722  */
723 static int
724 _ftp_authenticate(conn_t *conn, struct url *url, struct url *purl)
725 {
726         const char *user, *pwd, *logname;
727         char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1];
728         int e, len;
729
730         /* XXX FTP_AUTH, and maybe .netrc */
731
732         /* send user name and password */
733         if (url->user[0] == '\0')
734                 _fetch_netrc_auth(url);
735         user = url->user;
736         if (*user == '\0')
737                 user = getenv("FTP_LOGIN");
738         if (user == NULL || *user == '\0')
739                 user = FTP_ANONYMOUS_USER;
740         if (purl && url->port == _fetch_default_port(url->scheme))
741                 e = _ftp_cmd(conn, "USER %s@%s", user, url->host);
742         else if (purl)
743                 e = _ftp_cmd(conn, "USER %s@%s@%d", user, url->host, url->port);
744         else
745                 e = _ftp_cmd(conn, "USER %s", user);
746
747         /* did the server request a password? */
748         if (e == FTP_NEED_PASSWORD) {
749                 pwd = url->pwd;
750                 if (*pwd == '\0')
751                         pwd = getenv("FTP_PASSWORD");
752                 if (pwd == NULL || *pwd == '\0') {
753                         if ((logname = getlogin()) == 0)
754                                 logname = FTP_ANONYMOUS_USER;
755                         if ((len = snprintf(pbuf, MAXLOGNAME + 1, "%s@", logname)) < 0)
756                                 len = 0;
757                         else if (len > MAXLOGNAME)
758                                 len = MAXLOGNAME;
759                         gethostname(pbuf + len, sizeof(pbuf) - len);
760                         pwd = pbuf;
761                 }
762                 e = _ftp_cmd(conn, "PASS %s", pwd);
763         }
764
765         return (e);
766 }
767
768 /*
769  * Log on to FTP server
770  */
771 static conn_t *
772 _ftp_connect(struct url *url, struct url *purl, const char *flags)
773 {
774         conn_t *conn;
775         int e, direct, verbose;
776 #ifdef INET6
777         int af = AF_UNSPEC;
778 #else
779         int af = AF_INET;
780 #endif
781
782         direct = CHECK_FLAG('d');
783         verbose = CHECK_FLAG('v');
784         if (CHECK_FLAG('4'))
785                 af = AF_INET;
786         else if (CHECK_FLAG('6'))
787                 af = AF_INET6;
788
789         if (direct)
790                 purl = NULL;
791
792         /* check for proxy */
793         if (purl) {
794                 /* XXX proxy authentication! */
795                 conn = _fetch_connect(purl->host, purl->port, af, verbose);
796         } else {
797                 /* no proxy, go straight to target */
798                 conn = _fetch_connect(url->host, url->port, af, verbose);
799                 purl = NULL;
800         }
801
802         /* check connection */
803         if (conn == NULL)
804                 /* _fetch_connect() has already set an error code */
805                 return (NULL);
806
807         /* expect welcome message */
808         if ((e = _ftp_chkerr(conn)) != FTP_SERVICE_READY)
809                 goto fouch;
810
811         /* authenticate */
812         if ((e = _ftp_authenticate(conn, url, purl)) != FTP_LOGGED_IN)
813                 goto fouch;
814
815         /* might as well select mode and type at once */
816 #ifdef FTP_FORCE_STREAM_MODE
817         if ((e = _ftp_cmd(conn, "MODE S")) != FTP_OK) /* default is S */
818                 goto fouch;
819 #endif
820         if ((e = _ftp_cmd(conn, "TYPE I")) != FTP_OK) /* default is A */
821                 goto fouch;
822
823         /* done */
824         return (conn);
825
826 fouch:
827         if (e != -1)
828                 _ftp_seterr(e);
829         _fetch_close(conn);
830         return (NULL);
831 }
832
833 /*
834  * Disconnect from server
835  */
836 static void
837 _ftp_disconnect(conn_t *conn)
838 {
839         (void)_ftp_cmd(conn, "QUIT");
840         if (conn == cached_connection && conn->ref == 1)
841                 cached_connection = NULL;
842         _fetch_close(conn);
843 }
844
845 /*
846  * Check if we're already connected
847  */
848 static int
849 _ftp_isconnected(struct url *url)
850 {
851         return (cached_connection
852             && (strcmp(url->host, cached_host.host) == 0)
853             && (strcmp(url->user, cached_host.user) == 0)
854             && (strcmp(url->pwd, cached_host.pwd) == 0)
855             && (url->port == cached_host.port));
856 }
857
858 /*
859  * Check the cache, reconnect if no luck
860  */
861 static conn_t *
862 _ftp_cached_connect(struct url *url, struct url *purl, const char *flags)
863 {
864         conn_t *conn;
865         int e;
866
867         /* set default port */
868         if (!url->port)
869                 url->port = _fetch_default_port(url->scheme);
870
871         /* try to use previously cached connection */
872         if (_ftp_isconnected(url)) {
873                 e = _ftp_cmd(cached_connection, "NOOP");
874                 if (e == FTP_OK || e == FTP_SYNTAX_ERROR)
875                         return (_fetch_ref(cached_connection));
876         }
877
878         /* connect to server */
879         if ((conn = _ftp_connect(url, purl, flags)) == NULL)
880                 return (NULL);
881         if (cached_connection)
882                 _ftp_disconnect(cached_connection);
883         cached_connection = _fetch_ref(conn);
884         memcpy(&cached_host, url, sizeof(*url));
885         return (conn);
886 }
887
888 /*
889  * Check the proxy settings
890  */
891 static struct url *
892 _ftp_get_proxy(const char *flags)
893 {
894         struct url *purl;
895         char *p;
896
897         if (flags != NULL && strchr(flags, 'd') != NULL)
898                 return (NULL);
899         if (((p = getenv("FTP_PROXY")) || (p = getenv("ftp_proxy")) ||
900                 (p = getenv("HTTP_PROXY")) || (p = getenv("http_proxy"))) &&
901             *p && (purl = fetchParseURL(p)) != NULL) {
902                 if (!*purl->scheme) {
903                         if (getenv("FTP_PROXY") || getenv("ftp_proxy"))
904                                 strcpy(purl->scheme, SCHEME_FTP);
905                         else
906                                 strcpy(purl->scheme, SCHEME_HTTP);
907                 }
908                 if (!purl->port)
909                         purl->port = _fetch_default_proxy_port(purl->scheme);
910                 if (strcasecmp(purl->scheme, SCHEME_FTP) == 0 ||
911                     strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
912                         return (purl);
913                 fetchFreeURL(purl);
914         }
915         return (NULL);
916 }
917
918 /*
919  * Process an FTP request
920  */
921 FILE *
922 _ftp_request(struct url *url, const char *op, struct url_stat *us,
923     struct url *purl, const char *flags)
924 {
925         conn_t *conn;
926         int oflag;
927
928         /* check if we should use HTTP instead */
929         if (purl && strcasecmp(purl->scheme, SCHEME_HTTP) == 0) {
930                 if (strcmp(op, "STAT") == 0)
931                         return (_http_request(url, "HEAD", us, purl, flags));
932                 else if (strcmp(op, "RETR") == 0)
933                         return (_http_request(url, "GET", us, purl, flags));
934                 /*
935                  * Our HTTP code doesn't support PUT requests yet, so try
936                  * a direct connection.
937                  */
938         }
939
940         /* connect to server */
941         conn = _ftp_cached_connect(url, purl, flags);
942         if (purl)
943                 fetchFreeURL(purl);
944         if (conn == NULL)
945                 return (NULL);
946
947         /* change directory */
948         if (_ftp_cwd(conn, url->doc) == -1)
949                 return (NULL);
950
951         /* stat file */
952         if (us && _ftp_stat(conn, url->doc, us) == -1
953             && fetchLastErrCode != FETCH_PROTO
954             && fetchLastErrCode != FETCH_UNAVAIL)
955                 return (NULL);
956
957         /* just a stat */
958         if (strcmp(op, "STAT") == 0)
959                 return (FILE *)1; /* bogus return value */
960         if (strcmp(op, "STOR") == 0 || strcmp(op, "APPE") == 0)
961                 oflag = O_WRONLY;
962         else
963                 oflag = O_RDONLY;
964
965         /* initiate the transfer */
966         return (_ftp_transfer(conn, op, url->doc, oflag, url->offset, flags));
967 }
968
969 /*
970  * Get and stat file
971  */
972 FILE *
973 fetchXGetFTP(struct url *url, struct url_stat *us, const char *flags)
974 {
975         return (_ftp_request(url, "RETR", us, _ftp_get_proxy(flags), flags));
976 }
977
978 /*
979  * Get file
980  */
981 FILE *
982 fetchGetFTP(struct url *url, const char *flags)
983 {
984         return (fetchXGetFTP(url, NULL, flags));
985 }
986
987 /*
988  * Put file
989  */
990 FILE *
991 fetchPutFTP(struct url *url, const char *flags)
992 {
993
994         return (_ftp_request(url, CHECK_FLAG('a') ? "APPE" : "STOR", NULL,
995             _ftp_get_proxy(flags), flags));
996 }
997
998 /*
999  * Get file stats
1000  */
1001 int
1002 fetchStatFTP(struct url *url, struct url_stat *us, const char *flags)
1003 {
1004         FILE *f;
1005
1006         f = _ftp_request(url, "STAT", us, _ftp_get_proxy(flags), flags);
1007         if (f == NULL)
1008                 return (-1);
1009         fclose(f);
1010         return (0);
1011 }
1012
1013 /*
1014  * List a directory
1015  */
1016 struct url_ent *
1017 fetchListFTP(struct url *url __unused, const char *flags __unused)
1018 {
1019         warnx("fetchListFTP(): not implemented");
1020         return (NULL);
1021 }