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