Commit | Line | Data |
---|---|---|
a83a5d06 | 1 | /* $NetBSD: main.c,v 1.122 2012/12/22 16:57:10 christos Exp $ */ |
eedc536d PA |
2 | |
3 | /*- | |
20e59a13 | 4 | * Copyright (c) 1996-2009 The NetBSD Foundation, Inc. |
eedc536d PA |
5 | * All rights reserved. |
6 | * | |
7 | * This code is derived from software contributed to The NetBSD Foundation | |
8 | * by Luke Mewburn. | |
9 | * | |
10 | * Redistribution and use in source and binary forms, with or without | |
11 | * modification, are permitted provided that the following conditions | |
12 | * are met: | |
13 | * 1. Redistributions of source code must retain the above copyright | |
14 | * notice, this list of conditions and the following disclaimer. | |
15 | * 2. Redistributions in binary form must reproduce the above copyright | |
16 | * notice, this list of conditions and the following disclaimer in the | |
17 | * documentation and/or other materials provided with the distribution. | |
eedc536d PA |
18 | * |
19 | * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS | |
20 | * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED | |
21 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
22 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS | |
23 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
29 | * POSSIBILITY OF SUCH DAMAGE. | |
30 | */ | |
31 | ||
32 | /* | |
33 | * Copyright (c) 1985, 1989, 1993, 1994 | |
34 | * The Regents of the University of California. All rights reserved. | |
35 | * | |
36 | * Redistribution and use in source and binary forms, with or without | |
37 | * modification, are permitted provided that the following conditions | |
38 | * are met: | |
39 | * 1. Redistributions of source code must retain the above copyright | |
40 | * notice, this list of conditions and the following disclaimer. | |
41 | * 2. Redistributions in binary form must reproduce the above copyright | |
42 | * notice, this list of conditions and the following disclaimer in the | |
43 | * documentation and/or other materials provided with the distribution. | |
44 | * 3. Neither the name of the University nor the names of its contributors | |
45 | * may be used to endorse or promote products derived from this software | |
46 | * without specific prior written permission. | |
47 | * | |
48 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND | |
49 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
50 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
51 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | |
52 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
53 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
54 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
55 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
56 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
57 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
58 | * SUCH DAMAGE. | |
59 | */ | |
60 | ||
61 | /* | |
62 | * Copyright (C) 1997 and 1998 WIDE Project. | |
63 | * All rights reserved. | |
64 | * | |
65 | * Redistribution and use in source and binary forms, with or without | |
66 | * modification, are permitted provided that the following conditions | |
67 | * are met: | |
68 | * 1. Redistributions of source code must retain the above copyright | |
69 | * notice, this list of conditions and the following disclaimer. | |
70 | * 2. Redistributions in binary form must reproduce the above copyright | |
71 | * notice, this list of conditions and the following disclaimer in the | |
72 | * documentation and/or other materials provided with the distribution. | |
73 | * 3. Neither the name of the project nor the names of its contributors | |
74 | * may be used to endorse or promote products derived from this software | |
75 | * without specific prior written permission. | |
76 | * | |
77 | * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND | |
78 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
79 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
80 | * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE | |
81 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
82 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
83 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
84 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
85 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
86 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
87 | * SUCH DAMAGE. | |
88 | */ | |
89 | ||
90 | #include <sys/cdefs.h> | |
91 | #ifndef lint | |
20e59a13 PA |
92 | __COPYRIGHT("@(#) Copyright (c) 1985, 1989, 1993, 1994\ |
93 | The Regents of the University of California. All rights reserved.\ | |
94 | Copyright 1996-2008 The NetBSD Foundation, Inc. All rights reserved"); | |
eedc536d PA |
95 | #endif /* not lint */ |
96 | ||
97 | #ifndef lint | |
98 | #if 0 | |
99 | static char sccsid[] = "@(#)main.c 8.6 (Berkeley) 10/9/94"; | |
100 | #else | |
a83a5d06 | 101 | __RCSID("$NetBSD: main.c,v 1.122 2012/12/22 16:57:10 christos Exp $"); |
eedc536d PA |
102 | #endif |
103 | #endif /* not lint */ | |
104 | ||
105 | /* | |
106 | * FTP User Program -- Command Interface. | |
107 | */ | |
108 | #include <sys/types.h> | |
109 | #include <sys/socket.h> | |
110 | ||
111 | #include <err.h> | |
112 | #include <errno.h> | |
113 | #include <netdb.h> | |
114 | #include <paths.h> | |
115 | #include <pwd.h> | |
116 | #include <signal.h> | |
117 | #include <stdio.h> | |
118 | #include <stdlib.h> | |
119 | #include <string.h> | |
c106ca54 | 120 | #include <time.h> |
eedc536d PA |
121 | #include <unistd.h> |
122 | #include <locale.h> | |
123 | ||
124 | #define GLOBAL /* force GLOBAL decls in ftp_var.h to be declared */ | |
125 | #include "ftp_var.h" | |
126 | ||
127 | #define FTP_PROXY "ftp_proxy" /* env var with FTP proxy location */ | |
128 | #define HTTP_PROXY "http_proxy" /* env var with HTTP proxy location */ | |
a83a5d06 | 129 | #define HTTPS_PROXY "https_proxy" /* env var with HTTPS proxy location */ |
eedc536d PA |
130 | #define NO_PROXY "no_proxy" /* env var with list of non-proxied |
131 | * hosts, comma or space separated */ | |
132 | ||
c81ae005 | 133 | __dead static void usage(void); |
20e59a13 | 134 | static void setupoption(const char *, const char *, const char *); |
eedc536d PA |
135 | |
136 | int | |
0511850f | 137 | main(int volatile argc, char **volatile argv) |
eedc536d PA |
138 | { |
139 | int ch, rval; | |
140 | struct passwd *pw; | |
20e59a13 PA |
141 | char *cp, *ep, *anonpass, *upload_path, *src_addr; |
142 | const char *anonuser; | |
c81ae005 | 143 | int dumbterm, isupload; |
eedc536d | 144 | size_t len; |
eedc536d | 145 | |
c106ca54 | 146 | tzset(); |
eedc536d PA |
147 | setlocale(LC_ALL, ""); |
148 | setprogname(argv[0]); | |
149 | ||
150 | sigint_raised = 0; | |
151 | ||
152 | ftpport = "ftp"; | |
153 | httpport = "http"; | |
a83a5d06 PA |
154 | #ifdef WITH_SSL |
155 | httpsport = "https"; | |
156 | #endif | |
eedc536d PA |
157 | gateport = NULL; |
158 | cp = getenv("FTPSERVERPORT"); | |
159 | if (cp != NULL) | |
160 | gateport = cp; | |
161 | else | |
162 | gateport = "ftpgate"; | |
163 | doglob = 1; | |
164 | interactive = 1; | |
165 | autologin = 1; | |
166 | passivemode = 1; | |
167 | activefallback = 1; | |
168 | preserve = 1; | |
169 | verbose = 0; | |
170 | progress = 0; | |
171 | gatemode = 0; | |
172 | data = -1; | |
173 | outfile = NULL; | |
174 | restartautofetch = 0; | |
175 | #ifndef NO_EDITCOMPLETE | |
176 | editing = 0; | |
177 | el = NULL; | |
178 | hist = NULL; | |
179 | #endif | |
180 | bytes = 0; | |
181 | mark = HASHBYTES; | |
182 | rate_get = 0; | |
183 | rate_get_incr = DEFAULTINCR; | |
184 | rate_put = 0; | |
185 | rate_put_incr = DEFAULTINCR; | |
186 | #ifdef INET6 | |
187 | epsv4 = 1; | |
fa70ef36 | 188 | epsv6 = 1; |
eedc536d PA |
189 | #else |
190 | epsv4 = 0; | |
fa70ef36 | 191 | epsv6 = 0; |
eedc536d PA |
192 | #endif |
193 | epsv4bad = 0; | |
fa70ef36 | 194 | epsv6bad = 0; |
0511850f | 195 | src_addr = NULL; |
eedc536d PA |
196 | upload_path = NULL; |
197 | isupload = 0; | |
198 | reply_callback = NULL; | |
20e59a13 | 199 | #ifdef INET6 |
eedc536d | 200 | family = AF_UNSPEC; |
20e59a13 PA |
201 | #else |
202 | family = AF_INET; /* force AF_INET if no INET6 support */ | |
203 | #endif | |
eedc536d PA |
204 | |
205 | netrc[0] = '\0'; | |
206 | cp = getenv("NETRC"); | |
207 | if (cp != NULL && strlcpy(netrc, cp, sizeof(netrc)) >= sizeof(netrc)) | |
208 | errx(1, "$NETRC `%s': %s", cp, strerror(ENAMETOOLONG)); | |
209 | ||
eedc536d PA |
210 | marg_sl = ftp_sl_init(); |
211 | if ((tmpdir = getenv("TMPDIR")) == NULL) | |
212 | tmpdir = _PATH_TMP; | |
213 | ||
214 | /* Set default operation mode based on FTPMODE environment variable */ | |
215 | if ((cp = getenv("FTPMODE")) != NULL) { | |
216 | if (strcasecmp(cp, "passive") == 0) { | |
217 | passivemode = 1; | |
218 | activefallback = 0; | |
219 | } else if (strcasecmp(cp, "active") == 0) { | |
220 | passivemode = 0; | |
221 | activefallback = 0; | |
222 | } else if (strcasecmp(cp, "gate") == 0) { | |
223 | gatemode = 1; | |
224 | } else if (strcasecmp(cp, "auto") == 0) { | |
225 | passivemode = 1; | |
226 | activefallback = 1; | |
227 | } else | |
0511850f | 228 | warnx("Unknown $FTPMODE `%s'; using defaults", cp); |
eedc536d PA |
229 | } |
230 | ||
231 | if (strcmp(getprogname(), "pftp") == 0) { | |
232 | passivemode = 1; | |
233 | activefallback = 0; | |
234 | } else if (strcmp(getprogname(), "gate-ftp") == 0) | |
235 | gatemode = 1; | |
236 | ||
237 | gateserver = getenv("FTPSERVER"); | |
238 | if (gateserver == NULL || *gateserver == '\0') | |
239 | gateserver = GATE_SERVER; | |
240 | if (gatemode) { | |
241 | if (*gateserver == '\0') { | |
242 | warnx( | |
243 | "Neither $FTPSERVER nor GATE_SERVER is defined; disabling gate-ftp"); | |
244 | gatemode = 0; | |
245 | } | |
246 | } | |
247 | ||
248 | cp = getenv("TERM"); | |
249 | if (cp == NULL || strcmp(cp, "dumb") == 0) | |
250 | dumbterm = 1; | |
251 | else | |
252 | dumbterm = 0; | |
253 | fromatty = isatty(fileno(stdin)); | |
254 | ttyout = stdout; | |
255 | if (isatty(fileno(ttyout))) { | |
256 | verbose = 1; /* verbose if to a tty */ | |
257 | if (! dumbterm) { | |
258 | #ifndef NO_EDITCOMPLETE | |
259 | if (fromatty) /* editing mode on if tty is usable */ | |
260 | editing = 1; | |
261 | #endif | |
262 | #ifndef NO_PROGRESS | |
263 | if (foregroundproc()) | |
264 | progress = 1; /* progress bar on if fg */ | |
265 | #endif | |
266 | } | |
267 | } | |
268 | ||
0511850f | 269 | while ((ch = getopt(argc, argv, "46AadefginN:o:pP:q:r:Rs:tT:u:vV")) != -1) { |
eedc536d PA |
270 | switch (ch) { |
271 | case '4': | |
272 | family = AF_INET; | |
273 | break; | |
274 | ||
275 | case '6': | |
276 | #ifdef INET6 | |
277 | family = AF_INET6; | |
278 | #else | |
279 | warnx("INET6 support is not available; ignoring -6"); | |
280 | #endif | |
281 | break; | |
282 | ||
283 | case 'A': | |
284 | activefallback = 0; | |
285 | passivemode = 0; | |
286 | break; | |
287 | ||
288 | case 'a': | |
289 | anonftp = 1; | |
290 | break; | |
291 | ||
292 | case 'd': | |
293 | options |= SO_DEBUG; | |
294 | ftp_debug++; | |
295 | break; | |
296 | ||
297 | case 'e': | |
298 | #ifndef NO_EDITCOMPLETE | |
299 | editing = 0; | |
300 | #endif | |
301 | break; | |
302 | ||
303 | case 'f': | |
304 | flushcache = 1; | |
305 | break; | |
306 | ||
307 | case 'g': | |
308 | doglob = 0; | |
309 | break; | |
310 | ||
311 | case 'i': | |
312 | interactive = 0; | |
313 | break; | |
314 | ||
315 | case 'n': | |
316 | autologin = 0; | |
317 | break; | |
318 | ||
319 | case 'N': | |
320 | if (strlcpy(netrc, optarg, sizeof(netrc)) | |
321 | >= sizeof(netrc)) | |
322 | errx(1, "%s: %s", optarg, | |
323 | strerror(ENAMETOOLONG)); | |
324 | break; | |
325 | ||
326 | case 'o': | |
327 | outfile = optarg; | |
328 | if (strcmp(outfile, "-") == 0) | |
329 | ttyout = stderr; | |
330 | break; | |
331 | ||
332 | case 'p': | |
333 | passivemode = 1; | |
334 | activefallback = 0; | |
335 | break; | |
336 | ||
337 | case 'P': | |
338 | ftpport = optarg; | |
339 | break; | |
340 | ||
341 | case 'q': | |
342 | quit_time = strtol(optarg, &ep, 10); | |
343 | if (quit_time < 1 || *ep != '\0') | |
0511850f | 344 | errx(1, "Bad quit value: %s", optarg); |
eedc536d PA |
345 | break; |
346 | ||
347 | case 'r': | |
348 | retry_connect = strtol(optarg, &ep, 10); | |
349 | if (retry_connect < 1 || *ep != '\0') | |
0511850f | 350 | errx(1, "Bad retry value: %s", optarg); |
eedc536d PA |
351 | break; |
352 | ||
353 | case 'R': | |
354 | restartautofetch = 1; | |
355 | break; | |
356 | ||
0511850f PA |
357 | case 's': |
358 | src_addr = optarg; | |
359 | break; | |
360 | ||
eedc536d PA |
361 | case 't': |
362 | trace = 1; | |
363 | break; | |
364 | ||
365 | case 'T': | |
366 | { | |
367 | int targc; | |
368 | char *targv[6], *oac; | |
20e59a13 | 369 | char cmdbuf[MAX_C_NAME]; |
eedc536d PA |
370 | |
371 | /* look for `dir,max[,incr]' */ | |
372 | targc = 0; | |
20e59a13 PA |
373 | (void)strlcpy(cmdbuf, "-T", sizeof(cmdbuf)); |
374 | targv[targc++] = cmdbuf; | |
eedc536d PA |
375 | oac = ftp_strdup(optarg); |
376 | ||
377 | while ((cp = strsep(&oac, ",")) != NULL) { | |
378 | if (*cp == '\0') { | |
0511850f PA |
379 | warnx("Bad throttle value `%s'", |
380 | optarg); | |
eedc536d PA |
381 | usage(); |
382 | /* NOTREACHED */ | |
383 | } | |
384 | targv[targc++] = cp; | |
385 | if (targc >= 5) | |
386 | break; | |
387 | } | |
388 | if (parserate(targc, targv, 1) == -1) | |
389 | usage(); | |
390 | free(oac); | |
391 | break; | |
392 | } | |
393 | ||
394 | case 'u': | |
395 | { | |
396 | isupload = 1; | |
397 | interactive = 0; | |
398 | upload_path = ftp_strdup(optarg); | |
399 | ||
400 | break; | |
401 | } | |
402 | ||
403 | case 'v': | |
404 | progress = verbose = 1; | |
405 | break; | |
406 | ||
407 | case 'V': | |
408 | progress = verbose = 0; | |
409 | break; | |
410 | ||
411 | default: | |
412 | usage(); | |
413 | } | |
414 | } | |
415 | /* set line buffering on ttyout */ | |
416 | setvbuf(ttyout, NULL, _IOLBF, 0); | |
417 | argc -= optind; | |
418 | argv += optind; | |
419 | ||
420 | cpend = 0; /* no pending replies */ | |
421 | proxy = 0; /* proxy not active */ | |
422 | crflag = 1; /* strip c.r. on ascii gets */ | |
423 | sendport = -1; /* not using ports */ | |
424 | ||
0511850f PA |
425 | if (src_addr != NULL) { |
426 | struct addrinfo hints; | |
427 | int error; | |
428 | ||
429 | memset(&hints, 0, sizeof(hints)); | |
430 | hints.ai_family = family; | |
431 | hints.ai_socktype = SOCK_STREAM; | |
432 | hints.ai_flags = AI_PASSIVE; | |
433 | error = getaddrinfo(src_addr, NULL, &hints, &bindai); | |
434 | if (error) { | |
435 | errx(1, "Can't lookup `%s': %s", src_addr, | |
436 | (error == EAI_SYSTEM) ? strerror(errno) | |
437 | : gai_strerror(error)); | |
438 | } | |
439 | } | |
440 | ||
eedc536d PA |
441 | /* |
442 | * Cache the user name and home directory. | |
443 | */ | |
444 | localhome = NULL; | |
445 | localname = NULL; | |
446 | anonuser = "anonymous"; | |
447 | cp = getenv("HOME"); | |
448 | if (! EMPTYSTRING(cp)) | |
449 | localhome = ftp_strdup(cp); | |
450 | pw = NULL; | |
451 | cp = getlogin(); | |
452 | if (cp != NULL) | |
453 | pw = getpwnam(cp); | |
454 | if (pw == NULL) | |
455 | pw = getpwuid(getuid()); | |
456 | if (pw != NULL) { | |
457 | if (localhome == NULL && !EMPTYSTRING(pw->pw_dir)) | |
458 | localhome = ftp_strdup(pw->pw_dir); | |
459 | localname = ftp_strdup(pw->pw_name); | |
460 | anonuser = localname; | |
461 | } | |
462 | if (netrc[0] == '\0' && localhome != NULL) { | |
463 | if (strlcpy(netrc, localhome, sizeof(netrc)) >= sizeof(netrc) || | |
464 | strlcat(netrc, "/.netrc", sizeof(netrc)) >= sizeof(netrc)) { | |
465 | warnx("%s/.netrc: %s", localhome, | |
466 | strerror(ENAMETOOLONG)); | |
467 | netrc[0] = '\0'; | |
468 | } | |
469 | } | |
470 | if (localhome == NULL) | |
471 | localhome = ftp_strdup("/"); | |
472 | ||
473 | /* | |
474 | * Every anonymous FTP server I've encountered will accept the | |
475 | * string "username@", and will append the hostname itself. We | |
476 | * do this by default since many servers are picky about not | |
477 | * having a FQDN in the anonymous password. | |
478 | * - thorpej@NetBSD.org | |
479 | */ | |
480 | len = strlen(anonuser) + 2; | |
481 | anonpass = ftp_malloc(len); | |
482 | (void)strlcpy(anonpass, anonuser, len); | |
483 | (void)strlcat(anonpass, "@", len); | |
484 | ||
485 | /* | |
486 | * set all the defaults for options defined in | |
487 | * struct option optiontab[] declared in cmdtab.c | |
488 | */ | |
489 | setupoption("anonpass", getenv("FTPANONPASS"), anonpass); | |
490 | setupoption("ftp_proxy", getenv(FTP_PROXY), ""); | |
491 | setupoption("http_proxy", getenv(HTTP_PROXY), ""); | |
a83a5d06 | 492 | setupoption("https_proxy", getenv(HTTPS_PROXY), ""); |
eedc536d PA |
493 | setupoption("no_proxy", getenv(NO_PROXY), ""); |
494 | setupoption("pager", getenv("PAGER"), DEFAULTPAGER); | |
495 | setupoption("prompt", getenv("FTPPROMPT"), DEFAULTPROMPT); | |
496 | setupoption("rprompt", getenv("FTPRPROMPT"), DEFAULTRPROMPT); | |
497 | ||
498 | free(anonpass); | |
499 | ||
500 | setttywidth(0); | |
501 | #ifdef SIGINFO | |
502 | (void)xsignal(SIGINFO, psummary); | |
503 | #endif | |
504 | (void)xsignal(SIGQUIT, psummary); | |
505 | (void)xsignal(SIGUSR1, crankrate); | |
506 | (void)xsignal(SIGUSR2, crankrate); | |
507 | (void)xsignal(SIGWINCH, setttywidth); | |
508 | ||
eedc536d PA |
509 | if (argc > 0) { |
510 | if (isupload) { | |
511 | rval = auto_put(argc, argv, upload_path); | |
512 | sigint_or_rval_exit: | |
513 | if (sigint_raised) { | |
514 | (void)xsignal(SIGINT, SIG_DFL); | |
515 | raise(SIGINT); | |
516 | } | |
517 | exit(rval); | |
518 | } else if (strchr(argv[0], ':') != NULL | |
519 | && ! isipv6addr(argv[0])) { | |
520 | rval = auto_fetch(argc, argv); | |
521 | if (rval >= 0) /* -1 == connected and cd-ed */ | |
522 | goto sigint_or_rval_exit; | |
523 | } else { | |
20e59a13 PA |
524 | char *xargv[4], *uuser, *host; |
525 | char cmdbuf[MAXPATHLEN]; | |
eedc536d PA |
526 | |
527 | if ((rval = sigsetjmp(toplevel, 1))) | |
528 | goto sigint_or_rval_exit; | |
529 | (void)xsignal(SIGINT, intr); | |
530 | (void)xsignal(SIGPIPE, lostpeer); | |
20e59a13 | 531 | uuser = NULL; |
eedc536d PA |
532 | host = argv[0]; |
533 | cp = strchr(host, '@'); | |
534 | if (cp) { | |
535 | *cp = '\0'; | |
20e59a13 | 536 | uuser = host; |
eedc536d PA |
537 | host = cp + 1; |
538 | } | |
20e59a13 PA |
539 | (void)strlcpy(cmdbuf, getprogname(), sizeof(cmdbuf)); |
540 | xargv[0] = cmdbuf; | |
eedc536d PA |
541 | xargv[1] = host; |
542 | xargv[2] = argv[1]; | |
543 | xargv[3] = NULL; | |
544 | do { | |
545 | int oautologin; | |
546 | ||
547 | oautologin = autologin; | |
20e59a13 | 548 | if (uuser != NULL) { |
eedc536d PA |
549 | anonftp = 0; |
550 | autologin = 0; | |
551 | } | |
552 | setpeer(argc+1, xargv); | |
553 | autologin = oautologin; | |
20e59a13 PA |
554 | if (connected == 1 && uuser != NULL) |
555 | (void)ftp_login(host, uuser, NULL); | |
eedc536d PA |
556 | if (!retry_connect) |
557 | break; | |
558 | if (!connected) { | |
559 | macnum = 0; | |
560 | fprintf(ttyout, | |
561 | "Retrying in %d seconds...\n", | |
562 | retry_connect); | |
563 | sleep(retry_connect); | |
564 | } | |
565 | } while (!connected); | |
566 | retry_connect = 0; /* connected, stop hiding msgs */ | |
567 | } | |
568 | } | |
569 | if (isupload) | |
570 | usage(); | |
571 | ||
572 | #ifndef NO_EDITCOMPLETE | |
573 | controlediting(); | |
574 | #endif /* !NO_EDITCOMPLETE */ | |
575 | ||
576 | (void)sigsetjmp(toplevel, 1); | |
577 | (void)xsignal(SIGINT, intr); | |
578 | (void)xsignal(SIGPIPE, lostpeer); | |
579 | for (;;) | |
580 | cmdscanner(); | |
581 | } | |
582 | ||
583 | /* | |
584 | * Generate a prompt | |
585 | */ | |
586 | char * | |
587 | prompt(void) | |
588 | { | |
20e59a13 | 589 | static char **promptopt; |
eedc536d PA |
590 | static char buf[MAXPATHLEN]; |
591 | ||
20e59a13 | 592 | if (promptopt == NULL) { |
eedc536d PA |
593 | struct option *o; |
594 | ||
595 | o = getoption("prompt"); | |
596 | if (o == NULL) | |
0511850f | 597 | errx(1, "prompt: no such option `prompt'"); |
20e59a13 | 598 | promptopt = &(o->value); |
eedc536d | 599 | } |
20e59a13 | 600 | formatbuf(buf, sizeof(buf), *promptopt ? *promptopt : DEFAULTPROMPT); |
eedc536d PA |
601 | return (buf); |
602 | } | |
603 | ||
604 | /* | |
605 | * Generate an rprompt | |
606 | */ | |
607 | char * | |
608 | rprompt(void) | |
609 | { | |
20e59a13 | 610 | static char **rpromptopt; |
eedc536d PA |
611 | static char buf[MAXPATHLEN]; |
612 | ||
20e59a13 | 613 | if (rpromptopt == NULL) { |
eedc536d PA |
614 | struct option *o; |
615 | ||
616 | o = getoption("rprompt"); | |
617 | if (o == NULL) | |
0511850f | 618 | errx(1, "rprompt: no such option `rprompt'"); |
20e59a13 | 619 | rpromptopt = &(o->value); |
eedc536d | 620 | } |
20e59a13 | 621 | formatbuf(buf, sizeof(buf), *rpromptopt ? *rpromptopt : DEFAULTRPROMPT); |
eedc536d PA |
622 | return (buf); |
623 | } | |
624 | ||
625 | /* | |
626 | * Command parser. | |
627 | */ | |
628 | void | |
629 | cmdscanner(void) | |
630 | { | |
631 | struct cmd *c; | |
632 | char *p; | |
633 | #ifndef NO_EDITCOMPLETE | |
634 | int ch; | |
eedc536d | 635 | size_t num; |
20e59a13 PA |
636 | #endif |
637 | int len; | |
638 | char cmdbuf[MAX_C_NAME]; | |
eedc536d PA |
639 | |
640 | for (;;) { | |
641 | #ifndef NO_EDITCOMPLETE | |
642 | if (!editing) { | |
643 | #endif /* !NO_EDITCOMPLETE */ | |
644 | if (fromatty) { | |
645 | fputs(prompt(), ttyout); | |
646 | p = rprompt(); | |
647 | if (*p) | |
648 | fprintf(ttyout, "%s ", p); | |
eedc536d | 649 | } |
0511850f | 650 | (void)fflush(ttyout); |
20e59a13 PA |
651 | len = get_line(stdin, line, sizeof(line), NULL); |
652 | switch (len) { | |
eedc536d PA |
653 | case -1: /* EOF */ |
654 | case -2: /* error */ | |
655 | if (fromatty) | |
656 | putc('\n', ttyout); | |
657 | quit(0, NULL); | |
658 | /* NOTREACHED */ | |
659 | case -3: /* too long; try again */ | |
660 | fputs("Sorry, input line is too long.\n", | |
661 | ttyout); | |
662 | continue; | |
663 | case 0: /* empty; try again */ | |
664 | continue; | |
665 | default: /* all ok */ | |
666 | break; | |
667 | } | |
668 | #ifndef NO_EDITCOMPLETE | |
669 | } else { | |
670 | const char *buf; | |
671 | HistEvent ev; | |
672 | cursor_pos = NULL; | |
673 | ||
674 | buf = el_gets(el, &ch); | |
675 | num = ch; | |
676 | if (buf == NULL || num == 0) { | |
677 | if (fromatty) | |
678 | putc('\n', ttyout); | |
679 | quit(0, NULL); | |
680 | } | |
681 | if (num >= sizeof(line)) { | |
682 | fputs("Sorry, input line is too long.\n", | |
683 | ttyout); | |
684 | break; | |
685 | } | |
686 | memcpy(line, buf, num); | |
687 | if (line[--num] == '\n') { | |
688 | line[num] = '\0'; | |
689 | if (num == 0) | |
690 | break; | |
691 | } | |
692 | history(hist, &ev, H_ENTER, buf); | |
693 | } | |
694 | #endif /* !NO_EDITCOMPLETE */ | |
695 | ||
696 | makeargv(); | |
697 | if (margc == 0) | |
698 | continue; | |
699 | c = getcmd(margv[0]); | |
700 | if (c == (struct cmd *)-1) { | |
701 | fputs("?Ambiguous command.\n", ttyout); | |
702 | continue; | |
703 | } | |
704 | if (c == NULL) { | |
705 | #if !defined(NO_EDITCOMPLETE) | |
706 | /* | |
707 | * attempt to el_parse() unknown commands. | |
708 | * any command containing a ':' would be parsed | |
709 | * as "[prog:]cmd ...", and will result in a | |
710 | * false positive if prog != "ftp", so treat | |
711 | * such commands as invalid. | |
712 | */ | |
713 | if (strchr(margv[0], ':') != NULL || | |
fa70ef36 | 714 | !editing || |
c81ae005 | 715 | el_parse(el, margc, (void *)margv) != 0) |
eedc536d PA |
716 | #endif /* !NO_EDITCOMPLETE */ |
717 | fputs("?Invalid command.\n", ttyout); | |
718 | continue; | |
719 | } | |
720 | if (c->c_conn && !connected) { | |
721 | fputs("Not connected.\n", ttyout); | |
722 | continue; | |
723 | } | |
724 | confirmrest = 0; | |
20e59a13 PA |
725 | (void)strlcpy(cmdbuf, c->c_name, sizeof(cmdbuf)); |
726 | margv[0] = cmdbuf; | |
eedc536d PA |
727 | (*c->c_handler)(margc, margv); |
728 | if (bell && c->c_bell) | |
729 | (void)putc('\007', ttyout); | |
730 | if (c->c_handler != help) | |
731 | break; | |
732 | } | |
733 | (void)xsignal(SIGINT, intr); | |
734 | (void)xsignal(SIGPIPE, lostpeer); | |
735 | } | |
736 | ||
737 | struct cmd * | |
738 | getcmd(const char *name) | |
739 | { | |
740 | const char *p, *q; | |
741 | struct cmd *c, *found; | |
742 | int nmatches, longest; | |
743 | ||
744 | if (name == NULL) | |
745 | return (0); | |
746 | ||
747 | longest = 0; | |
748 | nmatches = 0; | |
749 | found = 0; | |
750 | for (c = cmdtab; (p = c->c_name) != NULL; c++) { | |
751 | for (q = name; *q == *p++; q++) | |
752 | if (*q == 0) /* exact match? */ | |
753 | return (c); | |
754 | if (!*q) { /* the name was a prefix */ | |
755 | if (q - name > longest) { | |
756 | longest = q - name; | |
757 | nmatches = 1; | |
758 | found = c; | |
759 | } else if (q - name == longest) | |
760 | nmatches++; | |
761 | } | |
762 | } | |
763 | if (nmatches > 1) | |
764 | return ((struct cmd *)-1); | |
765 | return (found); | |
766 | } | |
767 | ||
768 | /* | |
769 | * Slice a string up into argc/argv. | |
770 | */ | |
771 | ||
772 | int slrflag; | |
773 | ||
774 | void | |
775 | makeargv(void) | |
776 | { | |
777 | char *argp; | |
778 | ||
779 | stringbase = line; /* scan from first of buffer */ | |
780 | argbase = argbuf; /* store from first of buffer */ | |
781 | slrflag = 0; | |
782 | marg_sl->sl_cur = 0; /* reset to start of marg_sl */ | |
783 | for (margc = 0; ; margc++) { | |
784 | argp = slurpstring(); | |
785 | ftp_sl_add(marg_sl, argp); | |
786 | if (argp == NULL) | |
787 | break; | |
788 | } | |
789 | #ifndef NO_EDITCOMPLETE | |
790 | if (cursor_pos == line) { | |
791 | cursor_argc = 0; | |
792 | cursor_argo = 0; | |
793 | } else if (cursor_pos != NULL) { | |
794 | cursor_argc = margc; | |
795 | cursor_argo = strlen(margv[margc-1]); | |
796 | } | |
797 | #endif /* !NO_EDITCOMPLETE */ | |
798 | } | |
799 | ||
800 | #ifdef NO_EDITCOMPLETE | |
801 | #define INC_CHKCURSOR(x) (x)++ | |
802 | #else /* !NO_EDITCOMPLETE */ | |
803 | #define INC_CHKCURSOR(x) { (x)++ ; \ | |
804 | if (x == cursor_pos) { \ | |
805 | cursor_argc = margc; \ | |
806 | cursor_argo = ap-argbase; \ | |
807 | cursor_pos = NULL; \ | |
808 | } } | |
809 | ||
810 | #endif /* !NO_EDITCOMPLETE */ | |
811 | ||
812 | /* | |
813 | * Parse string into argbuf; | |
814 | * implemented with FSM to | |
815 | * handle quoting and strings | |
816 | */ | |
817 | char * | |
818 | slurpstring(void) | |
819 | { | |
20e59a13 PA |
820 | static char bangstr[2] = { '!', '\0' }; |
821 | static char dollarstr[2] = { '$', '\0' }; | |
eedc536d PA |
822 | int got_one = 0; |
823 | char *sb = stringbase; | |
824 | char *ap = argbase; | |
825 | char *tmp = argbase; /* will return this if token found */ | |
826 | ||
827 | if (*sb == '!' || *sb == '$') { /* recognize ! as a token for shell */ | |
828 | switch (slrflag) { /* and $ as token for macro invoke */ | |
829 | case 0: | |
830 | slrflag++; | |
831 | INC_CHKCURSOR(stringbase); | |
20e59a13 | 832 | return ((*sb == '!') ? bangstr : dollarstr); |
eedc536d PA |
833 | /* NOTREACHED */ |
834 | case 1: | |
835 | slrflag++; | |
836 | altarg = stringbase; | |
837 | break; | |
838 | default: | |
839 | break; | |
840 | } | |
841 | } | |
842 | ||
843 | S0: | |
844 | switch (*sb) { | |
845 | ||
846 | case '\0': | |
847 | goto OUT; | |
848 | ||
849 | case ' ': | |
850 | case '\t': | |
851 | INC_CHKCURSOR(sb); | |
852 | goto S0; | |
853 | ||
854 | default: | |
855 | switch (slrflag) { | |
856 | case 0: | |
857 | slrflag++; | |
858 | break; | |
859 | case 1: | |
860 | slrflag++; | |
861 | altarg = sb; | |
862 | break; | |
863 | default: | |
864 | break; | |
865 | } | |
866 | goto S1; | |
867 | } | |
868 | ||
869 | S1: | |
870 | switch (*sb) { | |
871 | ||
872 | case ' ': | |
873 | case '\t': | |
874 | case '\0': | |
875 | goto OUT; /* end of token */ | |
876 | ||
877 | case '\\': | |
878 | INC_CHKCURSOR(sb); | |
879 | goto S2; /* slurp next character */ | |
880 | ||
881 | case '"': | |
882 | INC_CHKCURSOR(sb); | |
883 | goto S3; /* slurp quoted string */ | |
884 | ||
885 | default: | |
886 | *ap = *sb; /* add character to token */ | |
887 | ap++; | |
888 | INC_CHKCURSOR(sb); | |
889 | got_one = 1; | |
890 | goto S1; | |
891 | } | |
892 | ||
893 | S2: | |
894 | switch (*sb) { | |
895 | ||
896 | case '\0': | |
897 | goto OUT; | |
898 | ||
899 | default: | |
900 | *ap = *sb; | |
901 | ap++; | |
902 | INC_CHKCURSOR(sb); | |
903 | got_one = 1; | |
904 | goto S1; | |
905 | } | |
906 | ||
907 | S3: | |
908 | switch (*sb) { | |
909 | ||
910 | case '\0': | |
911 | goto OUT; | |
912 | ||
913 | case '"': | |
914 | INC_CHKCURSOR(sb); | |
915 | goto S1; | |
916 | ||
917 | default: | |
918 | *ap = *sb; | |
919 | ap++; | |
920 | INC_CHKCURSOR(sb); | |
921 | got_one = 1; | |
922 | goto S3; | |
923 | } | |
924 | ||
925 | OUT: | |
926 | if (got_one) | |
927 | *ap++ = '\0'; | |
928 | argbase = ap; /* update storage pointer */ | |
929 | stringbase = sb; /* update scan pointer */ | |
930 | if (got_one) { | |
931 | return (tmp); | |
932 | } | |
933 | switch (slrflag) { | |
934 | case 0: | |
935 | slrflag++; | |
936 | break; | |
937 | case 1: | |
938 | slrflag++; | |
939 | altarg = NULL; | |
940 | break; | |
941 | default: | |
942 | break; | |
943 | } | |
944 | return (NULL); | |
945 | } | |
946 | ||
947 | /* | |
948 | * Help/usage command. | |
949 | * Call each command handler with argc == 0 and argv[0] == name. | |
950 | */ | |
951 | void | |
952 | help(int argc, char *argv[]) | |
953 | { | |
954 | struct cmd *c; | |
20e59a13 PA |
955 | char *nargv[1], *cmd; |
956 | const char *p; | |
eedc536d PA |
957 | int isusage; |
958 | ||
959 | cmd = argv[0]; | |
960 | isusage = (strcmp(cmd, "usage") == 0); | |
961 | if (argc == 0 || (isusage && argc == 1)) { | |
962 | UPRINTF("usage: %s [command [...]]\n", cmd); | |
963 | return; | |
964 | } | |
965 | if (argc == 1) { | |
966 | StringList *buf; | |
967 | ||
968 | buf = ftp_sl_init(); | |
969 | fprintf(ttyout, | |
970 | "%sommands may be abbreviated. Commands are:\n\n", | |
971 | proxy ? "Proxy c" : "C"); | |
972 | for (c = cmdtab; (p = c->c_name) != NULL; c++) | |
973 | if (!proxy || c->c_proxy) | |
20e59a13 | 974 | ftp_sl_add(buf, ftp_strdup(p)); |
eedc536d | 975 | list_vertical(buf); |
20e59a13 | 976 | sl_free(buf, 1); |
eedc536d PA |
977 | return; |
978 | } | |
979 | ||
980 | #define HELPINDENT ((int) sizeof("disconnect")) | |
981 | ||
982 | while (--argc > 0) { | |
983 | char *arg; | |
20e59a13 | 984 | char cmdbuf[MAX_C_NAME]; |
eedc536d PA |
985 | |
986 | arg = *++argv; | |
987 | c = getcmd(arg); | |
988 | if (c == (struct cmd *)-1) | |
989 | fprintf(ttyout, "?Ambiguous %s command `%s'\n", | |
990 | cmd, arg); | |
991 | else if (c == NULL) | |
992 | fprintf(ttyout, "?Invalid %s command `%s'\n", | |
993 | cmd, arg); | |
994 | else { | |
995 | if (isusage) { | |
20e59a13 PA |
996 | (void)strlcpy(cmdbuf, c->c_name, sizeof(cmdbuf)); |
997 | nargv[0] = cmdbuf; | |
eedc536d PA |
998 | (*c->c_handler)(0, nargv); |
999 | } else | |
1000 | fprintf(ttyout, "%-*s\t%s\n", HELPINDENT, | |
1001 | c->c_name, c->c_help); | |
1002 | } | |
1003 | } | |
1004 | } | |
1005 | ||
1006 | struct option * | |
1007 | getoption(const char *name) | |
1008 | { | |
1009 | const char *p; | |
1010 | struct option *c; | |
1011 | ||
1012 | if (name == NULL) | |
1013 | return (NULL); | |
1014 | for (c = optiontab; (p = c->name) != NULL; c++) { | |
1015 | if (strcasecmp(p, name) == 0) | |
1016 | return (c); | |
1017 | } | |
1018 | return (NULL); | |
1019 | } | |
1020 | ||
1021 | char * | |
1022 | getoptionvalue(const char *name) | |
1023 | { | |
1024 | struct option *c; | |
1025 | ||
1026 | if (name == NULL) | |
0511850f | 1027 | errx(1, "getoptionvalue: invoked with NULL name"); |
eedc536d PA |
1028 | c = getoption(name); |
1029 | if (c != NULL) | |
1030 | return (c->value); | |
0511850f | 1031 | errx(1, "getoptionvalue: invoked with unknown option `%s'", name); |
eedc536d PA |
1032 | /* NOTREACHED */ |
1033 | } | |
1034 | ||
1035 | static void | |
20e59a13 | 1036 | setupoption(const char *name, const char *value, const char *defaultvalue) |
eedc536d | 1037 | { |
20e59a13 | 1038 | set_option(name, value ? value : defaultvalue, 0); |
eedc536d PA |
1039 | } |
1040 | ||
1041 | void | |
1042 | usage(void) | |
1043 | { | |
1044 | const char *progname = getprogname(); | |
1045 | ||
1046 | (void)fprintf(stderr, | |
3b706bf0 | 1047 | "usage: %s [-46AadefginpRtVv] [-N netrc] [-o outfile] [-P port] [-q quittime]\n" |
0511850f PA |
1048 | " [-r retry] [-s srcaddr] [-T dir,max[,inc]]\n" |
1049 | " [[user@]host [port]] [host:path[/]] [file:///file]\n" | |
1050 | " [ftp://[user[:pass]@]host[:port]/path[/]]\n" | |
eedc536d | 1051 | " [http://[user[:pass]@]host[:port]/path] [...]\n" |
a83a5d06 PA |
1052 | #ifdef WITH_SSL |
1053 | " [https://[user[:pass]@]host[:port]/path] [...]\n" | |
1054 | #endif | |
eedc536d PA |
1055 | " %s -u URL file [...]\n", progname, progname); |
1056 | exit(1); | |
1057 | } |