main.c revision 1.12 1 /* $NetBSD: main.c,v 1.12 1996/11/25 05:13:26 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 #ifndef lint
37 static char copyright[] =
38 "@(#) 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 static char rcsid[] = "$NetBSD: main.c,v 1.12 1996/11/25 05:13:26 lukem Exp $";
47 #endif
48 #endif /* not lint */
49
50 /*
51 * FTP User Program -- Command Interface.
52 */
53 /*#include <sys/ioctl.h>*/
54 #include <sys/types.h>
55 #include <sys/socket.h>
56
57 #include <arpa/ftp.h>
58
59 #include <ctype.h>
60 #include <err.h>
61 #include <netdb.h>
62 #include <pwd.h>
63 #include <signal.h>
64 #include <stdio.h>
65 #include <stdlib.h>
66 #include <string.h>
67 #include <unistd.h>
68
69 #include "ftp_var.h"
70
71 extern char *__progname; /* from crt0.o */
72
73 char *portnum; /* alternate port number */
74
75 int auto_fetch __P((int, char **));
76
77 int
78 main(argc, argv)
79 int argc;
80 char *argv[];
81 {
82 int ch, top;
83 struct passwd *pw = NULL;
84 char *cp, homedir[MAXPATHLEN];
85
86 sp = getservbyname("ftp", "tcp");
87 if (sp == 0)
88 errx(1, "ftp/tcp: unknown service");
89 doglob = 1;
90 interactive = 1;
91 autologin = 1;
92 passivemode = 0;
93 mark = HASHBYTES;
94
95 while ((ch = getopt(argc, argv, "adginpP:tv")) != EOF) {
96 switch (ch) {
97 case 'a':
98 anonftp = 1;
99 break;
100
101 case 'd':
102 options |= SO_DEBUG;
103 debug++;
104 break;
105
106 case 'g':
107 doglob = 0;
108 break;
109
110 case 'i':
111 interactive = 0;
112 break;
113
114 case 'n':
115 autologin = 0;
116 break;
117
118 case 'p':
119 passivemode = 1;
120 break;
121
122 case 'P':
123 portnum = optarg;
124 break;
125
126 case 't':
127 trace++;
128 break;
129
130 case 'v':
131 verbose++;
132 break;
133
134 default:
135 (void)fprintf(stderr,
136 "usage: %s [-adginptv] [-P port] [host [port]]\n"
137 " %s ftp://host[:port]/file\n"
138 " %s host:file\n",
139 __progname, __progname, __progname);
140 exit(1);
141 }
142 }
143 argc -= optind;
144 argv += optind;
145
146 fromatty = isatty(fileno(stdin));
147 if (fromatty)
148 verbose++;
149 cpend = 0; /* no pending replies */
150 proxy = 0; /* proxy not active */
151 crflag = 1; /* strip c.r. on ascii gets */
152 sendport = -1; /* not using ports */
153 /*
154 * Set up the home directory in case we're globbing.
155 */
156 cp = getlogin();
157 if (cp != NULL) {
158 pw = getpwnam(cp);
159 }
160 if (pw == NULL)
161 pw = getpwuid(getuid());
162 if (pw != NULL) {
163 home = homedir;
164 (void) strcpy(home, pw->pw_dir);
165 }
166
167 /* Handle "automatic" transfers. */
168 if (argc > 0 && strchr(argv[0], ':') != NULL) {
169 anonftp = 1;
170 exit(auto_fetch(argc, argv));
171 }
172
173 if (argc > 0) {
174 char *xargv[5];
175
176 if (setjmp(toplevel))
177 exit(0);
178 (void) signal(SIGINT, intr);
179 (void) signal(SIGPIPE, lostpeer);
180 xargv[0] = __progname;
181 xargv[1] = argv[0];
182 xargv[2] = argv[1];
183 xargv[3] = argv[2];
184 xargv[4] = NULL;
185 setpeer(argc+1, xargv);
186 }
187 top = setjmp(toplevel) == 0;
188 if (top) {
189 (void) signal(SIGINT, intr);
190 (void) signal(SIGPIPE, lostpeer);
191 }
192 for (;;) {
193 cmdscanner(top);
194 top = 1;
195 }
196 }
197
198 void
199 intr()
200 {
201
202 longjmp(toplevel, 1);
203 }
204
205 void
206 lostpeer()
207 {
208
209 if (connected) {
210 if (cout != NULL) {
211 (void) shutdown(fileno(cout), 1+1);
212 (void) fclose(cout);
213 cout = NULL;
214 }
215 if (data >= 0) {
216 (void) shutdown(data, 1+1);
217 (void) close(data);
218 data = -1;
219 }
220 connected = 0;
221 }
222 pswitch(1);
223 if (connected) {
224 if (cout != NULL) {
225 (void) shutdown(fileno(cout), 1+1);
226 (void) fclose(cout);
227 cout = NULL;
228 }
229 connected = 0;
230 }
231 proxflag = 0;
232 pswitch(0);
233 }
234
235 /*
236 char *
237 tail(filename)
238 char *filename;
239 {
240 char *s;
241
242 while (*filename) {
243 s = strrchr(filename, '/');
244 if (s == NULL)
245 break;
246 if (s[1])
247 return (s + 1);
248 if (s == filename)
249 break; XXX
250 *s = '\0';
251 }
252 return (filename);
253 }
254 */
255
256 /*
257 * Command parser.
258 */
259 void
260 cmdscanner(top)
261 int top;
262 {
263 struct cmd *c;
264 int l;
265
266 if (!top)
267 (void) putchar('\n');
268 for (;;) {
269 if (fromatty) {
270 printf("ftp> ");
271 (void) fflush(stdout);
272 }
273 if (fgets(line, sizeof line, stdin) == NULL)
274 quit(0, 0);
275 l = strlen(line);
276 if (l == 0)
277 break;
278 if (line[--l] == '\n') {
279 if (l == 0)
280 break;
281 line[l] = '\0';
282 } else if (l == sizeof(line) - 2) {
283 printf("sorry, input line too long\n");
284 while ((l = getchar()) != '\n' && l != EOF)
285 /* void */;
286 break;
287 } /* else it was a line without a newline */
288 makeargv();
289 if (margc == 0) {
290 continue;
291 }
292 c = getcmd(margv[0]);
293 if (c == (struct cmd *)-1) {
294 printf("?Ambiguous command\n");
295 continue;
296 }
297 if (c == 0) {
298 printf("?Invalid command\n");
299 continue;
300 }
301 if (c->c_conn && !connected) {
302 printf("Not connected.\n");
303 continue;
304 }
305 (*c->c_handler)(margc, margv);
306 if (bell && c->c_bell)
307 (void) putchar('\007');
308 if (c->c_handler != help)
309 break;
310 }
311 (void) signal(SIGINT, intr);
312 (void) signal(SIGPIPE, lostpeer);
313 }
314
315 struct cmd *
316 getcmd(name)
317 char *name;
318 {
319 char *p, *q;
320 struct cmd *c, *found;
321 int nmatches, longest;
322
323 if (name == NULL)
324 return (0);
325
326 longest = 0;
327 nmatches = 0;
328 found = 0;
329 for (c = cmdtab; (p = c->c_name) != NULL; c++) {
330 for (q = name; *q == *p++; q++)
331 if (*q == 0) /* exact match? */
332 return (c);
333 if (!*q) { /* the name was a prefix */
334 if (q - name > longest) {
335 longest = q - name;
336 nmatches = 1;
337 found = c;
338 } else if (q - name == longest)
339 nmatches++;
340 }
341 }
342 if (nmatches > 1)
343 return ((struct cmd *)-1);
344 return (found);
345 }
346
347 /*
348 * Slice a string up into argc/argv.
349 */
350
351 int slrflag;
352
353 void
354 makeargv()
355 {
356 char **argp;
357
358 argp = margv;
359 stringbase = line; /* scan from first of buffer */
360 argbase = argbuf; /* store from first of buffer */
361 slrflag = 0;
362 for (margc = 0; ; margc++) {
363 /* Expand array if necessary */
364 if (margc == margvlen) {
365 margv = (margvlen == 0)
366 ? (char **)malloc(20 * sizeof(char *))
367 : (char **)realloc(margv,
368 (margvlen + 20)*sizeof(char *));
369 if (margv == NULL)
370 errx(1, "cannot realloc argv array");
371 margvlen += 20;
372 argp = margv + margc;
373 }
374
375 if ((*argp++ = slurpstring()) == NULL)
376 break;
377 }
378
379 }
380
381 /*
382 * Parse string into argbuf;
383 * implemented with FSM to
384 * handle quoting and strings
385 */
386 char *
387 slurpstring()
388 {
389 int got_one = 0;
390 char *sb = stringbase;
391 char *ap = argbase;
392 char *tmp = argbase; /* will return this if token found */
393
394 if (*sb == '!' || *sb == '$') { /* recognize ! as a token for shell */
395 switch (slrflag) { /* and $ as token for macro invoke */
396 case 0:
397 slrflag++;
398 stringbase++;
399 return ((*sb == '!') ? "!" : "$");
400 /* NOTREACHED */
401 case 1:
402 slrflag++;
403 altarg = stringbase;
404 break;
405 default:
406 break;
407 }
408 }
409
410 S0:
411 switch (*sb) {
412
413 case '\0':
414 goto OUT;
415
416 case ' ':
417 case '\t':
418 sb++; goto S0;
419
420 default:
421 switch (slrflag) {
422 case 0:
423 slrflag++;
424 break;
425 case 1:
426 slrflag++;
427 altarg = sb;
428 break;
429 default:
430 break;
431 }
432 goto S1;
433 }
434
435 S1:
436 switch (*sb) {
437
438 case ' ':
439 case '\t':
440 case '\0':
441 goto OUT; /* end of token */
442
443 case '\\':
444 sb++; goto S2; /* slurp next character */
445
446 case '"':
447 sb++; goto S3; /* slurp quoted string */
448
449 default:
450 *ap++ = *sb++; /* add character to token */
451 got_one = 1;
452 goto S1;
453 }
454
455 S2:
456 switch (*sb) {
457
458 case '\0':
459 goto OUT;
460
461 default:
462 *ap++ = *sb++;
463 got_one = 1;
464 goto S1;
465 }
466
467 S3:
468 switch (*sb) {
469
470 case '\0':
471 goto OUT;
472
473 case '"':
474 sb++; goto S1;
475
476 default:
477 *ap++ = *sb++;
478 got_one = 1;
479 goto S3;
480 }
481
482 OUT:
483 if (got_one)
484 *ap++ = '\0';
485 argbase = ap; /* update storage pointer */
486 stringbase = sb; /* update scan pointer */
487 if (got_one) {
488 return (tmp);
489 }
490 switch (slrflag) {
491 case 0:
492 slrflag++;
493 break;
494 case 1:
495 slrflag++;
496 altarg = (char *) 0;
497 break;
498 default:
499 break;
500 }
501 return ((char *)0);
502 }
503
504 #define HELPINDENT ((int) sizeof ("directory"))
505
506 /*
507 * Help command.
508 * Call each command handler with argc == 0 and argv[0] == name.
509 */
510 void
511 help(argc, argv)
512 int argc;
513 char *argv[];
514 {
515 struct cmd *c;
516
517 if (argc == 1) {
518 int i, j, w, k;
519 int columns, width = 0, lines;
520
521 printf("Commands may be abbreviated. Commands are:\n\n");
522 for (c = cmdtab; c < &cmdtab[NCMDS]; c++) {
523 int len = strlen(c->c_name);
524
525 if (len > width)
526 width = len;
527 }
528 width = (width + 8) &~ 7;
529 columns = 80 / width;
530 if (columns == 0)
531 columns = 1;
532 lines = (NCMDS + columns - 1) / columns;
533 for (i = 0; i < lines; i++) {
534 for (j = 0; j < columns; j++) {
535 c = cmdtab + j * lines + i;
536 if (c->c_name && (!proxy || c->c_proxy)) {
537 printf("%s", c->c_name);
538 }
539 else if (c->c_name) {
540 for (k=0; k < strlen(c->c_name); k++) {
541 (void) putchar(' ');
542 }
543 }
544 if (c + lines >= &cmdtab[NCMDS]) {
545 printf("\n");
546 break;
547 }
548 w = strlen(c->c_name);
549 while (w < width) {
550 w = (w + 8) &~ 7;
551 (void) putchar('\t');
552 }
553 }
554 }
555 return;
556 }
557 while (--argc > 0) {
558 char *arg;
559 arg = *++argv;
560 c = getcmd(arg);
561 if (c == (struct cmd *)-1)
562 printf("?Ambiguous help command %s\n", arg);
563 else if (c == (struct cmd *)0)
564 printf("?Invalid help command %s\n", arg);
565 else
566 printf("%-*s\t%s\n", HELPINDENT,
567 c->c_name, c->c_help);
568 }
569 }
570
571 int
572 auto_fetch(argc, argv)
573 int argc;
574 char **argv;
575 {
576 char *xargv[5];
577 char *cp, *host, *dir, *file;
578 int rval, xargc;
579 size_t urllen;
580
581 urllen = strlen(FTPURL);
582 rval = 0;
583
584 if (setjmp(toplevel))
585 exit(0);
586 (void) signal(SIGINT, intr);
587 (void) signal(SIGPIPE, lostpeer);
588
589 /*
590 * Loop through as long as there's files to fetch.
591 */
592 while (argc > 0 && strchr(argv[0], ':') != NULL) {
593 host = dir = file = portnum = NULL;
594
595 /*
596 * We muck with the string, so we make a copy.
597 */
598 host = strdup(argv[0]);
599 if (host == NULL)
600 errx(1, "Can't allocate memory for auto-fetch.");
601
602 /*
603 * Try URL-style arguments first, then host:file.
604 */
605 if (strncmp(host, FTPURL, urllen) == 0) {
606 host += urllen;
607 cp = strchr(host, '/');
608
609 /* Look for a port number after the nost name. */
610 portnum = strchr(host, ':');
611 if (portnum != NULL)
612 *portnum++ = '\0';
613 } else
614 cp = strchr(host, ':');
615
616 /*
617 * Extract the file and (if present) directory name.
618 */
619 *cp++ = '\0';
620 dir = cp;
621 cp = strrchr(cp, '/');
622 if (cp != NULL) {
623 *cp++ = '\0';
624 file = cp;
625 } else {
626 file = dir;
627 dir = NULL;
628 }
629
630 /*
631 * Set up the connection.
632 */
633 xargv[0] = __progname;
634 xargv[1] = host;
635 xargv[2] = NULL;
636 xargc = 2;
637 if (portnum != NULL) {
638 xargv[2] = portnum;
639 xargv[3] = NULL;
640 xargc = 3;
641 }
642 setpeer(xargc, xargv);
643 if (connected == 0) {
644 warnx("Can't connect to host `%s'.", host);
645 rval = 1;
646 free(host);
647 continue;
648 }
649
650 /* Always use binary transfers. */
651 setbinary(0, NULL);
652
653 /* Change directories, if necessary. */
654 if (dir != NULL) {
655 xargv[0] = "cd";
656 xargv[1] = dir;
657 xargv[2] = NULL;
658 cd(2, xargv);
659 /* XXX SHOULD CHECK FOR ERROR CONDITION! */
660 }
661
662 /* Fetch the file. */
663 xargv[0] = "get";
664 xargv[1] = file;
665 xargv[2] = NULL;
666 get(2, xargv);
667
668 disconnect(0, NULL);
669 free(host);
670
671 --argc, ++argv;
672 }
673
674 return (rval);
675 }
676