Initial import from FreeBSD RELENG_4:
[dragonfly.git] / usr.bin / ftp / main.c
1 /* $FreeBSD: src/usr.bin/ftp/main.c,v 1.25.2.4 2002/08/27 09:55:08 yar Exp $    */
2 /*      $NetBSD: main.c,v 1.26 1997/10/14 16:31:22 christos Exp $       */
3
4 /*
5  * Copyright (c) 1985, 1989, 1993, 1994
6  *      The Regents of the University of California.  All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
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. All advertising materials mentioning features or use of this software
17  *    must display the following acknowledgement:
18  *      This product includes software developed by the University of
19  *      California, Berkeley and its contributors.
20  * 4. Neither the name of the University nor the names of its contributors
21  *    may be used to endorse or promote products derived from this software
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  */
36
37 #include <sys/cdefs.h>
38 #ifndef lint
39 __COPYRIGHT("@(#) Copyright (c) 1985, 1989, 1993, 1994\
40 \tThe Regents of the University of California.  All rights reserved.");
41 #endif /* not lint */
42
43 #ifndef lint
44 #if 0
45 static char sccsid[] = "@(#)main.c      8.6 (Berkeley) 10/9/94";
46 #else
47 __RCSID("$FreeBSD: src/usr.bin/ftp/main.c,v 1.25.2.4 2002/08/27 09:55:08 yar Exp $");
48 __RCSID_SOURCE("$NetBSD: main.c,v 1.26 1997/10/14 16:31:22 christos Exp $");
49 #endif
50 #endif /* not lint */
51
52 /*
53  * FTP User Program -- Command Interface.
54  */
55 #include <sys/types.h>
56 #include <sys/socket.h>
57 #include <netinet/in.h>
58 #include <arpa/inet.h>
59
60 #include <err.h>
61 #include <errno.h>
62 #include <locale.h>
63 #include <netdb.h>
64 #include <pwd.h>
65 #include <stdio.h>
66 #include <stdlib.h>
67 #include <string.h>
68 #include <unistd.h>
69
70 #include "ftp_var.h"
71 #include "pathnames.h"
72
73 int family = AF_UNSPEC;
74
75 int main __P((int, char **));
76
77 int
78 main(argc, argv)
79         int argc;
80         char *argv[];
81 {
82         int ch, top, rval;
83         struct passwd *pw = NULL;
84         char *cp, homedir[MAXPATHLEN], *s;
85         int dumbterm;
86         char *src_addr = NULL;
87
88         (void) setlocale(LC_ALL, "");
89
90         ftpport = "ftp";
91         httpport = "http";
92         gateport = NULL;
93         cp = getenv("FTPSERVERPORT");
94         if (cp != NULL)
95                 asprintf(&gateport, "%s", cp);
96         if (!gateport)
97                 asprintf(&gateport, "ftpgate");
98         doglob = 1;
99         interactive = 1;
100         autologin = 1;
101         passivemode = 0;
102         epsv4 = 1;
103         try_epsv = epsv4;       /* so status w/o connection isn't bogus */
104         restricted_data_ports = 1;
105         preserve = 1;
106         verbose = 0;
107         progress = 0;
108         gatemode = 0;
109 #ifndef SMALL
110         editing = 0;
111         el = NULL;
112         hist = NULL;
113 #endif
114         mark = HASHBYTES;
115         marg_sl = sl_init();
116         if ((tmpdir = getenv("TMPDIR")) == NULL)
117                 tmpdir = _PATH_TMP;
118
119         cp = strrchr(argv[0], '/');
120         cp = (cp == NULL) ? argv[0] : cp + 1;
121         if ((s = getenv("FTP_PASSIVE_MODE")) != NULL
122             && strcasecmp(s, "no") != 0)
123                 passivemode = 1;
124         if (strcmp(cp, "pftp") == 0)
125                 passivemode = 1;
126         else if (strcmp(cp, "gate-ftp") == 0)
127                 gatemode = 1;
128
129         gateserver = getenv("FTPSERVER");
130         if (gateserver == NULL || *gateserver == '\0')
131                 gateserver = GATE_SERVER;
132         if (gatemode) {
133                 if (*gateserver == '\0') {
134                         warnx(
135 "Neither $FTPSERVER nor GATE_SERVER is defined; disabling gate-ftp");
136                         gatemode = 0;
137                 }
138         }
139
140         cp = getenv("TERM");
141         if (cp == NULL || strcmp(cp, "dumb") == 0)
142                 dumbterm = 1;
143         else
144                 dumbterm = 0;
145         fromatty = isatty(fileno(stdin));
146         if (fromatty) {
147                 verbose = 1;            /* verbose if from a tty */
148 #ifndef SMALL
149                 if (! dumbterm)
150                         editing = 1;    /* editing mode on if tty is usable */
151 #endif
152         }
153         if (isatty(fileno(stdout)) && !dumbterm)
154                 progress = 1;           /* progress bar on if tty is usable */
155
156         while ((ch = getopt(argc, argv, "46adeginpP:s:tUvV")) != -1) {
157                 switch (ch) {
158                 case '4':
159                         family = AF_INET;
160                         break;
161 #ifdef INET6
162                 case '6':
163                         family = AF_INET6;
164                         break;
165 #endif
166                 case 'a':
167                         anonftp = 1;
168                         break;
169
170                 case 'd':
171                         options |= SO_DEBUG;
172                         debug++;
173                         break;
174
175                 case 'e':
176 #ifndef SMALL
177                         editing = 0;
178 #endif
179                         break;
180
181                 case 'g':
182                         doglob = 0;
183                         break;
184
185                 case 'i':
186                         interactive = 0;
187                         break;
188
189                 case 'n':
190                         autologin = 0;
191                         break;
192
193                 case 'p':
194                         passivemode = 1;
195                         break;
196
197                 case 'P':
198                         ftpport = optarg;
199                         break;
200
201                 case 's':
202                         dobind = 1;
203                         src_addr = optarg;
204                         break;
205
206                 case 't':
207                         trace = 1;
208                         break;
209
210                 case 'U':
211                         restricted_data_ports = 0;
212                         break;
213
214                 case 'v':
215                         verbose = 1;
216                         break;
217
218                 case 'V':
219                         verbose = 0;
220                         break;
221
222                 default:
223                         usage();
224                 }
225         }
226         argc -= optind;
227         argv += optind;
228
229         cpend = 0;      /* no pending replies */
230         proxy = 0;      /* proxy not active */
231         crflag = 1;     /* strip c.r. on ascii gets */
232         sendport = -1;  /* not using ports */
233
234         if (dobind) {
235                 struct addrinfo hints;
236                 struct addrinfo *res;
237                 int error;
238
239                 memset(&hints, 0, sizeof(hints));
240                 hints.ai_family = family;
241                 hints.ai_socktype = SOCK_STREAM;
242                 error = getaddrinfo(src_addr, NULL, &hints, &res);
243                 if (error) {
244                         warnx("%s: %s", src_addr, gai_strerror(error));
245                         if (error == EAI_SYSTEM)
246                                 warnx("%s", strerror(errno));
247                         exit(1);
248                 }
249                 bindres0 = res;
250         }
251
252         /*
253          * Set up the home directory in case we're globbing.
254          */
255         cp = getlogin();
256         if (cp != NULL) {
257                 pw = getpwnam(cp);
258         }
259         if (pw == NULL)
260                 pw = getpwuid(getuid());
261         if (pw != NULL) {
262                 home = homedir;
263                 (void)strcpy(home, pw->pw_dir);
264         }
265
266         setttywidth(0);
267         (void)signal(SIGWINCH, setttywidth);
268
269 #ifdef __GNUC__                 /* XXX: to shut up gcc warnings */
270         (void)&argc;
271         (void)&argv;
272 #endif
273
274         if (argc > 0) {
275                 if (strchr(argv[0], ':') != NULL && ! isipv6addr(argv[0])) {
276                         anonftp = 1;    /* Handle "automatic" transfers. */
277                         rval = auto_fetch(argc, argv);
278                         if (rval >= 0)          /* -1 == connected and cd-ed */
279                                 exit(rval);
280                 } else {
281                         char *xargv[4], **xargp = xargv;
282
283 #ifdef __GNUC__                 /* XXX: to shut up gcc warnings */
284                         (void)&xargp;
285 #endif
286                         if (setjmp(toplevel))
287                                 exit(0);
288                         (void)signal(SIGINT, (sig_t)intr);
289                         (void)signal(SIGPIPE, (sig_t)lostpeer);
290                         *xargp++ = __progname;
291                         *xargp++ = argv[0];             /* host */
292                         if (argc > 1)
293                                 *xargp++ = argv[1];     /* port */
294                         *xargp = NULL;
295                         setpeer(xargp-xargv, xargv);
296                 }
297         }
298 #ifndef SMALL
299         controlediting();
300 #endif /* !SMALL */
301         top = setjmp(toplevel) == 0;
302         if (top) {
303                 (void)signal(SIGINT, (sig_t)intr);
304                 (void)signal(SIGPIPE, (sig_t)lostpeer);
305         }
306         for (;;) {
307                 cmdscanner(top);
308                 top = 1;
309         }
310 }
311
312 void
313 intr()
314 {
315
316         alarmtimer(0);
317         longjmp(toplevel, 1);
318 }
319
320 void
321 lostpeer()
322 {
323
324         alarmtimer(0);
325         if (connected) {
326                 if (cout != NULL) {
327                         (void)shutdown(fileno(cout), 1+1);
328                         (void)fclose(cout);
329                         cout = NULL;
330                 }
331                 if (data >= 0) {
332                         (void)shutdown(data, 1+1);
333                         (void)close(data);
334                         data = -1;
335                 }
336                 connected = 0;
337         }
338         pswitch(1);
339         if (connected) {
340                 if (cout != NULL) {
341                         (void)shutdown(fileno(cout), 1+1);
342                         (void)fclose(cout);
343                         cout = NULL;
344                 }
345                 connected = 0;
346         }
347         proxflag = 0;
348         pswitch(0);
349 }
350
351 /*
352  * Generate a prompt
353  */
354 char *
355 prompt()
356 {
357         return ("ftp> ");
358 }
359
360 /*
361  * Command parser.
362  */
363 void
364 cmdscanner(top)
365         int top;
366 {
367         struct cmd *c;
368         int num;
369
370         if (!top 
371 #ifndef SMALL
372             && !editing
373 #endif /* !SMALL */
374             )
375                 (void)putchar('\n');
376         for (;;) {
377 #ifndef SMALL
378                 if (!editing) {
379 #endif /* !SMALL */
380                         if (fromatty) {
381                                 fputs(prompt(), stdout);
382                                 (void)fflush(stdout);
383                         }
384                         if (fgets(line, sizeof(line), stdin) == NULL)
385                                 quit(0, 0);
386                         num = strlen(line);
387                         if (num == 0)
388                                 break;
389                         if (line[--num] == '\n') {
390                                 if (num == 0)
391                                         break;
392                                 line[num] = '\0';
393                         } else if (num == sizeof(line) - 2) {
394                                 puts("sorry, input line too long.");
395                                 while ((num = getchar()) != '\n' && num != EOF)
396                                         /* void */;
397                                 break;
398                         } /* else it was a line without a newline */
399 #ifndef SMALL
400                 } else {
401                         const char *buf;
402                         cursor_pos = NULL;
403
404                         if ((buf = el_gets(el, &num)) == NULL || num == 0)
405                                 quit(0, 0);
406                         if (buf[--num] == '\n') {
407                                 if (num == 0)
408                                         break;
409                         } else if (num >= sizeof(line)) {
410                                 puts("sorry, input line too long.");
411                                 break;
412                         }
413                         memcpy(line, buf, num);
414                         line[num] = '\0';
415                         history(hist, H_ENTER, buf);
416                 }
417 #endif /* !SMALL */
418
419                 makeargv();
420                 if (margc == 0)
421                         continue;
422 #if 0 && !defined(SMALL)        /* XXX: don't want el_parse */
423                 /*
424                  * el_parse returns -1 to signal that it's not been handled
425                  * internally.
426                  */
427                 if (el_parse(el, margc, margv) != -1)
428                         continue;
429 #endif /* !SMALL */
430                 c = getcmd(margv[0]);
431                 if (c == (struct cmd *)-1) {
432                         puts("?Ambiguous command.");
433                         continue;
434                 }
435                 if (c == 0) {
436                         puts("?Invalid command.");
437                         continue;
438                 }
439                 if (c->c_conn && !connected) {
440                         puts("Not connected.");
441                         continue;
442                 }
443                 confirmrest = 0;
444                 (*c->c_handler)(margc, margv);
445                 if (bell && c->c_bell)
446                         (void)putchar('\007');
447                 if (c->c_handler != help)
448                         break;
449         }
450         (void)signal(SIGINT, (sig_t)intr);
451         (void)signal(SIGPIPE, (sig_t)lostpeer);
452 }
453
454 struct cmd *
455 getcmd(name)
456         const char *name;
457 {
458         const char *p, *q;
459         struct cmd *c, *found;
460         int nmatches, longest;
461
462         if (name == NULL)
463                 return (0);
464
465         longest = 0;
466         nmatches = 0;
467         found = 0;
468         for (c = cmdtab; (p = c->c_name) != NULL; c++) {
469                 for (q = name; *q == *p++; q++)
470                         if (*q == 0)            /* exact match? */
471                                 return (c);
472                 if (!*q) {                      /* the name was a prefix */
473                         if (q - name > longest) {
474                                 longest = q - name;
475                                 nmatches = 1;
476                                 found = c;
477                         } else if (q - name == longest)
478                                 nmatches++;
479                 }
480         }
481         if (nmatches > 1)
482                 return ((struct cmd *)-1);
483         return (found);
484 }
485
486 /*
487  * Slice a string up into argc/argv.
488  */
489
490 int slrflag;
491
492 void
493 makeargv()
494 {
495         char *argp;
496
497         stringbase = line;              /* scan from first of buffer */
498         argbase = argbuf;               /* store from first of buffer */
499         slrflag = 0;
500         marg_sl->sl_cur = 0;            /* reset to start of marg_sl */
501         for (margc = 0; ; margc++) {
502                 argp = slurpstring();
503                 sl_add(marg_sl, argp);
504                 if (argp == NULL)
505                         break;
506         }
507 #ifndef SMALL
508         if (cursor_pos == line) {
509                 cursor_argc = 0;
510                 cursor_argo = 0;
511         } else if (cursor_pos != NULL) {
512                 cursor_argc = margc;
513                 cursor_argo = strlen(margv[margc-1]);
514         }
515 #endif /* !SMALL */
516 }
517
518 #ifdef SMALL
519 #define INC_CHKCURSOR(x)        (x)++
520 #else  /* !SMALL */
521 #define INC_CHKCURSOR(x)        { (x)++ ; \
522                                 if (x == cursor_pos) { \
523                                         cursor_argc = margc; \
524                                         cursor_argo = ap-argbase; \
525                                         cursor_pos = NULL; \
526                                 } }
527                                                 
528 #endif /* !SMALL */
529
530 /*
531  * Parse string into argbuf;
532  * implemented with FSM to
533  * handle quoting and strings
534  */
535 char *
536 slurpstring()
537 {
538         int got_one = 0;
539         char *sb = stringbase;
540         char *ap = argbase;
541         char *tmp = argbase;            /* will return this if token found */
542
543         if (*sb == '!' || *sb == '$') { /* recognize ! as a token for shell */
544                 switch (slrflag) {      /* and $ as token for macro invoke */
545                         case 0:
546                                 slrflag++;
547                                 INC_CHKCURSOR(stringbase);
548                                 return ((*sb == '!') ? "!" : "$");
549                                 /* NOTREACHED */
550                         case 1:
551                                 slrflag++;
552                                 altarg = stringbase;
553                                 break;
554                         default:
555                                 break;
556                 }
557         }
558
559 S0:
560         switch (*sb) {
561
562         case '\0':
563                 goto OUT;
564
565         case ' ':
566         case '\t':
567                 INC_CHKCURSOR(sb);
568                 goto S0;
569
570         default:
571                 switch (slrflag) {
572                         case 0:
573                                 slrflag++;
574                                 break;
575                         case 1:
576                                 slrflag++;
577                                 altarg = sb;
578                                 break;
579                         default:
580                                 break;
581                 }
582                 goto S1;
583         }
584
585 S1:
586         switch (*sb) {
587
588         case ' ':
589         case '\t':
590         case '\0':
591                 goto OUT;       /* end of token */
592
593         case '\\':
594                 INC_CHKCURSOR(sb);
595                 goto S2;        /* slurp next character */
596
597         case '"':
598                 INC_CHKCURSOR(sb);
599                 goto S3;        /* slurp quoted string */
600
601         default:
602                 *ap = *sb;      /* add character to token */
603                 ap++;
604                 INC_CHKCURSOR(sb);
605                 got_one = 1;
606                 goto S1;
607         }
608
609 S2:
610         switch (*sb) {
611
612         case '\0':
613                 goto OUT;
614
615         default:
616                 *ap = *sb;
617                 ap++;
618                 INC_CHKCURSOR(sb);
619                 got_one = 1;
620                 goto S1;
621         }
622
623 S3:
624         switch (*sb) {
625
626         case '\0':
627                 goto OUT;
628
629         case '"':
630                 INC_CHKCURSOR(sb);
631                 goto S1;
632
633         default:
634                 *ap = *sb;
635                 ap++;
636                 INC_CHKCURSOR(sb);
637                 got_one = 1;
638                 goto S3;
639         }
640
641 OUT:
642         if (got_one)
643                 *ap++ = '\0';
644         argbase = ap;                   /* update storage pointer */
645         stringbase = sb;                /* update scan pointer */
646         if (got_one) {
647                 return (tmp);
648         }
649         switch (slrflag) {
650                 case 0:
651                         slrflag++;
652                         break;
653                 case 1:
654                         slrflag++;
655                         altarg = (char *) 0;
656                         break;
657                 default:
658                         break;
659         }
660         return ((char *)0);
661 }
662
663 /*
664  * Help command.
665  * Call each command handler with argc == 0 and argv[0] == name.
666  */
667 void
668 help(argc, argv)
669         int argc;
670         char *argv[];
671 {
672         struct cmd *c;
673
674         if (argc == 1) {
675                 StringList *buf;
676
677                 buf = sl_init();
678                 printf("%sommands may be abbreviated.  Commands are:\n\n",
679                     proxy ? "Proxy c" : "C");
680                 for (c = cmdtab; c < &cmdtab[NCMDS]; c++)
681                         if (c->c_name && (!proxy || c->c_proxy))
682                                 sl_add(buf, c->c_name);
683                 list_vertical(buf);
684                 sl_free(buf, 0);
685                 return;
686         }
687
688 #define HELPINDENT ((int) sizeof("disconnect"))
689
690         while (--argc > 0) {
691                 char *arg;
692
693                 arg = *++argv;
694                 c = getcmd(arg);
695                 if (c == (struct cmd *)-1)
696                         printf("?Ambiguous help command %s\n", arg);
697                 else if (c == (struct cmd *)0)
698                         printf("?Invalid help command %s\n", arg);
699                 else
700                         printf("%-*s\t%s\n", HELPINDENT,
701                                 c->c_name, c->c_help);
702         }
703 }
704
705 void
706 usage()
707 {
708         (void)fprintf(stderr,
709             "usage: %s [-46adeginptUvV] [-P port] [-s src_addr] [host [port]]\n"
710             "       %s host:path[/]\n"
711             "       %s ftp://host[:port]/path[/]\n"
712             "       %s http://host[:port]/file\n",
713             __progname, __progname, __progname, __progname);
714         exit(1);
715 }