xref: /dragonfly/lib/libftpio/ftpio.c (revision 86d7f5d305c6adaa56ff4582ece9859d73106103)
1 /*
2  * ----------------------------------------------------------------------------
3  * "THE BEER-WARE LICENSE" (Revision 42):
4  * <phk@login.dknet.dk> wrote this file.  As long as you retain this notice you
5  * can do whatever you want with this stuff. If we meet some day, and you think
6  * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
7  * ----------------------------------------------------------------------------
8  *
9  * Major Changelog:
10  *
11  * Jordan K. Hubbard
12  * 17 Jan 1996
13  *
14  * Turned inside out. Now returns xfers as new file ids, not as a special
15  * `state' of FTP_t
16  *
17  * $FreeBSD: src/lib/libftpio/ftpio.c,v 1.33.2.4 2002/07/25 15:25:32 ume Exp $
18  * $DragonFly: src/lib/libftpio/ftpio.c,v 1.8 2005/07/23 20:23:06 joerg Exp $
19  *
20  */
21 
22 #include <sys/param.h>
23 #include <sys/socket.h>
24 
25 #include <netinet/in.h>
26 
27 #include <arpa/inet.h>
28 
29 #include <ctype.h>
30 #include <errno.h>
31 #include <ftpio.h>
32 #include <inttypes.h>
33 #include <netdb.h>
34 #include <signal.h>
35 #include <stdarg.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <unistd.h>
40 
41 #define SUCCESS                0
42 #define FAILURE               -1
43 
44 #ifndef TRUE
45 #define TRUE        (1)
46 #define FALSE       (0)
47 #endif
48 
49 /* How to see by a given code whether or not the connection has timed out */
50 #define FTP_TIMEOUT(code)     (FtpTimedOut || code == FTP_TIMED_OUT)
51 
52 /* Internal routines - deal only with internal FTP_t type */
53 static FTP_t        ftp_new(void);
54 static void         check_passive(FILE *fp);
55 static int          ftp_read_method(void *n, char *buf, int nbytes);
56 static int          ftp_write_method(void *n, const char *buf, int nbytes);
57 static int          ftp_close_method(void *n);
58 static int          writes(int fd, const char *s);
59 static char         *get_a_line(FTP_t ftp);
60 static int          get_a_number(FTP_t ftp, char **q);
61 static int          botch(const char *func, const char *botch_state);
62 static int          cmd(FTP_t ftp, const char *fmt, ...) __printflike(2, 3);
63 static int          ftp_login_session(FTP_t ftp, const char *host, int af,
64                                           const char *user, const char *passwd,
65                                           int port, int verbose);
66 static int          ftp_file_op(FTP_t ftp, const char *operation, const char *file,
67                                   FILE **fp, const char *mode, off_t *seekto);
68 static int          ftp_close(FTP_t ftp);
69 static int          get_url_info(const char *url_in, char *host_ret,
70                                    size_t host_len, int *port_ret,
71                                    char *name_ret, size_t name_len);
72 static void         ftp_timeout(int sig);
73 static void         ftp_set_timeout(void);
74 static void         ftp_clear_timeout(void);
75 static void         ai_unmapped(struct addrinfo *);
76 
77 int                 networkInit(void);
78 
79 
80 /* Global status variable - ick */
81 int FtpTimedOut;
82 
83 /* FTP happy status codes */
84 #define FTP_GENERALLY_HAPPY   200
85 #define FTP_ASCII_HAPPY                 FTP_GENERALLY_HAPPY
86 #define FTP_BINARY_HAPPY      FTP_GENERALLY_HAPPY
87 #define FTP_PORT_HAPPY                  FTP_GENERALLY_HAPPY
88 #define FTP_HAPPY_COMMENT     220
89 #define FTP_QUIT_HAPPY                  221
90 #define FTP_TRANSFER_HAPPY    226
91 #define FTP_PASSIVE_HAPPY     227
92 #define FTP_LPASSIVE_HAPPY    228
93 #define FTP_EPASSIVE_HAPPY    229
94 #define FTP_CHDIR_HAPPY                 250
95 
96 /* FTP unhappy status codes */
97 #define FTP_TIMED_OUT                   421
98 
99 /* Placeholder in case we want to do any pre-init stuff at some point */
100 int
networkInit(void)101 networkInit(void)
102 {
103     return SUCCESS; /* XXX dummy function for now XXX */
104 }
105 
106 /* Check a return code with some lenience for back-dated garbage that might be in the buffer */
107 static int
check_code(FTP_t ftp,int var,int preferred)108 check_code(FTP_t ftp, int var, int preferred)
109 {
110     ftp->error = 0;
111     while (1) {
112           if (var == preferred)
113               return 0;
114           else if (var == FTP_TRANSFER_HAPPY)     /* last operation succeeded */
115               var = get_a_number(ftp, NULL);
116           else if (var == FTP_HAPPY_COMMENT)      /* chit-chat */
117               var = get_a_number(ftp, NULL);
118           else if (var == FTP_GENERALLY_HAPPY)    /* general success code */
119               var = get_a_number(ftp, NULL);
120           else {
121               ftp->error = var;
122               return 1;
123           }
124     }
125 }
126 
127 int
ftpAscii(FILE * fp)128 ftpAscii(FILE *fp)
129 {
130     FTP_t ftp = fcookie(fp);
131     int i;
132 
133     if (!ftp->is_binary)
134           return SUCCESS;
135     i = cmd(ftp, "TYPE A");
136     if (i < 0 || check_code(ftp, i, FTP_ASCII_HAPPY))
137           return i;
138     ftp->is_binary = FALSE;
139     return SUCCESS;
140 }
141 
142 int
ftpBinary(FILE * fp)143 ftpBinary(FILE *fp)
144 {
145     FTP_t ftp = fcookie(fp);
146     int i;
147 
148     if (ftp->is_binary)
149           return SUCCESS;
150     i = cmd(ftp, "TYPE I");
151     if (i < 0 || check_code(ftp, i, FTP_BINARY_HAPPY))
152           return i;
153     ftp->is_binary = TRUE;
154     return SUCCESS;
155 }
156 void
ftpVerbose(FILE * fp,int status)157 ftpVerbose(FILE *fp, int status)
158 {
159     FTP_t ftp = fcookie(fp);
160     ftp->is_verbose = status;
161 }
162 
163 int
ftpChdir(FILE * fp,char * dir)164 ftpChdir(FILE *fp, char *dir)
165 {
166     int i;
167     FTP_t ftp = fcookie(fp);
168 
169     i = cmd(ftp, "CWD %s", dir);
170     if (i < 0 || check_code(ftp, i, FTP_CHDIR_HAPPY))
171           return i;
172     return SUCCESS;
173 }
174 
175 int
ftpErrno(FILE * fp)176 ftpErrno(FILE *fp)
177 {
178     FTP_t ftp = fcookie(fp);
179     return ftp->error;
180 }
181 
182 const char *
ftpErrString(int error)183 ftpErrString(int error)
184 {
185     int   k;
186 
187     if (error == -1)
188           return("connection in wrong state");
189     if (error < 100)
190           /* XXX soon UNIX errnos will catch up with FTP protocol errnos */
191           return strerror(error);
192     for (k = 0; k < ftpErrListLength; k++)
193       if (ftpErrList[k].num == error)
194           return(ftpErrList[k].string);
195     return("Unknown error");
196 }
197 
198 off_t
ftpGetSize(FILE * fp,const char * name)199 ftpGetSize(FILE *fp, const char *name)
200 {
201     int i;
202     char p[BUFSIZ], *cp, *ep;
203     FTP_t ftp = fcookie(fp);
204     off_t size;
205 
206     check_passive(fp);
207     if ((size_t)snprintf(p, sizeof(p), "SIZE %s\r\n", name) >= sizeof(p))
208           return (off_t)(-1);
209     if (ftp->is_verbose)
210           fprintf(stderr, "Sending %s", p);
211     if (writes(ftp->fd_ctrl, p))
212           return (off_t)(-1);
213     i = get_a_number(ftp, &cp);
214     if (check_code(ftp, i, 213))
215           return (off_t)(-1);
216 
217     errno = 0;                                    /* to check for ERANGE */
218     size = (off_t)strtoq(cp, &ep, 10);
219     if (*ep != '\0' || errno == ERANGE)
220           return (off_t)(-1);
221     return size;
222 }
223 
224 time_t
ftpGetModtime(FILE * fp,const char * name)225 ftpGetModtime(FILE *fp, const char *name)
226 {
227     char p[BUFSIZ], *cp;
228     struct tm t;
229     time_t t0 = time (0);
230     FTP_t ftp = fcookie(fp);
231     int i;
232 
233     check_passive(fp);
234     if ((size_t)snprintf(p, sizeof(p), "MDTM %s\r\n", name) >= sizeof(p))
235           return (time_t)0;
236     if (ftp->is_verbose)
237           fprintf(stderr, "Sending %s", p);
238     if (writes(ftp->fd_ctrl, p))
239           return (time_t)0;
240     i = get_a_number(ftp, &cp);
241     if (check_code(ftp, i, 213))
242           return (time_t)0;
243     while (*cp && !isdigit(*cp))
244           cp++;
245     if (!*cp)
246           return (time_t)0;
247     t0 = localtime (&t0)->tm_gmtoff;
248     sscanf(cp, "%04d%02d%02d%02d%02d%02d", &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &t.tm_sec);
249     t.tm_mon--;
250     t.tm_year -= 1900;
251     t.tm_isdst=-1;
252     t.tm_gmtoff = 0;
253     t0 += mktime (&t);
254     return t0;
255 }
256 
257 FILE *
ftpGet(FILE * fp,const char * file,off_t * seekto)258 ftpGet(FILE *fp, const char *file, off_t *seekto)
259 {
260     FILE *fp2;
261     FTP_t ftp = fcookie(fp);
262 
263     check_passive(fp);
264     if (ftpBinary(fp) != SUCCESS)
265           return NULL;
266 
267     if (ftp_file_op(ftp, "RETR", file, &fp2, "r", seekto) == SUCCESS)
268           return fp2;
269     return NULL;
270 }
271 
272 /* Returns a standard FILE pointer type representing an open control connection */
273 FILE *
ftpLogin(const char * host,const char * user,const char * passwd,int port,int verbose,int * retcode)274 ftpLogin(const char *host, const char *user, const char *passwd, int port,
275            int verbose, int *retcode)
276 {
277 #ifdef INET6
278     return ftpLoginAf(host, AF_UNSPEC, user, passwd, port, verbose, retcode);
279 #else
280     return ftpLoginAf(host, AF_INET, user, passwd, port, verbose, retcode);
281 #endif
282 }
283 
284 FILE *
ftpLoginAf(const char * host,int af,const char * user,const char * passwd,int port,int verbose,int * retcode)285 ftpLoginAf(const char *host, int af, const char *user, const char *passwd,
286              int port, int verbose, int *retcode)
287 {
288     FTP_t n;
289     FILE *fp;
290 
291     if (retcode)
292           *retcode = 0;
293     if (networkInit() != SUCCESS)
294           return NULL;
295 
296     n = ftp_new();
297     fp = NULL;
298     if (n && ftp_login_session(n, host, af, user, passwd, port, verbose) == SUCCESS) {
299           fp = funopen(n, ftp_read_method, ftp_write_method, NULL, ftp_close_method);     /* BSD 4.4 function! */
300     }
301     if (retcode) {
302           if (!n)
303               *retcode = (FtpTimedOut ? FTP_TIMED_OUT : -1);
304           /* Poor attempt at mapping real errnos to FTP error codes */
305           else switch(n->error) {
306               case EADDRNOTAVAIL:
307                     *retcode = FTP_TIMED_OUT;     /* Actually no such host, but we have no way of saying that. :-( */
308                     break;
309 
310             case ETIMEDOUT:
311                     *retcode = FTP_TIMED_OUT;
312                     break;
313 
314               default:
315                     *retcode = n->error;
316                     break;
317           }
318     }
319     return fp;
320 }
321 
322 FILE *
ftpPut(FILE * fp,const char * file)323 ftpPut(FILE *fp, const char *file)
324 {
325     FILE *fp2;
326     FTP_t ftp = fcookie(fp);
327 
328     check_passive(fp);
329     if (ftp_file_op(ftp, "STOR", file, &fp2, "w", NULL) == SUCCESS)
330           return fp2;
331     return NULL;
332 }
333 
334 int
ftpPassive(FILE * fp,int st)335 ftpPassive(FILE *fp, int st)
336 {
337     FTP_t ftp = fcookie(fp);
338 
339     ftp->is_passive = !!st;   /* normalize "st" to zero or one */
340     return SUCCESS;
341 }
342 
343 FILE *
ftpGetURL(const char * url,const char * user,const char * passwd,int * retcode)344 ftpGetURL(const char *url, const char *user, const char *passwd, int *retcode)
345 {
346 #ifdef INET6
347     return ftpGetURLAf(url, AF_UNSPEC, user, passwd, retcode);
348 #else
349     return ftpGetURLAf(url, AF_INET, user, passwd, retcode);
350 #endif
351 }
352 
353 FILE *
ftpGetURLAf(const char * url,int af,const char * user,const char * passwd,int * retcode)354 ftpGetURLAf(const char *url, int af, const char *user, const char *passwd,
355               int *retcode)
356 {
357     char host[255], name[255];
358     int port;
359     FILE *fp2;
360     static FILE *fp = NULL;
361     static char *prev_host;
362 
363     if (retcode)
364           *retcode = 0;
365     if (get_url_info(url, host, sizeof(host), &port, name,
366                          sizeof(name)) == SUCCESS) {
367           if (fp && prev_host) {
368               if (!strcmp(prev_host, host)) {
369                     /* Try to use cached connection */
370                     fp2 = ftpGet(fp, name, NULL);
371                     if (!fp2) {
372                         /* Connection timed out or was no longer valid */
373                         fclose(fp);
374                         free(prev_host);
375                         prev_host = NULL;
376                     }
377                     else
378                         return fp2;
379               }
380               else {
381                     /* It's a different host now, flush old */
382                     fclose(fp);
383                     free(prev_host);
384                     prev_host = NULL;
385               }
386           }
387           fp = ftpLoginAf(host, af, user, passwd, port, 0, retcode);
388           if (fp) {
389               fp2 = ftpGet(fp, name, NULL);
390               if (!fp2) {
391                     /* Connection timed out or was no longer valid */
392                     if (retcode)
393                         *retcode = ftpErrno(fp);
394                     fclose(fp);
395                     fp = NULL;
396               }
397               else
398                     prev_host = strdup(host);
399               return fp2;
400           }
401     }
402     return NULL;
403 }
404 
405 FILE *
ftpPutURL(const char * url,const char * user,const char * passwd,int * retcode)406 ftpPutURL(const char *url, const char *user, const char *passwd, int *retcode)
407 {
408 #ifdef INET6
409     return ftpPutURLAf(url, AF_UNSPEC, user, passwd, retcode);
410 #else
411     return ftpPutURLAf(url, AF_INET, user, passwd, retcode);
412 #endif
413 
414 }
415 
416 FILE *
ftpPutURLAf(const char * url,int af,const char * user,const char * passwd,int * retcode)417 ftpPutURLAf(const char *url, int af, const char *user, const char *passwd,
418               int *retcode)
419 {
420     char host[255], name[255];
421     int port;
422     static FILE *fp = NULL;
423     FILE *fp2;
424 
425     if (retcode)
426           *retcode = 0;
427     if (fp) {       /* Close previous managed connection */
428           fclose(fp);
429           fp = NULL;
430     }
431     if (get_url_info(url, host, sizeof(host), &port,
432                          name, sizeof(name)) == SUCCESS) {
433           fp = ftpLoginAf(host, af, user, passwd, port, 0, retcode);
434           if (fp) {
435               fp2 = ftpPut(fp, name);
436               if (!fp2) {
437                     if (retcode)
438                         *retcode = ftpErrno(fp);
439                     fclose(fp);
440                     fp = NULL;
441               }
442               return fp2;
443           }
444     }
445     return NULL;
446 }
447 
448 /* Internal workhorse function for dissecting URLs.  Takes a URL as the first argument and returns the
449    result of such disection in the host, user, passwd, port and name variables. */
450 static int
get_url_info(const char * url_in,char * host_ret,size_t host_len,int * port_ret,char * name_ret,size_t name_len)451 get_url_info(const char *url_in, char *host_ret, size_t host_len,
452                int *port_ret, char *name_ret, size_t name_len)
453 {
454     char *name, *host, *cp, url[BUFSIZ];
455     int port;
456 
457     name = host = NULL;
458     /* XXX add http:// here or somewhere reasonable at some point XXX */
459     if (strncmp("ftp://", url_in, 6) != 0)
460           return FAILURE;
461     /* We like to stomp a lot on the URL string in dissecting it, so copy it first */
462     strncpy(url, url_in, BUFSIZ);
463     host = url + 6;
464     if ((cp = index(host, ':')) != NULL) {
465           *(cp++) = '\0';
466           port = strtol(cp, 0, 0);
467     }
468     else
469           port = 0; /* use default */
470     if (port_ret)
471           *port_ret = port;
472 
473     if ((name = index(cp ? cp : host, '/')) != NULL)
474           *(name++) = '\0';
475     if (host_ret) {
476           if (strlen(host) >= host_len)
477               return FAILURE;
478           strcpy(host_ret, host);
479     }
480     if (name && name_ret) {
481           if (strlen(name) >= name_len)
482               return FAILURE;
483           strcpy(name_ret, name);
484     }
485     return SUCCESS;
486 }
487 
488 static FTP_t
ftp_new(void)489 ftp_new(void)
490 {
491     FTP_t ftp;
492 
493     ftp = (FTP_t)malloc(sizeof *ftp);
494     if (!ftp)
495           return NULL;
496     memset(ftp, 0, sizeof *ftp);
497     ftp->fd_ctrl = -1;
498     ftp->con_state = init;
499     ftp->is_binary = FALSE;
500     ftp->is_passive = FALSE;
501     ftp->is_verbose = FALSE;
502     ftp->error = 0;
503     return ftp;
504 }
505 
506 static int
ftp_read_method(void * vp,char * buf,int nbytes)507 ftp_read_method(void *vp, char *buf, int nbytes)
508 {
509     int i, fd;
510     FTP_t n = (FTP_t)vp;
511 
512     fd = n->fd_ctrl;
513     i = (fd >= 0) ? read(fd, buf, nbytes) : EOF;
514     return i;
515 }
516 
517 static int
ftp_write_method(void * vp,const char * buf,int nbytes)518 ftp_write_method(void *vp, const char *buf, int nbytes)
519 {
520     int i, fd;
521     FTP_t n = (FTP_t)vp;
522 
523     fd = n->fd_ctrl;
524     i = (fd >= 0) ? write(fd, buf, nbytes) : EOF;
525     return i;
526 }
527 
528 static int
ftp_close_method(void * n)529 ftp_close_method(void *n)
530 {
531     int i;
532 
533     i = ftp_close((FTP_t)n);
534     free(n);
535     return i;
536 }
537 
538 /*
539  * This function checks whether the FTP_PASSIVE_MODE environment
540  * variable is set, and, if so, enforces the desired mode.
541  */
542 static void
check_passive(FILE * fp)543 check_passive(FILE *fp)
544 {
545     const char *cp = getenv("FTP_PASSIVE_MODE");
546 
547     if (cp != NULL)
548           ftpPassive(fp, strncasecmp(cp, "no", 2));
549 }
550 
551 static void
ftp_timeout(int sig __unused)552 ftp_timeout(int sig __unused)
553 {
554     FtpTimedOut = TRUE;
555     /* Debug("ftp_pkg: ftp_timeout called - operation timed out"); */
556 }
557 
558 static void
ftp_set_timeout(void)559 ftp_set_timeout(void)
560 {
561     struct sigaction new;
562     char *cp;
563     int ival;
564 
565     FtpTimedOut = FALSE;
566     sigemptyset(&new.sa_mask);
567     new.sa_flags = 0;
568     new.sa_handler = ftp_timeout;
569     sigaction(SIGALRM, &new, NULL);
570     cp = getenv("FTP_TIMEOUT");
571     if (!cp || !(ival = atoi(cp)))
572           ival = 120;
573     alarm(ival);
574 }
575 
576 static void
ftp_clear_timeout(void)577 ftp_clear_timeout(void)
578 {
579     struct sigaction new;
580 
581     alarm(0);
582     sigemptyset(&new.sa_mask);
583     new.sa_flags = 0;
584     new.sa_handler = SIG_DFL;
585     sigaction(SIGALRM, &new, NULL);
586 }
587 
588 static int
writes(int fd,const char * s)589 writes(int fd, const char *s)
590 {
591     int n, i = strlen(s);
592 
593     ftp_set_timeout();
594     n = write(fd, s, i);
595     ftp_clear_timeout();
596     if (FtpTimedOut || i != n)
597           return TRUE;
598     return FALSE;
599 }
600 
601 static char *
get_a_line(FTP_t ftp)602 get_a_line(FTP_t ftp)
603 {
604     static char buf[BUFSIZ];
605     int i,j;
606 
607     /* Debug("ftp_pkg: trying to read a line from %d", ftp->fd_ctrl); */
608     for(i = 0; i < BUFSIZ;) {
609           ftp_set_timeout();
610           j = read(ftp->fd_ctrl, buf + i, 1);
611           ftp_clear_timeout();
612           if (FtpTimedOut || j != 1)
613               return NULL;
614           if (buf[i] == '\r' || buf[i] == '\n') {
615               if (!i)
616                     continue;
617               buf[i] = '\0';
618               if (ftp->is_verbose == TRUE)
619                     fprintf(stderr, "%s\n",buf+4);
620               return buf;
621           }
622           i++;
623     }
624     /* Debug("ftp_pkg: read string \"%s\" from %d", buf, ftp->fd_ctrl); */
625     return buf;
626 }
627 
628 static int
get_a_number(FTP_t ftp,char ** q)629 get_a_number(FTP_t ftp, char **q)
630 {
631     char *p;
632     int i = -1, j;
633 
634     while(1) {
635           p = get_a_line(ftp);
636           if (!p) {
637               ftp_close(ftp);
638               if (FtpTimedOut)
639                     return FTP_TIMED_OUT;
640               return FAILURE;
641           }
642           if (!(isdigit(p[0]) && isdigit(p[1]) && isdigit(p[2])))
643               continue;
644           if (i == -1 && p[3] == '-') {
645               i = strtol(p, 0, 0);
646               continue;
647           }
648           if (p[3] != ' ' && p[3] != '\t')
649               continue;
650           j = strtol(p, 0, 0);
651           if (i == -1) {
652               if (q) *q = p+4;
653               /* Debug("ftp_pkg: read reply %d from server (%s)", j, p); */
654               return j;
655           } else if (j == i) {
656               if (q) *q = p+4;
657               /* Debug("ftp_pkg: read reply %d from server (%s)", j, p); */
658               return j;
659           }
660     }
661 }
662 
663 static int
ftp_close(FTP_t ftp)664 ftp_close(FTP_t ftp)
665 {
666     int i, rcode;
667 
668     rcode = FAILURE;
669     if (ftp->con_state == isopen) {
670           ftp->con_state = quit;
671           /* If last operation timed out, don't try to quit - just close */
672           if (ftp->error != FTP_TIMED_OUT)
673               i = cmd(ftp, "QUIT");
674           else
675               i = FTP_QUIT_HAPPY;
676           if (!check_code(ftp, i, FTP_QUIT_HAPPY))
677               rcode = SUCCESS;
678           close(ftp->fd_ctrl);
679           ftp->fd_ctrl = -1;
680     }
681     else if (ftp->con_state == quit)
682           rcode = SUCCESS;
683     return rcode;
684 }
685 
686 static int
botch(const char * func __unused,const char * botch_state __unused)687 botch(const char *func __unused, const char *botch_state __unused)
688 {
689     /* Debug("ftp_pkg: botch: %s(%s)", func, botch_state); */
690     return FAILURE;
691 }
692 
693 static int
cmd(FTP_t ftp,const char * fmt,...)694 cmd(FTP_t ftp, const char *fmt, ...)
695 {
696     char p[BUFSIZ];
697     int i;
698 
699     va_list ap;
700     va_start(ap, fmt);
701     if ((size_t)vsnprintf(p, sizeof p - 2, fmt, ap) >= sizeof(p) - 2)
702           return FAILURE;
703     va_end(ap);
704 
705     if (ftp->con_state == init)
706           return botch("cmd", "open");
707 
708     strcat(p, "\r\n");
709     if (ftp->is_verbose)
710           fprintf(stderr, "Sending: %s", p);
711     if (writes(ftp->fd_ctrl, p)) {
712           if (FtpTimedOut)
713               return FTP_TIMED_OUT;
714           return FAILURE;
715     }
716     while ((i = get_a_number(ftp, NULL)) == FTP_HAPPY_COMMENT);
717     return i;
718 }
719 
720 static int
ftp_login_session(FTP_t ftp,const char * host,int af,const char * user,const char * passwd,int port,int verbose)721 ftp_login_session(FTP_t ftp, const char *host, int af,
722                       const char *user, const char *passwd, int port, int verbose)
723 {
724     char pbuf[10];
725     struct addrinfo hints, *res, *res0;
726     int                       err;
727     int             s;
728     int                       i;
729 
730     if (networkInit() != SUCCESS)
731           return FAILURE;
732 
733     if (ftp->con_state != init) {
734           ftp_close(ftp);
735           ftp->error = -1;
736           return FAILURE;
737     }
738 
739     if (!user)
740           user = "ftp";
741 
742     if (!passwd)
743           passwd = "setup@";
744 
745     if (!port)
746           port = 21;
747 
748     if ((size_t)snprintf(pbuf, sizeof(pbuf), "%d", port) >= sizeof(pbuf))
749           return FAILURE;
750     memset(&hints, 0, sizeof(hints));
751     hints.ai_family = af;
752     hints.ai_socktype = SOCK_STREAM;
753     hints.ai_protocol = 0;
754     err = getaddrinfo(host, pbuf, &hints, &res0);
755     if (err) {
756           ftp->error = 0;
757           return FAILURE;
758     }
759 
760     s = -1;
761     for (res = res0; res; res = res->ai_next) {
762           ai_unmapped(res);
763           ftp->addrtype = res->ai_family;
764 
765           if ((s = socket(res->ai_family, res->ai_socktype,
766                               res->ai_protocol)) < 0)
767               continue;
768 
769           if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
770               close(s);
771               s = -1;
772               continue;
773           }
774 
775           break;
776     }
777     freeaddrinfo(res0);
778     if (s < 0) {
779           ftp->error = errno;
780           return FAILURE;
781     }
782 
783     ftp->fd_ctrl = s;
784     ftp->con_state = isopen;
785     ftp->is_verbose = verbose;
786 
787     i = cmd(ftp, "USER %s", user);
788     if (i >= 300 && i < 400)
789           i = cmd(ftp, "PASS %s", passwd);
790     if (i >= 299 || i < 0) {
791           ftp_close(ftp);
792           if (i > 0)
793               ftp->error = i;
794           return FAILURE;
795     }
796     return SUCCESS;
797 }
798 
799 static int
ftp_file_op(FTP_t ftp,const char * operation,const char * file,FILE ** fp,const char * mode,off_t * seekto)800 ftp_file_op(FTP_t ftp, const char *operation, const char *file, FILE **fp,
801               const char *mode, off_t *seekto)
802 {
803     int i,l,s;
804     char *q;
805     unsigned char addr[64];
806     union sockaddr_cmn {
807           struct sockaddr_in sin4;
808           struct sockaddr_in6 sin6;
809     } sin;
810     const char *cmdstr;
811 
812     if (!fp)
813           return FAILURE;
814     *fp = NULL;
815 
816     if (ftp->con_state != isopen)
817           return botch("ftp_file_op", "open");
818 
819     if ((s = socket(ftp->addrtype, SOCK_STREAM, 0)) < 0) {
820           ftp->error = errno;
821           return FAILURE;
822     }
823 
824     if (ftp->is_passive) {
825           if (ftp->addrtype == AF_INET) {
826             if (ftp->is_verbose)
827                     fprintf(stderr, "Sending PASV\n");
828               if (writes(ftp->fd_ctrl, "PASV\r\n")) {
829                     ftp_close(ftp);
830                     if (FtpTimedOut)
831                         ftp->error = FTP_TIMED_OUT;
832                     return FTP_TIMED_OUT;
833               }
834               i = get_a_number(ftp, &q);
835               if (check_code(ftp, i, FTP_PASSIVE_HAPPY)) {
836                     ftp_close(ftp);
837                     return i;
838               }
839               cmdstr = "PASV";
840           } else {
841             if (ftp->is_verbose)
842                     fprintf(stderr, "Sending EPSV\n");
843               if (writes(ftp->fd_ctrl, "EPSV\r\n")) {
844                     ftp_close(ftp);
845                     if (FtpTimedOut)
846                         ftp->error = FTP_TIMED_OUT;
847                     return FTP_TIMED_OUT;
848               }
849               i = get_a_number(ftp, &q);
850               if (check_code(ftp, i, FTP_EPASSIVE_HAPPY)) {
851                     if (ftp->is_verbose)
852                         fprintf(stderr, "Sending LPSV\n");
853                     if (writes(ftp->fd_ctrl, "LPSV\r\n")) {
854                         ftp_close(ftp);
855                         if (FtpTimedOut)
856                               ftp->error = FTP_TIMED_OUT;
857                         return FTP_TIMED_OUT;
858                     }
859                     i = get_a_number(ftp, &q);
860                     if (check_code(ftp, i, FTP_LPASSIVE_HAPPY)) {
861                         ftp_close(ftp);
862                         return i;
863                     }
864                     cmdstr = "LPSV";
865               } else
866                     cmdstr = "EPSV";
867           }
868           if (strcmp(cmdstr, "PASV") == 0 || strcmp(cmdstr, "LPSV") == 0) {
869               while (*q && !isdigit(*q))
870                     q++;
871               if (!*q) {
872                     ftp_close(ftp);
873                     return FAILURE;
874               }
875               q--;
876               l = (ftp->addrtype == AF_INET ? 6 : 21);
877               for (i = 0; i < l; i++) {
878                     q++;
879                     addr[i] = strtol(q, &q, 10);
880               }
881 
882               sin.sin4.sin_family = ftp->addrtype;
883               if (ftp->addrtype == AF_INET6) {
884                     sin.sin6.sin6_len = sizeof(struct sockaddr_in6);
885                     bcopy(addr + 2, (char *)&sin.sin6.sin6_addr, 16);
886                     bcopy(addr + 19, (char *)&sin.sin6.sin6_port, 2);
887               } else {
888                     sin.sin4.sin_len = sizeof(struct sockaddr_in);
889                     bcopy(addr, (char *)&sin.sin4.sin_addr, 4);
890                     bcopy(addr + 4, (char *)&sin.sin4.sin_port, 2);
891               }
892           } else if (strcmp(cmdstr, "EPSV") == 0) {
893               int port;
894               int sinlen;
895               while (*q && *q != '(')             /* ) */
896                     q++;
897               if (!*q) {
898                     ftp_close(ftp);
899                     return FAILURE;
900               }
901               q++;
902               if (sscanf(q, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2],
903                         &port, &addr[3]) != 5
904                || addr[0] != addr[1] || addr[0] != addr[2] || addr[0] != addr[3]) {
905                     ftp_close(ftp);
906                     return FAILURE;
907               }
908               sinlen = sizeof(sin);
909               if (getpeername(ftp->fd_ctrl, (struct sockaddr *)&sin, &sinlen) < 0) {
910                     ftp_close(ftp);
911                     return FAILURE;
912               }
913               switch (sin.sin4.sin_family) {
914               case AF_INET:
915                     sin.sin4.sin_port = htons(port);
916                     break;
917               case AF_INET6:
918                     sin.sin6.sin6_port = htons(port);
919                     break;
920               default:
921                     ftp_close(ftp);
922                     return FAILURE;
923               }
924           }
925 
926           if (connect(s, (struct sockaddr *)&sin, sin.sin4.sin_len) < 0) {
927               (void)close(s);
928               return FAILURE;
929           }
930 
931           if (seekto && *seekto) {
932               i = cmd(ftp, "REST %" PRId64, *seekto);
933               if (i < 0 || FTP_TIMEOUT(i)) {
934                     close(s);
935                     ftp->error = i;
936                     *seekto = (off_t)0;
937                     return i;
938               }
939           }
940           i = cmd(ftp, "%s %s", operation, file);
941           if (i < 0 || i > 299) {
942               close(s);
943               ftp->error = i;
944               return i;
945           }
946           *fp = fdopen(s, mode);
947     }
948     else {
949           int fd,portrange;
950 
951 #ifdef IPV6_PORTRANGE
952           if (ftp->addrtype == AF_INET6) {
953                     portrange = IPV6_PORTRANGE_HIGH;
954                     if (setsockopt(s, IPPROTO_IPV6, IPV6_PORTRANGE, (char *)
955                                      &portrange, sizeof(portrange)) < 0) {
956                               close(s);
957                               return FAILURE;
958                     }
959           }
960 #endif
961 #ifdef IP_PORTRANGE
962           if (ftp->addrtype == AF_INET) {
963                     portrange = IP_PORTRANGE_HIGH;
964                     if (setsockopt(s, IPPROTO_IP, IP_PORTRANGE, (char *)
965                                      &portrange, sizeof(portrange)) < 0) {
966                               close(s);
967                               return FAILURE;
968                     }
969           }
970 #endif
971 
972           i = sizeof sin;
973           getsockname(ftp->fd_ctrl, (struct sockaddr *)&sin, &i);
974           sin.sin4.sin_port = 0;
975           i = ((struct sockaddr *)&sin)->sa_len;
976           if (bind(s, (struct sockaddr *)&sin, i) < 0) {
977               close(s);
978               return FAILURE;
979           }
980           i = sizeof sin;
981           getsockname(s,(struct sockaddr *)&sin,&i);
982           if (listen(s, 1) < 0) {
983               close(s);
984               return FAILURE;
985           }
986           if (sin.sin4.sin_family == AF_INET) {
987             u_long a;
988               a = ntohl(sin.sin4.sin_addr.s_addr);
989               i = cmd(ftp, "PORT %ld,%ld,%ld,%ld,%d,%d",
990                         (a                   >> 24) & 0xff,
991                         (a                   >> 16) & 0xff,
992                         (a                   >>  8) & 0xff,
993                         a                           & 0xff,
994                         (ntohs(sin.sin4.sin_port) >>  8) & 0xff,
995                         ntohs(sin.sin4.sin_port)         & 0xff);
996               if (check_code(ftp, i, FTP_PORT_HAPPY)) {
997                     close(s);
998                     return i;
999               }
1000           } else {
1001 #define UC(b)       (((int)b)&0xff)
1002               char *a;
1003               char hname[INET6_ADDRSTRLEN];
1004 
1005               sin.sin6.sin6_scope_id = 0;
1006               if (getnameinfo((struct sockaddr *)&sin, sin.sin6.sin6_len,
1007                                   hname, sizeof(hname),
1008                                   NULL, 0, NI_NUMERICHOST) != 0) {
1009                     goto try_lprt;
1010               }
1011               i = cmd(ftp, "EPRT |%d|%s|%d|", 2, hname,
1012                         htons(sin.sin6.sin6_port));
1013               if (check_code(ftp, i, FTP_PORT_HAPPY)) {
1014 try_lprt:
1015                     a = (char *)&sin.sin6.sin6_addr;
1016                     i = cmd(ftp,
1017 "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
1018                               6, 16,
1019                               UC(a[0]),UC(a[1]),UC(a[2]),UC(a[3]),
1020                               UC(a[4]),UC(a[5]),UC(a[6]),UC(a[7]),
1021                               UC(a[8]),UC(a[9]),UC(a[10]),UC(a[11]),
1022                               UC(a[12]),UC(a[13]),UC(a[14]),UC(a[15]),
1023                               2,
1024                               (ntohs(sin.sin4.sin_port) >>  8) & 0xff,
1025                               ntohs(sin.sin4.sin_port)         & 0xff);
1026                     if (check_code(ftp, i, FTP_PORT_HAPPY)) {
1027                         close(s);
1028                         return i;
1029                     }
1030               }
1031           }
1032           if (seekto && *seekto) {
1033               i = cmd(ftp, "REST %" PRId64, *seekto);
1034               if (i < 0 || FTP_TIMEOUT(i)) {
1035                     close(s);
1036                     ftp->error = i;
1037                     return i;
1038               }
1039               else if (i != 350)
1040                     *seekto = (off_t)0;
1041           }
1042           i = cmd(ftp, "%s %s", operation, file);
1043           if (i < 0 || i > 299) {
1044               close(s);
1045               ftp->error = i;
1046               return FAILURE;
1047           }
1048           fd = accept(s, 0, 0);
1049           if (fd < 0) {
1050               close(s);
1051               ftp->error = 401;
1052               return FAILURE;
1053           }
1054           close(s);
1055           *fp = fdopen(fd, mode);
1056     }
1057     if (*fp)
1058           return SUCCESS;
1059     else
1060           return FAILURE;
1061 }
1062 
1063 static void
ai_unmapped(struct addrinfo * ai)1064 ai_unmapped(struct addrinfo *ai)
1065 {
1066           struct sockaddr_in6 *sin6;
1067           struct sockaddr_in sin;
1068 
1069           if (ai->ai_family != AF_INET6)
1070                     return;
1071           if (ai->ai_addrlen != sizeof(struct sockaddr_in6) ||
1072               sizeof(sin) > ai->ai_addrlen)
1073                     return;
1074           sin6 = (struct sockaddr_in6 *)ai->ai_addr;
1075           if (!IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
1076                     return;
1077 
1078           memset(&sin, 0, sizeof(sin));
1079           sin.sin_family = AF_INET;
1080           sin.sin_len = sizeof(struct sockaddr_in);
1081           memcpy(&sin.sin_addr, &sin6->sin6_addr.s6_addr[12],
1082               sizeof(sin.sin_addr));
1083           sin.sin_port = sin6->sin6_port;
1084 
1085           ai->ai_family = AF_INET;
1086           memcpy(ai->ai_addr, &sin, sin.sin_len);
1087           ai->ai_addrlen = sin.sin_len;
1088 }
1089