GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/ftp/main.c Lines: 91 425 21.4 %
Date: 2017-11-07 Branches: 33 307 10.7 %

Line Branch Exec Source
1
/*	$OpenBSD: main.c,v 1.119 2017/01/24 23:47:34 beck Exp $	*/
2
/*	$NetBSD: main.c,v 1.24 1997/08/18 10:20:26 lukem Exp $	*/
3
4
/*
5
 * Copyright (C) 1997 and 1998 WIDE Project.
6
 * 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. Neither the name of the project nor the names of its contributors
17
 *    may be used to endorse or promote products derived from this software
18
 *    without specific prior written permission.
19
 *
20
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
21
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
24
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30
 * SUCH DAMAGE.
31
 */
32
33
/*
34
 * Copyright (c) 1985, 1989, 1993, 1994
35
 *	The Regents of the University of California.  All rights reserved.
36
 *
37
 * Redistribution and use in source and binary forms, with or without
38
 * modification, are permitted provided that the following conditions
39
 * are met:
40
 * 1. Redistributions of source code must retain the above copyright
41
 *    notice, this list of conditions and the following disclaimer.
42
 * 2. Redistributions in binary form must reproduce the above copyright
43
 *    notice, this list of conditions and the following disclaimer in the
44
 *    documentation and/or other materials provided with the distribution.
45
 * 3. Neither the name of the University nor the names of its contributors
46
 *    may be used to endorse or promote products derived from this software
47
 *    without specific prior written permission.
48
 *
49
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
50
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
51
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
52
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
53
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
54
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
55
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
56
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
57
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
58
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
59
 * SUCH DAMAGE.
60
 */
61
62
/*
63
 * FTP User Program -- Command Interface.
64
 */
65
#include <sys/types.h>
66
#include <sys/socket.h>
67
68
#include <ctype.h>
69
#include <err.h>
70
#include <netdb.h>
71
#include <pwd.h>
72
#include <stdio.h>
73
#include <errno.h>
74
#include <stdlib.h>
75
#include <string.h>
76
#include <unistd.h>
77
78
#include <tls.h>
79
80
#include "cmds.h"
81
#include "ftp_var.h"
82
83
int connect_timeout;
84
85
#ifndef NOSSL
86
char * const ssl_verify_opts[] = {
87
#define SSL_CAFILE	0
88
	"cafile",
89
#define SSL_CAPATH	1
90
	"capath",
91
#define SSL_CIPHERS	2
92
	"ciphers",
93
#define SSL_DONTVERIFY	3
94
	"dont",
95
#define SSL_DOVERIFY	4
96
	"do",
97
#define SSL_VERIFYDEPTH	5
98
	"depth",
99
#define SSL_MUSTSTAPLE	6
100
	"muststaple",
101
#define SSL_NOVERIFYTIME	7
102
	"noverifytime",
103
	NULL
104
};
105
106
struct tls_config *tls_config;
107
108
static void
109
process_ssl_options(char *cp)
110
{
111
	const char *errstr;
112
	long long depth;
113
	char *str;
114
115
	while (*cp) {
116
		switch (getsubopt(&cp, ssl_verify_opts, &str)) {
117
		case SSL_CAFILE:
118
			if (str == NULL)
119
				errx(1, "missing CA file");
120
			if (tls_config_set_ca_file(tls_config, str) != 0)
121
				errx(1, "tls ca file failed: %s",
122
				    tls_config_error(tls_config));
123
			break;
124
		case SSL_CAPATH:
125
			if (str == NULL)
126
				errx(1, "missing CA directory path");
127
			if (tls_config_set_ca_path(tls_config, str) != 0)
128
				errx(1, "tls ca path failed: %s",
129
				    tls_config_error(tls_config));
130
			break;
131
		case SSL_CIPHERS:
132
			if (str == NULL)
133
				errx(1, "missing cipher list");
134
			if (tls_config_set_ciphers(tls_config, str) != 0)
135
				errx(1, "tls ciphers failed: %s",
136
				    tls_config_error(tls_config));
137
			break;
138
		case SSL_DONTVERIFY:
139
			tls_config_insecure_noverifycert(tls_config);
140
			tls_config_insecure_noverifyname(tls_config);
141
			break;
142
		case SSL_DOVERIFY:
143
			tls_config_verify(tls_config);
144
			break;
145
		case SSL_VERIFYDEPTH:
146
			if (str == NULL)
147
				errx(1, "missing depth");
148
			depth = strtonum(str, 0, INT_MAX, &errstr);
149
			if (errstr)
150
				errx(1, "certificate validation depth is %s",
151
				    errstr);
152
			tls_config_set_verify_depth(tls_config, (int)depth);
153
			break;
154
		case SSL_MUSTSTAPLE:
155
			tls_config_ocsp_require_stapling(tls_config);
156
			break;
157
		case SSL_NOVERIFYTIME:
158
			tls_config_insecure_noverifytime(tls_config);
159
			break;
160
		default:
161
			errx(1, "unknown -S suboption `%s'",
162
			    suboptarg ? suboptarg : "");
163
			/* NOTREACHED */
164
		}
165
	}
166
}
167
#endif /* !NOSSL */
168
169
int family = PF_UNSPEC;
170
int pipeout;
171
172
int
173
main(volatile int argc, char *argv[])
174
{
175
	int ch, rval;
176
#ifndef SMALL
177
	int top;
178
#endif
179
	struct passwd *pw = NULL;
180
15
	char *cp, homedir[PATH_MAX];
181
	char *outfile = NULL;
182
15
	const char *errstr;
183
	int dumb_terminal = 0;
184
185
15
	ftpport = "ftp";
186
15
	httpport = "http";
187
#ifndef NOSSL
188
15
	httpsport = "https";
189
#endif /* !NOSSL */
190
15
	gateport = getenv("FTPSERVERPORT");
191

15
	if (gateport == NULL || *gateport == '\0')
192
15
		gateport = "ftpgate";
193
15
	doglob = 1;
194
15
	interactive = 1;
195
15
	autologin = 1;
196
15
	passivemode = 1;
197
15
	activefallback = 1;
198
15
	preserve = 1;
199
15
	verbose = 0;
200
15
	progress = 0;
201
15
	gatemode = 0;
202
#ifndef NOSSL
203
15
	cookiefile = NULL;
204
#endif /* NOSSL */
205
#ifndef SMALL
206
15
	editing = 0;
207
15
	el = NULL;
208
15
	hist = NULL;
209
15
	resume = 0;
210
15
	srcaddr = NULL;
211
15
	marg_sl = sl_init();
212
#endif /* !SMALL */
213
15
	mark = HASHBYTES;
214
15
	epsv4 = 1;
215
15
	epsv4bad = 0;
216
217
	/* Set default operation mode based on FTPMODE environment variable */
218

15
	if ((cp = getenv("FTPMODE")) != NULL && *cp != '\0') {
219
		if (strcmp(cp, "passive") == 0) {
220
			passivemode = 1;
221
			activefallback = 0;
222
		} else if (strcmp(cp, "active") == 0) {
223
			passivemode = 0;
224
			activefallback = 0;
225
		} else if (strcmp(cp, "gate") == 0) {
226
			gatemode = 1;
227
		} else if (strcmp(cp, "auto") == 0) {
228
			passivemode = 1;
229
			activefallback = 1;
230
		} else
231
			warnx("unknown FTPMODE: %s.  Using defaults", cp);
232
	}
233
234
15
	if (strcmp(__progname, "gate-ftp") == 0)
235
		gatemode = 1;
236
15
	gateserver = getenv("FTPSERVER");
237
15
	if (gateserver == NULL)
238
15
		gateserver = "";
239
15
	if (gatemode) {
240
		if (*gateserver == '\0') {
241
			warnx(
242
"Neither $FTPSERVER nor $GATE_SERVER is defined; disabling gate-ftp");
243
			gatemode = 0;
244
		}
245
	}
246
247
15
	cp = getenv("TERM");
248

45
	dumb_terminal = (cp == NULL || *cp == '\0' || !strcmp(cp, "dumb") ||
249
45
	    !strcmp(cp, "emacs") || !strcmp(cp, "su"));
250
45
	fromatty = isatty(fileno(stdin));
251
15
	if (fromatty) {
252
15
		verbose = 1;		/* verbose if from a tty */
253
#ifndef SMALL
254
15
		if (!dumb_terminal)
255
15
			editing = 1;	/* editing mode on if tty is usable */
256
#endif /* !SMALL */
257
	}
258
259
15
	ttyout = stdout;
260

45
	if (isatty(fileno(ttyout)) && !dumb_terminal && foregroundproc())
261
		progress = 1;		/* progress bar on if tty is usable */
262
263
#ifndef NOSSL
264
15
	cookiefile = getenv("http_cookies");
265
15
	if (tls_init() != 0)
266
		errx(1, "tls init failed");
267
15
	if (tls_config == NULL) {
268
15
		tls_config = tls_config_new();
269
15
		if (tls_config == NULL)
270
			errx(1, "tls config failed");
271
30
		if (tls_config_set_protocols(tls_config,
272
15
		    TLS_PROTOCOLS_ALL) != 0)
273
			errx(1, "tls set protocols failed: %s",
274
			    tls_config_error(tls_config));
275
15
		if (tls_config_set_ciphers(tls_config, "legacy") != 0)
276
			errx(1, "tls set ciphers failed: %s",
277
			    tls_config_error(tls_config));
278
	}
279
#endif /* !SMALL */
280
281
15
	httpuseragent = NULL;
282
283
135
	while ((ch = getopt(argc, argv,
284
60
		    "46AaCc:dD:Eegik:Mmno:pP:r:S:s:tU:vVw:")) != -1) {
285







45
		switch (ch) {
286
		case '4':
287
15
			family = PF_INET;
288
15
			break;
289
		case '6':
290
			family = PF_INET6;
291
			break;
292
		case 'A':
293
			activefallback = 0;
294
			passivemode = 0;
295
			break;
296
297
		case 'a':
298
			anonftp = 1;
299
			break;
300
301
		case 'C':
302
#ifndef SMALL
303
			resume = 1;
304
#endif /* !SMALL */
305
			break;
306
307
		case 'c':
308
#ifndef SMALL
309
			cookiefile = optarg;
310
#endif /* !SMALL */
311
			break;
312
313
		case 'D':
314
			action = optarg;
315
			break;
316
		case 'd':
317
#ifndef SMALL
318
			options |= SO_DEBUG;
319
			debug++;
320
#endif /* !SMALL */
321
			break;
322
323
		case 'E':
324
			epsv4 = 0;
325
			break;
326
327
		case 'e':
328
#ifndef SMALL
329
			editing = 0;
330
#endif /* !SMALL */
331
			break;
332
333
		case 'g':
334
			doglob = 0;
335
			break;
336
337
		case 'i':
338
			interactive = 0;
339
			break;
340
341
		case 'k':
342
			keep_alive_timeout = strtonum(optarg, 0, INT_MAX,
343
			    &errstr);
344
			if (errstr != NULL) {
345
				warnx("keep alive amount is %s: %s", errstr,
346
					optarg);
347
				usage();
348
			}
349
			break;
350
		case 'M':
351
			progress = 0;
352
			break;
353
		case 'm':
354
			progress = -1;
355
			break;
356
357
		case 'n':
358
			autologin = 0;
359
			break;
360
361
		case 'o':
362
15
			outfile = optarg;
363
15
			if (*outfile == '\0') {
364
				pipeout = 0;
365
				outfile = NULL;
366
				ttyout = stdout;
367
			} else {
368
15
				pipeout = strcmp(outfile, "-") == 0;
369
15
				ttyout = pipeout ? stderr : stdout;
370
			}
371
15
			break;
372
373
		case 'p':
374
			passivemode = 1;
375
			activefallback = 0;
376
			break;
377
378
		case 'P':
379
			ftpport = optarg;
380
			break;
381
382
		case 'r':
383
			retry_connect = strtonum(optarg, 0, INT_MAX, &errstr);
384
			if (errstr != NULL) {
385
				warnx("retry amount is %s: %s", errstr,
386
					optarg);
387
				usage();
388
			}
389
			break;
390
391
		case 'S':
392
#ifndef NOSSL
393
			process_ssl_options(optarg);
394
#endif /* !NOSSL */
395
			break;
396
397
		case 's':
398
#ifndef SMALL
399
			srcaddr = optarg;
400
#endif /* !SMALL */
401
			break;
402
403
		case 't':
404
			trace = 1;
405
			break;
406
407
#ifndef SMALL
408
		case 'U':
409
			free (httpuseragent);
410
			if (strcspn(optarg, "\r\n") != strlen(optarg))
411
				errx(1, "Invalid User-Agent: %s.", optarg);
412
			if (asprintf(&httpuseragent, "User-Agent: %s",
413
			    optarg) == -1)
414
				errx(1, "Can't allocate memory for HTTP(S) "
415
				    "User-Agent");
416
			break;
417
#endif /* !SMALL */
418
419
		case 'v':
420
15
			verbose = 1;
421
15
			break;
422
423
		case 'V':
424
			verbose = 0;
425
			break;
426
427
		case 'w':
428
			connect_timeout = strtonum(optarg, 0, 200, &errstr);
429
			if (errstr)
430
				errx(1, "-w: %s", errstr);
431
			break;
432
		default:
433
			usage();
434
		}
435
	}
436
15
	argc -= optind;
437
15
	argv += optind;
438
439
#ifndef NOSSL
440
15
	cookie_load();
441
#endif /* !NOSSL */
442
15
	if (httpuseragent == NULL)
443
15
		httpuseragent = HTTP_USER_AGENT;
444
445
15
	cpend = 0;	/* no pending replies */
446
15
	proxy = 0;	/* proxy not active */
447
15
	crflag = 1;	/* strip c.r. on ascii gets */
448
15
	sendport = -1;	/* not using ports */
449
	/*
450
	 * Set up the home directory in case we're globbing.
451
	 */
452
15
	cp = getlogin();
453
15
	if (cp != NULL) {
454
15
		pw = getpwnam(cp);
455
15
	}
456
15
	if (pw == NULL)
457
		pw = getpwuid(getuid());
458
15
	if (pw != NULL) {
459
15
		(void)strlcpy(homedir, pw->pw_dir, sizeof homedir);
460
15
		home = homedir;
461
15
	}
462
463
15
	setttywidth(0);
464
15
	(void)signal(SIGWINCH, setttywidth);
465
466
15
	if (argc > 0) {
467
15
		if (isurl(argv[0])) {
468
15
			if (pipeout) {
469
#ifndef SMALL
470
				if (pledge("stdio rpath wpath flock dns tty inet proc exec fattr",
471
				    NULL) == -1)
472
					err(1, "pledge");
473
#else
474
				if (pledge("stdio wpath rpath flock dns tty inet fattr",
475
				    NULL) == -1)
476
					err(1, "pledge");
477
#endif
478
			} else {
479
#ifndef SMALL
480
30
				if (pledge("stdio wpath rpath flock wpath cpath dns tty inet proc exec fattr",
481
15
				    NULL) == -1)
482
					err(1, "pledge");
483
#else
484
				if (pledge("stdio wpath flock rpath wpath cpath dns tty inet fattr",
485
				    NULL) == -1)
486
					err(1, "pledge");
487
#endif
488
			}
489
490
15
			rval = auto_fetch(argc, argv, outfile);
491
15
			if (rval >= 0)		/* -1 == connected and cd-ed */
492
				exit(rval);
493
		} else {
494
#ifndef SMALL
495
			char *xargv[5];
496
497
			if (setjmp(toplevel))
498
				exit(0);
499
			(void)signal(SIGINT, (sig_t)intr);
500
			(void)signal(SIGPIPE, (sig_t)lostpeer);
501
			xargv[0] = __progname;
502
			xargv[1] = argv[0];
503
			xargv[2] = argv[1];
504
			xargv[3] = argv[2];
505
			xargv[4] = NULL;
506
			do {
507
				setpeer(argc+1, xargv);
508
				if (!retry_connect)
509
					break;
510
				if (!connected) {
511
					macnum = 0;
512
					fputs("Retrying...\n", ttyout);
513
					sleep(retry_connect);
514
				}
515
			} while (!connected);
516
			retry_connect = 0; /* connected, stop hiding msgs */
517
#endif /* !SMALL */
518
		}
519
	}
520
#ifndef SMALL
521
	controlediting();
522
	top = setjmp(toplevel) == 0;
523
	if (top) {
524
		(void)signal(SIGINT, (sig_t)intr);
525
		(void)signal(SIGPIPE, (sig_t)lostpeer);
526
	}
527
	for (;;) {
528
		cmdscanner(top);
529
		top = 1;
530
	}
531
#else /* !SMALL */
532
	usage();
533
#endif /* !SMALL */
534
}
535
536
void
537
intr(void)
538
{
539
	int save_errno = errno;
540
541
	write(fileno(ttyout), "\n\r", 2);
542
	alarmtimer(0);
543
544
	errno = save_errno;
545
	longjmp(toplevel, 1);
546
}
547
548
void
549
lostpeer(void)
550
{
551
	int save_errno = errno;
552
553
	alarmtimer(0);
554
	if (connected) {
555
		if (cout != NULL) {
556
			(void)shutdown(fileno(cout), SHUT_RDWR);
557
			(void)fclose(cout);
558
			cout = NULL;
559
		}
560
		if (data >= 0) {
561
			(void)shutdown(data, SHUT_RDWR);
562
			(void)close(data);
563
			data = -1;
564
		}
565
		connected = 0;
566
	}
567
	pswitch(1);
568
	if (connected) {
569
		if (cout != NULL) {
570
			(void)shutdown(fileno(cout), SHUT_RDWR);
571
			(void)fclose(cout);
572
			cout = NULL;
573
		}
574
		connected = 0;
575
	}
576
	proxflag = 0;
577
	pswitch(0);
578
	errno = save_errno;
579
}
580
581
#ifndef SMALL
582
/*
583
 * Generate a prompt
584
 */
585
char *
586
prompt(void)
587
{
588
	return ("ftp> ");
589
}
590
591
/*
592
 * Command parser.
593
 */
594
void
595
cmdscanner(int top)
596
{
597
	struct cmd *c;
598
	int num;
599
	HistEvent hev;
600
601
	if (!top && !editing)
602
		(void)putc('\n', ttyout);
603
	for (;;) {
604
		if (!editing) {
605
			if (fromatty) {
606
				fputs(prompt(), ttyout);
607
				(void)fflush(ttyout);
608
			}
609
			if (fgets(line, sizeof(line), stdin) == NULL)
610
				quit(0, 0);
611
			num = strlen(line);
612
			if (num == 0)
613
				break;
614
			if (line[--num] == '\n') {
615
				if (num == 0)
616
					break;
617
				line[num] = '\0';
618
			} else if (num == sizeof(line) - 2) {
619
				fputs("sorry, input line too long.\n", ttyout);
620
				while ((num = getchar()) != '\n' && num != EOF)
621
					/* void */;
622
				break;
623
			} /* else it was a line without a newline */
624
		} else {
625
			const char *buf;
626
			cursor_pos = NULL;
627
628
			if ((buf = el_gets(el, &num)) == NULL || num == 0) {
629
				putc('\n', ttyout);
630
				fflush(ttyout);
631
				quit(0, 0);
632
			}
633
			if (buf[--num] == '\n') {
634
				if (num == 0)
635
					break;
636
			}
637
			if (num >= sizeof(line)) {
638
				fputs("sorry, input line too long.\n", ttyout);
639
				break;
640
			}
641
			memcpy(line, buf, (size_t)num);
642
			line[num] = '\0';
643
			history(hist, &hev, H_ENTER, buf);
644
		}
645
646
		makeargv();
647
		if (margc == 0)
648
			continue;
649
		c = getcmd(margv[0]);
650
		if (c == (struct cmd *)-1) {
651
			fputs("?Ambiguous command.\n", ttyout);
652
			continue;
653
		}
654
		if (c == 0) {
655
			/*
656
			 * Give editline(3) a shot at unknown commands.
657
			 * XXX - bogus commands with a colon in
658
			 *       them will not elicit an error.
659
			 */
660
			if (editing &&
661
			    el_parse(el, margc, (const char **)margv) != 0)
662
				fputs("?Invalid command.\n", ttyout);
663
			continue;
664
		}
665
		if (c->c_conn && !connected) {
666
			fputs("Not connected.\n", ttyout);
667
			continue;
668
		}
669
		confirmrest = 0;
670
		(*c->c_handler)(margc, margv);
671
		if (bell && c->c_bell)
672
			(void)putc('\007', ttyout);
673
		if (c->c_handler != help)
674
			break;
675
	}
676
	(void)signal(SIGINT, (sig_t)intr);
677
	(void)signal(SIGPIPE, (sig_t)lostpeer);
678
}
679
680
struct cmd *
681
getcmd(const char *name)
682
{
683
	const char *p, *q;
684
	struct cmd *c, *found;
685
	int nmatches, longest;
686
687
	if (name == NULL)
688
		return (0);
689
690
	longest = 0;
691
	nmatches = 0;
692
	found = 0;
693
	for (c = cmdtab; (p = c->c_name) != NULL; c++) {
694
		for (q = name; *q == *p++; q++)
695
			if (*q == 0)		/* exact match? */
696
				return (c);
697
		if (!*q) {			/* the name was a prefix */
698
			if (q - name > longest) {
699
				longest = q - name;
700
				nmatches = 1;
701
				found = c;
702
			} else if (q - name == longest)
703
				nmatches++;
704
		}
705
	}
706
	if (nmatches > 1)
707
		return ((struct cmd *)-1);
708
	return (found);
709
}
710
711
/*
712
 * Slice a string up into argc/argv.
713
 */
714
715
int slrflag;
716
717
void
718
makeargv(void)
719
{
720
	char *argp;
721
722
	stringbase = line;		/* scan from first of buffer */
723
	argbase = argbuf;		/* store from first of buffer */
724
	slrflag = 0;
725
	marg_sl->sl_cur = 0;		/* reset to start of marg_sl */
726
	for (margc = 0; ; margc++) {
727
		argp = slurpstring();
728
		sl_add(marg_sl, argp);
729
		if (argp == NULL)
730
			break;
731
	}
732
	if (cursor_pos == line) {
733
		cursor_argc = 0;
734
		cursor_argo = 0;
735
	} else if (cursor_pos != NULL) {
736
		cursor_argc = margc;
737
		cursor_argo = strlen(margv[margc-1]);
738
	}
739
}
740
741
#define INC_CHKCURSOR(x)	{ (x)++ ; \
742
				if (x == cursor_pos) { \
743
					cursor_argc = margc; \
744
					cursor_argo = ap-argbase; \
745
					cursor_pos = NULL; \
746
				} }
747
748
/*
749
 * Parse string into argbuf;
750
 * implemented with FSM to
751
 * handle quoting and strings
752
 */
753
char *
754
slurpstring(void)
755
{
756
	int got_one = 0;
757
	char *sb = stringbase;
758
	char *ap = argbase;
759
	char *tmp = argbase;		/* will return this if token found */
760
761
	if (*sb == '!' || *sb == '$') {	/* recognize ! as a token for shell */
762
		switch (slrflag) {	/* and $ as token for macro invoke */
763
			case 0:
764
				slrflag++;
765
				INC_CHKCURSOR(stringbase);
766
				return ((*sb == '!') ? "!" : "$");
767
				/* NOTREACHED */
768
			case 1:
769
				slrflag++;
770
				altarg = stringbase;
771
				break;
772
			default:
773
				break;
774
		}
775
	}
776
777
S0:
778
	switch (*sb) {
779
780
	case '\0':
781
		goto OUT;
782
783
	case ' ':
784
	case '\t':
785
		INC_CHKCURSOR(sb);
786
		goto S0;
787
788
	default:
789
		switch (slrflag) {
790
			case 0:
791
				slrflag++;
792
				break;
793
			case 1:
794
				slrflag++;
795
				altarg = sb;
796
				break;
797
			default:
798
				break;
799
		}
800
		goto S1;
801
	}
802
803
S1:
804
	switch (*sb) {
805
806
	case ' ':
807
	case '\t':
808
	case '\0':
809
		goto OUT;	/* end of token */
810
811
	case '\\':
812
		INC_CHKCURSOR(sb);
813
		goto S2;	/* slurp next character */
814
815
	case '"':
816
		INC_CHKCURSOR(sb);
817
		goto S3;	/* slurp quoted string */
818
819
	default:
820
		*ap = *sb;	/* add character to token */
821
		ap++;
822
		INC_CHKCURSOR(sb);
823
		got_one = 1;
824
		goto S1;
825
	}
826
827
S2:
828
	switch (*sb) {
829
830
	case '\0':
831
		goto OUT;
832
833
	default:
834
		*ap = *sb;
835
		ap++;
836
		INC_CHKCURSOR(sb);
837
		got_one = 1;
838
		goto S1;
839
	}
840
841
S3:
842
	switch (*sb) {
843
844
	case '\0':
845
		goto OUT;
846
847
	case '"':
848
		INC_CHKCURSOR(sb);
849
		goto S1;
850
851
	default:
852
		*ap = *sb;
853
		ap++;
854
		INC_CHKCURSOR(sb);
855
		got_one = 1;
856
		goto S3;
857
	}
858
859
OUT:
860
	if (got_one)
861
		*ap++ = '\0';
862
	argbase = ap;			/* update storage pointer */
863
	stringbase = sb;		/* update scan pointer */
864
	if (got_one) {
865
		return (tmp);
866
	}
867
	switch (slrflag) {
868
		case 0:
869
			slrflag++;
870
			break;
871
		case 1:
872
			slrflag++;
873
			altarg = (char *) 0;
874
			break;
875
		default:
876
			break;
877
	}
878
	return (NULL);
879
}
880
881
/*
882
 * Help command.
883
 * Call each command handler with argc == 0 and argv[0] == name.
884
 */
885
void
886
help(int argc, char *argv[])
887
{
888
	struct cmd *c;
889
890
	if (argc == 1) {
891
		StringList *buf;
892
893
		buf = sl_init();
894
		fprintf(ttyout, "%sommands may be abbreviated.  Commands are:\n\n",
895
		    proxy ? "Proxy c" : "C");
896
		for (c = cmdtab; c < &cmdtab[NCMDS]; c++)
897
			if (c->c_name && (!proxy || c->c_proxy))
898
				sl_add(buf, c->c_name);
899
		list_vertical(buf);
900
		sl_free(buf, 0);
901
		return;
902
	}
903
904
#define HELPINDENT ((int) sizeof("disconnect"))
905
906
	while (--argc > 0) {
907
		char *arg;
908
909
		arg = *++argv;
910
		c = getcmd(arg);
911
		if (c == (struct cmd *)-1)
912
			fprintf(ttyout, "?Ambiguous help command %s\n", arg);
913
		else if (c == NULL)
914
			fprintf(ttyout, "?Invalid help command %s\n", arg);
915
		else
916
			fprintf(ttyout, "%-*s\t%s\n", HELPINDENT,
917
				c->c_name, c->c_help);
918
	}
919
}
920
#endif /* !SMALL */
921
922
__dead void
923
usage(void)
924
{
925
	fprintf(stderr, "usage: "
926
#ifndef SMALL
927
	    "ftp [-46AadEegiMmnptVv] [-D title] [-k seconds] [-P port] "
928
	    "[-r seconds]\n"
929
	    "           [-s srcaddr] [host [port]]\n"
930
	    "       ftp [-C] [-o output] [-s srcaddr]\n"
931
	    "           ftp://[user:password@]host[:port]/file[/] ...\n"
932
	    "       ftp [-C] [-c cookie] [-o output] [-S ssl_options] "
933
	    "[-s srcaddr]\n"
934
	    "           [-U useragent] [-w seconds] "
935
	    "http[s]://[user:password@]host[:port]/file ...\n"
936
	    "       ftp [-C] [-o output] [-s srcaddr] file:file ...\n"
937
	    "       ftp [-C] [-o output] [-s srcaddr] host:/file[/] ...\n"
938
#else /* !SMALL */
939
	    "ftp [-o output] "
940
	    "ftp://[user:password@]host[:port]/file[/] ...\n"
941
#ifndef NOSSL
942
	    "       ftp [-o output] [-S ssl_options] [-w seconds] "
943
	    "http[s]://[user:password@]host[:port]/file ...\n"
944
#else
945
	    "       ftp [-o output] [-w seconds] http://host[:port]/file ...\n"
946
#endif /* NOSSL */
947
	    "       ftp [-o output] file:file ...\n"
948
	    "       ftp [-o output] host:/file[/] ...\n"
949
#endif /* !SMALL */
950
	    );
951
	exit(1);
952
}