xref: /openbsd/usr.bin/ftp/cmds.c (revision 73471bf0)
1 /*	$OpenBSD: cmds.c,v 1.84 2019/11/18 04:37:35 deraadt Exp $	*/
2 /*	$NetBSD: cmds.c,v 1.27 1997/08/18 10:20:15 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 #ifndef SMALL
63 
64 /*
65  * FTP User Program -- Command Routines.
66  */
67 #include <sys/types.h>
68 #include <sys/socket.h>
69 #include <sys/stat.h>
70 #include <sys/wait.h>
71 #include <arpa/ftp.h>
72 
73 #include <ctype.h>
74 #include <err.h>
75 #include <fnmatch.h>
76 #include <glob.h>
77 #include <netdb.h>
78 #include <stdio.h>
79 #include <stdlib.h>
80 #include <string.h>
81 #include <unistd.h>
82 #include <errno.h>
83 
84 #include "ftp_var.h"
85 #include "pathnames.h"
86 #include "cmds.h"
87 
88 /*
89  * Set ascii transfer type.
90  */
91 /*ARGSUSED*/
92 void
93 setascii(int argc, char *argv[])
94 {
95 
96 	stype[1] = "ascii";
97 	settype(2, stype);
98 }
99 
100 /*
101  * Set file transfer mode.
102  */
103 /*ARGSUSED*/
104 void
105 setftmode(int argc, char *argv[])
106 {
107 
108 	fprintf(ttyout, "We only support %s mode, sorry.\n", modename);
109 	code = -1;
110 }
111 
112 /*
113  * Set file transfer format.
114  */
115 /*ARGSUSED*/
116 void
117 setform(int argc, char *argv[])
118 {
119 
120 	fprintf(ttyout, "We only support %s format, sorry.\n", formname);
121 	code = -1;
122 }
123 
124 /*
125  * Set file transfer structure.
126  */
127 /*ARGSUSED*/
128 void
129 setstruct(int argc, char *argv[])
130 {
131 
132 	fprintf(ttyout, "We only support %s structure, sorry.\n", structname);
133 	code = -1;
134 }
135 
136 void
137 reput(int argc, char *argv[])
138 {
139 
140 	(void)putit(argc, argv, 1);
141 }
142 
143 void
144 put(int argc, char *argv[])
145 {
146 
147 	(void)putit(argc, argv, 0);
148 }
149 
150 /*
151  * Send a single file.
152  */
153 void
154 putit(int argc, char *argv[], int restartit)
155 {
156 	char *cmd;
157 	int loc = 0;
158 	char *oldargv1, *oldargv2;
159 
160 	if (argc == 2) {
161 		argc++;
162 		argv[2] = argv[1];
163 		loc++;
164 	}
165 	if (argc < 2 && !another(&argc, &argv, "local-file"))
166 		goto usage;
167 	if ((argc < 3 && !another(&argc, &argv, "remote-file")) || argc > 3) {
168 usage:
169 		fprintf(ttyout, "usage: %s local-file [remote-file]\n",
170 		    argv[0]);
171 		code = -1;
172 		return;
173 	}
174 	oldargv1 = argv[1];
175 	oldargv2 = argv[2];
176 	if (!globulize(&argv[1])) {
177 		code = -1;
178 		return;
179 	}
180 	/*
181 	 * If "globulize" modifies argv[1], and argv[2] is a copy of
182 	 * the old argv[1], make it a copy of the new argv[1].
183 	 */
184 	if (argv[1] != oldargv1 && argv[2] == oldargv1) {
185 		argv[2] = argv[1];
186 	}
187 	if (restartit == 1) {
188 		if (curtype != type)
189 			changetype(type, 0);
190 		restart_point = remotesize(argv[2], 1);
191 		if (restart_point < 0) {
192 			restart_point = 0;
193 			code = -1;
194 			return;
195 		}
196 	}
197 	if (strcmp(argv[0], "append") == 0) {
198 		restartit = 1;
199 	}
200 	cmd = restartit ? "APPE" : ((sunique) ? "STOU" : "STOR");
201 	if (loc && ntflag) {
202 		argv[2] = dotrans(argv[2]);
203 	}
204 	if (loc && mapflag) {
205 		argv[2] = domap(argv[2]);
206 	}
207 	sendrequest(cmd, argv[1], argv[2],
208 	    argv[1] != oldargv1 || argv[2] != oldargv2);
209 	restart_point = 0;
210 	if (oldargv1 != argv[1])	/* free up after globulize() */
211 		free(argv[1]);
212 }
213 
214 /*
215  * Send multiple files.
216  */
217 void
218 mput(int argc, char *argv[])
219 {
220 	extern int optind, optreset;
221 	int ch, i, restartit = 0;
222 	sig_t oldintr;
223 	char *cmd, *tp, *xargv[] = { argv[0], NULL, NULL };
224 	const char *errstr;
225 	static int depth = 0, max_depth = 0;
226 
227 	optind = optreset = 1;
228 
229 	if (depth)
230 		depth++;
231 
232 	while ((ch = getopt(argc, argv, "cd:r")) != -1) {
233 		switch(ch) {
234 		case 'c':
235 			restartit = 1;
236 			break;
237 		case 'd':
238 			max_depth = strtonum(optarg, 0, INT_MAX, &errstr);
239 			if (errstr != NULL) {
240 				fprintf(ttyout, "bad depth value, %s: %s\n",
241 				    errstr, optarg);
242 				code = -1;
243 				return;
244 			}
245 			break;
246 		case 'r':
247 			depth = 1;
248 			break;
249 		default:
250 			goto usage;
251 		}
252 	}
253 
254 	if (argc - optind < 1 && !another(&argc, &argv, "local-files")) {
255 usage:
256 		fprintf(ttyout, "usage: %s [-cr] [-d depth] local-files\n",
257 		    argv[0]);
258 		code = -1;
259 		return;
260 	}
261 
262 	argv[optind - 1] = argv[0];
263 	argc -= optind - 1;
264 	argv += optind - 1;
265 
266 	mname = argv[0];
267 	mflag = 1;
268 
269 	oldintr = signal(SIGINT, mabort);
270 	(void)setjmp(jabort);
271 	if (proxy) {
272 		char *cp, *tp2, tmpbuf[PATH_MAX];
273 
274 		while ((cp = remglob(argv, 0, NULL)) != NULL) {
275 			if (*cp == '\0') {
276 				mflag = 0;
277 				continue;
278 			}
279 			if (mflag && confirm(argv[0], cp)) {
280 				tp = cp;
281 				if (mcase) {
282 					while (*tp && !islower((unsigned char)*tp)) {
283 						tp++;
284 					}
285 					if (!*tp) {
286 						tp = cp;
287 						tp2 = tmpbuf;
288 						while ((*tp2 = *tp) != '\0') {
289 							if (isupper((unsigned char)*tp2)) {
290 								*tp2 =
291 								    tolower((unsigned char)*tp2);
292 							}
293 							tp++;
294 							tp2++;
295 						}
296 					}
297 					tp = tmpbuf;
298 				}
299 				if (ntflag) {
300 					tp = dotrans(tp);
301 				}
302 				if (mapflag) {
303 					tp = domap(tp);
304 				}
305 				if (restartit == 1) {
306 					off_t ret;
307 
308 					if (curtype != type)
309 						changetype(type, 0);
310 					ret = remotesize(tp, 0);
311 					restart_point = (ret < 0) ? 0 : ret;
312 				}
313 				cmd = restartit ? "APPE" : ((sunique) ?
314 				    "STOU" : "STOR");
315 				sendrequest(cmd, cp, tp,
316 				    cp != tp || !interactive);
317 				restart_point = 0;
318 				if (!mflag && fromatty) {
319 					if (confirm(argv[0], NULL))
320 						mflag = 1;
321 				}
322 			}
323 		}
324 		(void)signal(SIGINT, oldintr);
325 		mflag = 0;
326 		return;
327 	}
328 
329 	for (i = 1; i < argc; i++) {
330 		char **cpp;
331 		glob_t gl;
332 		int flags;
333 
334 		/* Copy files without word expansion */
335 		if (!doglob) {
336 			if (mflag && confirm(argv[0], argv[i])) {
337 				tp = (ntflag) ? dotrans(argv[i]) : argv[i];
338 				tp = (mapflag) ? domap(tp) : tp;
339 				if (restartit == 1) {
340 					off_t ret;
341 
342 					if (curtype != type)
343 						changetype(type, 0);
344 					ret = remotesize(tp, 0);
345 					restart_point = (ret < 0) ? 0 : ret;
346 				}
347 				cmd = restartit ? "APPE" : ((sunique) ?
348 				    "STOU" : "STOR");
349 				sendrequest(cmd, argv[i], tp,
350 				    tp != argv[i] || !interactive);
351 				restart_point = 0;
352 				if (!mflag && fromatty) {
353 					if (confirm(argv[0], NULL))
354 						mflag = 1;
355 				}
356 			}
357 			continue;
358 		}
359 
360 		/* expanding file names */
361 		memset(&gl, 0, sizeof(gl));
362 		flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
363 		if (glob(argv[i], flags, NULL, &gl) || gl.gl_pathc == 0) {
364 			warnx("%s: not found", argv[i]);
365 			globfree(&gl);
366 			continue;
367 		}
368 
369 		/* traverse all expanded file names */
370 		for (cpp = gl.gl_pathv; cpp && *cpp != NULL; cpp++) {
371 			struct stat filestat;
372 
373 			if (!mflag)
374 				continue;
375 			if (stat(*cpp, &filestat) != 0) {
376 				warn("local: %s", *cpp);
377 				continue;
378 			}
379 			if (S_ISDIR(filestat.st_mode) && depth == max_depth)
380 				continue;
381 			if (!confirm(argv[0], *cpp))
382 				continue;
383 
384 			/*
385 			 * If file is a directory then create a new one
386 			 * at the remote machine.
387 			 */
388 			if (S_ISDIR(filestat.st_mode)) {
389 				xargv[1] = *cpp;
390 				makedir(2, xargv);
391 				cd(2, xargv);
392 				if (dirchange != 1) {
393 					warnx("remote: %s", *cpp);
394 					continue;
395 				}
396 
397 				if (chdir(*cpp) != 0) {
398 					warn("local: %s", *cpp);
399 					goto out;
400 				}
401 
402 				/* Copy the whole directory recursively. */
403 				xargv[1] = "*";
404 				mput(2, xargv);
405 
406 				if (chdir("..") != 0) {
407 					mflag = 0;
408 					warn("local: %s", *cpp);
409 					goto out;
410 				}
411 
412  out:
413 				xargv[1] = "..";
414 				cd(2, xargv);
415 				if (dirchange != 1) {
416 					warnx("remote: %s", *cpp);
417 					mflag = 0;
418 				}
419 				continue;
420 			}
421 
422 			tp = (ntflag) ? dotrans(*cpp) : *cpp;
423 			tp = (mapflag) ? domap(tp) : tp;
424 			if (restartit == 1) {
425 				off_t ret;
426 
427 				if (curtype != type)
428 					changetype(type, 0);
429 				ret = remotesize(tp, 0);
430 				restart_point = (ret < 0) ? 0 : ret;
431 			}
432 			cmd = restartit ? "APPE" : ((sunique) ?
433 			    "STOU" : "STOR");
434 			sendrequest(cmd, *cpp, tp,
435 			    *cpp != tp || !interactive);
436 			restart_point = 0;
437 			if (!mflag && fromatty) {
438 				if (confirm(argv[0], NULL))
439 					mflag = 1;
440 			}
441 		}
442 		globfree(&gl);
443 	}
444 
445 	(void)signal(SIGINT, oldintr);
446 
447 	if (depth)
448 		depth--;
449 	if (depth == 0 || mflag == 0)
450 		depth = max_depth = mflag = 0;
451 }
452 
453 void
454 reget(int argc, char *argv[])
455 {
456 
457 	(void)getit(argc, argv, 1, "a+w");
458 }
459 
460 char *
461 onoff(int bool)
462 {
463 
464 	return (bool ? "on" : "off");
465 }
466 
467 /*
468  * Show status.
469  */
470 /*ARGSUSED*/
471 void
472 status(int argc, char *argv[])
473 {
474 	int i;
475 
476 	if (connected)
477 		fprintf(ttyout, "Connected %sto %s.\n",
478 		    connected == -1 ? "and logged in" : "", hostname);
479 	else
480 		fputs("Not connected.\n", ttyout);
481 	if (!proxy) {
482 		pswitch(1);
483 		if (connected) {
484 			fprintf(ttyout, "Connected for proxy commands to %s.\n",
485 			    hostname);
486 		}
487 		else {
488 			fputs("No proxy connection.\n", ttyout);
489 		}
490 		pswitch(0);
491 	}
492 	fprintf(ttyout, "Gate ftp: %s, server %s, port %s.\n", onoff(gatemode),
493 	    *gateserver ? gateserver : "(none)", gateport);
494 	fprintf(ttyout, "Passive mode: %s.\n", onoff(passivemode));
495 	fprintf(ttyout, "Mode: %s; Type: %s; Form: %s; Structure: %s.\n",
496 		modename, typename, formname, structname);
497 	fprintf(ttyout, "Verbose: %s; Bell: %s; Prompting: %s; Globbing: %s.\n",
498 		onoff(verbose), onoff(bell), onoff(interactive),
499 		onoff(doglob));
500 	fprintf(ttyout, "Store unique: %s; Receive unique: %s.\n", onoff(sunique),
501 		onoff(runique));
502 	fprintf(ttyout, "Preserve modification times: %s.\n", onoff(preserve));
503 	fprintf(ttyout, "Case: %s; CR stripping: %s.\n", onoff(mcase), onoff(crflag));
504 	if (ntflag) {
505 		fprintf(ttyout, "Ntrans: (in) %s (out) %s\n", ntin, ntout);
506 	}
507 	else {
508 		fputs("Ntrans: off.\n", ttyout);
509 	}
510 	if (mapflag) {
511 		fprintf(ttyout, "Nmap: (in) %s (out) %s\n", mapin, mapout);
512 	}
513 	else {
514 		fputs("Nmap: off.\n", ttyout);
515 	}
516 	fprintf(ttyout, "Hash mark printing: %s; Mark count: %d; Progress bar: %s.\n",
517 	    onoff(hash), mark, onoff(progress));
518 	fprintf(ttyout, "Use of PORT/LPRT cmds: %s.\n", onoff(sendport));
519 	fprintf(ttyout, "Use of EPSV/EPRT cmds for IPv4: %s%s.\n", onoff(epsv4),
520 	    epsv4bad ? " (disabled for this connection)" : "");
521 	fprintf(ttyout, "Command line editing: %s.\n", onoff(editing));
522 	if (macnum > 0) {
523 		fputs("Macros:\n", ttyout);
524 		for (i=0; i<macnum; i++) {
525 			fprintf(ttyout, "\t%s\n", macros[i].mac_name);
526 		}
527 	}
528 	code = 0;
529 }
530 
531 /*
532  * Toggle a variable
533  */
534 int
535 togglevar(int argc, char *argv[], int *var, const char *mesg)
536 {
537 	if (argc < 2) {
538 		*var = !*var;
539 	} else if (argc == 2 && strcasecmp(argv[1], "on") == 0) {
540 		*var = 1;
541 	} else if (argc == 2 && strcasecmp(argv[1], "off") == 0) {
542 		*var = 0;
543 	} else {
544 		fprintf(ttyout, "usage: %s [on | off]\n", argv[0]);
545 		return (-1);
546 	}
547 	if (mesg)
548 		fprintf(ttyout, "%s %s.\n", mesg, onoff(*var));
549 	return (*var);
550 }
551 
552 /*
553  * Set beep on cmd completed mode.
554  */
555 /*ARGSUSED*/
556 void
557 setbell(int argc, char *argv[])
558 {
559 
560 	code = togglevar(argc, argv, &bell, "Bell mode");
561 }
562 
563 /*
564  * Set command line editing
565  */
566 /*ARGSUSED*/
567 void
568 setedit(int argc, char *argv[])
569 {
570 
571 	code = togglevar(argc, argv, &editing, "Editing mode");
572 	controlediting();
573 }
574 
575 /*
576  * Toggle use of IPv4 EPSV/EPRT
577  */
578 /*ARGSUSED*/
579 void
580 setepsv4(int argc, char *argv[])
581 {
582 
583 	code = togglevar(argc, argv, &epsv4, "EPSV/EPRT on IPv4");
584 	epsv4bad = 0;
585 }
586 
587 /*
588  * Turn on packet tracing.
589  */
590 /*ARGSUSED*/
591 void
592 settrace(int argc, char *argv[])
593 {
594 
595 	code = togglevar(argc, argv, &trace, "Packet tracing");
596 }
597 
598 /*
599  * Toggle hash mark printing during transfers, or set hash mark bytecount.
600  */
601 /*ARGSUSED*/
602 void
603 sethash(int argc, char *argv[])
604 {
605 	if (argc == 1)
606 		hash = !hash;
607 	else if (argc != 2) {
608 		fprintf(ttyout, "usage: %s [on | off | size]\n", argv[0]);
609 		code = -1;
610 		return;
611 	} else if (strcasecmp(argv[1], "on") == 0)
612 		hash = 1;
613 	else if (strcasecmp(argv[1], "off") == 0)
614 		hash = 0;
615 	else {
616 		int nmark;
617 		const char *errstr;
618 
619 		nmark = strtonum(argv[1], 1, INT_MAX, &errstr);
620 		if (errstr) {
621 			fprintf(ttyout, "bytecount value is %s: %s\n",
622 			    errstr, argv[1]);
623 			code = -1;
624 			return;
625 		}
626 		mark = nmark;
627 		hash = 1;
628 	}
629 	fprintf(ttyout, "Hash mark printing %s", onoff(hash));
630 	if (hash)
631 		fprintf(ttyout, " (%d bytes/hash mark)", mark);
632 	fputs(".\n", ttyout);
633 	code = hash;
634 }
635 
636 /*
637  * Turn on printing of server echo's.
638  */
639 /*ARGSUSED*/
640 void
641 setverbose(int argc, char *argv[])
642 {
643 
644 	code = togglevar(argc, argv, &verbose, "Verbose mode");
645 }
646 
647 /*
648  * Toggle PORT/LPRT cmd use before each data connection.
649  */
650 /*ARGSUSED*/
651 void
652 setport(int argc, char *argv[])
653 {
654 
655 	code = togglevar(argc, argv, &sendport, "Use of PORT/LPRT cmds");
656 }
657 
658 /*
659  * Toggle transfer progress bar.
660  */
661 /*ARGSUSED*/
662 void
663 setprogress(int argc, char *argv[])
664 {
665 
666 	code = togglevar(argc, argv, &progress, "Progress bar");
667 }
668 
669 /*
670  * Turn on interactive prompting during mget, mput, and mdelete.
671  */
672 /*ARGSUSED*/
673 void
674 setprompt(int argc, char *argv[])
675 {
676 
677 	code = togglevar(argc, argv, &interactive, "Interactive mode");
678 }
679 
680 /*
681  * Toggle gate-ftp mode, or set gate-ftp server
682  */
683 /*ARGSUSED*/
684 void
685 setgate(int argc, char *argv[])
686 {
687 	static char gsbuf[HOST_NAME_MAX+1];
688 
689 	if (argc > 3) {
690 		fprintf(ttyout, "usage: %s [on | off | host [port]]\n",
691 		    argv[0]);
692 		code = -1;
693 		return;
694 	} else if (argc < 2) {
695 		gatemode = !gatemode;
696 	} else {
697 		if (argc == 2 && strcasecmp(argv[1], "on") == 0)
698 			gatemode = 1;
699 		else if (argc == 2 && strcasecmp(argv[1], "off") == 0)
700 			gatemode = 0;
701 		else {
702 			if (argc == 3) {
703 				gateport = strdup(argv[2]);
704 				if (gateport == NULL)
705 					err(1, NULL);
706 			}
707 			strlcpy(gsbuf, argv[1], sizeof(gsbuf));
708 			gateserver = gsbuf;
709 			gatemode = 1;
710 		}
711 	}
712 	if (gatemode && (gateserver == NULL || *gateserver == '\0')) {
713 		fprintf(ttyout,
714 		    "Disabling gate-ftp mode - no gate-ftp server defined.\n");
715 		gatemode = 0;
716 	} else {
717 		fprintf(ttyout, "Gate ftp: %s, server %s, port %s.\n",
718 		    onoff(gatemode),
719 		    *gateserver ? gateserver : "(none)", gateport);
720 	}
721 	code = gatemode;
722 }
723 
724 /*
725  * Toggle metacharacter interpretation on local file names.
726  */
727 /*ARGSUSED*/
728 void
729 setglob(int argc, char *argv[])
730 {
731 
732 	code = togglevar(argc, argv, &doglob, "Globbing");
733 }
734 
735 /*
736  * Toggle preserving modification times on retrieved files.
737  */
738 /*ARGSUSED*/
739 void
740 setpreserve(int argc, char *argv[])
741 {
742 
743 	code = togglevar(argc, argv, &preserve, "Preserve modification times");
744 }
745 
746 /*
747  * Set debugging mode on/off and/or set level of debugging.
748  */
749 /*ARGSUSED*/
750 void
751 setdebug(int argc, char *argv[])
752 {
753 	if (argc > 2) {
754 		fprintf(ttyout, "usage: %s [on | off | debuglevel]\n", argv[0]);
755 		code = -1;
756 		return;
757 	} else if (argc == 2) {
758 		if (strcasecmp(argv[1], "on") == 0)
759 			debug = 1;
760 		else if (strcasecmp(argv[1], "off") == 0)
761 			debug = 0;
762 		else {
763 			const char *errstr;
764 			int val;
765 
766 			val = strtonum(argv[1], 0, INT_MAX, &errstr);
767 			if (errstr) {
768 				fprintf(ttyout, "debugging value is %s: %s\n",
769 				    errstr, argv[1]);
770 				code = -1;
771 				return;
772 			}
773 			debug = val;
774 		}
775 	} else
776 		debug = !debug;
777 	if (debug)
778 		options |= SO_DEBUG;
779 	else
780 		options &= ~SO_DEBUG;
781 	fprintf(ttyout, "Debugging %s (debug=%d).\n", onoff(debug), debug);
782 	code = debug > 0;
783 }
784 
785 /*
786  * Set current working directory on local machine.
787  */
788 void
789 lcd(int argc, char *argv[])
790 {
791 	char buf[PATH_MAX];
792 	char *oldargv1;
793 
794 	if (argc < 2)
795 		argc++, argv[1] = home;
796 	if (argc != 2) {
797 		fprintf(ttyout, "usage: %s [local-directory]\n", argv[0]);
798 		code = -1;
799 		return;
800 	}
801 	oldargv1 = argv[1];
802 	if (!globulize(&argv[1])) {
803 		code = -1;
804 		return;
805 	}
806 	if (chdir(argv[1]) == -1) {
807 		warn("local: %s", argv[1]);
808 		code = -1;
809 	} else {
810 		if (getcwd(buf, sizeof(buf)) != NULL)
811 			fprintf(ttyout, "Local directory now %s\n", buf);
812 		else
813 			warn("getcwd: %s", argv[1]);
814 		code = 0;
815 	}
816 	if (oldargv1 != argv[1])	/* free up after globulize() */
817 		free(argv[1]);
818 }
819 
820 /*
821  * Delete a single file.
822  */
823 void
824 deletecmd(int argc, char *argv[])
825 {
826 
827 	if ((argc < 2 && !another(&argc, &argv, "remote-file")) || argc > 2) {
828 		fprintf(ttyout, "usage: %s remote-file\n", argv[0]);
829 		code = -1;
830 		return;
831 	}
832 	(void)command("DELE %s", argv[1]);
833 }
834 
835 /*
836  * Delete multiple files.
837  */
838 void
839 mdelete(int argc, char *argv[])
840 {
841 	sig_t oldintr;
842 	char *cp;
843 
844 	if (argc < 2 && !another(&argc, &argv, "remote-files")) {
845 		fprintf(ttyout, "usage: %s remote-files\n", argv[0]);
846 		code = -1;
847 		return;
848 	}
849 	mname = argv[0];
850 	mflag = 1;
851 	oldintr = signal(SIGINT, mabort);
852 	(void)setjmp(jabort);
853 	while ((cp = remglob(argv, 0, NULL)) != NULL) {
854 		if (*cp == '\0') {
855 			mflag = 0;
856 			continue;
857 		}
858 		if (mflag && confirm(argv[0], cp)) {
859 			(void)command("DELE %s", cp);
860 			if (!mflag && fromatty) {
861 				if (confirm(argv[0], NULL))
862 					mflag = 1;
863 			}
864 		}
865 	}
866 	(void)signal(SIGINT, oldintr);
867 	mflag = 0;
868 }
869 
870 /*
871  * Rename a remote file.
872  */
873 void
874 renamefile(int argc, char *argv[])
875 {
876 
877 	if (argc < 2 && !another(&argc, &argv, "from-name"))
878 		goto usage;
879 	if ((argc < 3 && !another(&argc, &argv, "to-name")) || argc > 3) {
880 usage:
881 		fprintf(ttyout, "usage: %s from-name to-name\n", argv[0]);
882 		code = -1;
883 		return;
884 	}
885 	if (command("RNFR %s", argv[1]) == CONTINUE)
886 		(void)command("RNTO %s", argv[2]);
887 }
888 
889 /*
890  * Get a directory listing of remote files.
891  */
892 void
893 ls(int argc, char *argv[])
894 {
895 	const char *cmd;
896 	char *oldargv2, *globargv2;
897 
898 	if (argc < 2)
899 		argc++, argv[1] = NULL;
900 	if (argc < 3)
901 		argc++, argv[2] = "-";
902 	if (argc > 3) {
903 		fprintf(ttyout, "usage: %s [remote-directory [local-file]]\n",
904 		    argv[0]);
905 		code = -1;
906 		return;
907 	}
908 	cmd = strcmp(argv[0], "nlist") == 0 ? "NLST" : "LIST";
909 	oldargv2 = argv[2];
910 	if (strcmp(argv[2], "-") && !globulize(&argv[2])) {
911 		code = -1;
912 		return;
913 	}
914 	globargv2 = argv[2];
915 	if (strcmp(argv[2], "-") && *argv[2] != '|' && (!globulize(&argv[2]) ||
916 	    !confirm("output to local-file:", argv[2]))) {
917 		code = -1;
918 		goto freels;
919 	}
920 	recvrequest(cmd, argv[2], argv[1], "w", 0, 0);
921 
922 	/* flush results in case commands are coming from a pipe */
923 	fflush(ttyout);
924 freels:
925 	if (argv[2] != globargv2)		/* free up after globulize() */
926 		free(argv[2]);
927 	if (globargv2 != oldargv2)
928 		free(globargv2);
929 }
930 
931 /*
932  * Get a directory listing of multiple remote files.
933  */
934 void
935 mls(int argc, char *argv[])
936 {
937 	sig_t oldintr;
938 	int i;
939 	char lmode[1], *dest, *odest;
940 
941 	if (argc < 2 && !another(&argc, &argv, "remote-files"))
942 		goto usage;
943 	if (argc < 3 && !another(&argc, &argv, "local-file")) {
944 usage:
945 		fprintf(ttyout, "usage: %s remote-files local-file\n", argv[0]);
946 		code = -1;
947 		return;
948 	}
949 	odest = dest = argv[argc - 1];
950 	argv[argc - 1] = NULL;
951 	if (strcmp(dest, "-") && *dest != '|')
952 		if (!globulize(&dest) ||
953 		    !confirm("output to local-file:", dest)) {
954 			code = -1;
955 			return;
956 	}
957 	mname = argv[0];
958 	mflag = 1;
959 	oldintr = signal(SIGINT, mabort);
960 	(void)setjmp(jabort);
961 	for (i = 1; mflag && i < argc-1; ++i) {
962 		*lmode = (i == 1) ? 'w' : 'a';
963 		recvrequest("LIST", dest, argv[i], lmode, 0, 0);
964 		if (!mflag && fromatty) {
965 			if (confirm(argv[0], NULL))
966 				mflag ++;
967 		}
968 	}
969 	(void)signal(SIGINT, oldintr);
970 	mflag = 0;
971 	if (dest != odest)			/* free up after globulize() */
972 		free(dest);
973 }
974 
975 /*
976  * Do a shell escape
977  */
978 /*ARGSUSED*/
979 void
980 shell(int argc, char *argv[])
981 {
982 	pid_t pid;
983 	sig_t old1, old2;
984 	char shellnam[PATH_MAX], *shellp, *namep;
985 	int wait_status;
986 
987 	old1 = signal (SIGINT, SIG_IGN);
988 	old2 = signal (SIGQUIT, SIG_IGN);
989 	if ((pid = fork()) == 0) {
990 		(void)closefrom(3);
991 		(void)signal(SIGINT, SIG_DFL);
992 		(void)signal(SIGQUIT, SIG_DFL);
993 		shellp = getenv("SHELL");
994 		if (shellp == NULL || *shellp == '\0')
995 			shellp = _PATH_BSHELL;
996 		namep = strrchr(shellp, '/');
997 		if (namep == NULL)
998 			namep = shellp;
999 		shellnam[0] = '-';
1000 		(void)strlcpy(shellnam + 1, ++namep, sizeof(shellnam) - 1);
1001 		if (strcmp(namep, "sh") != 0)
1002 			shellnam[0] = '+';
1003 		if (debug) {
1004 			fputs(shellp, ttyout);
1005 			fputc('\n', ttyout);
1006 			(void)fflush(ttyout);
1007 		}
1008 		if (argc > 1) {
1009 			execl(shellp, shellnam, "-c", altarg, (char *)NULL);
1010 		}
1011 		else {
1012 			execl(shellp, shellnam, (char *)NULL);
1013 		}
1014 		warn("%s", shellp);
1015 		code = -1;
1016 		exit(1);
1017 	}
1018 	if (pid > 0)
1019 		while (wait(&wait_status) != pid)
1020 			;
1021 	(void)signal(SIGINT, old1);
1022 	(void)signal(SIGQUIT, old2);
1023 	if (pid == -1) {
1024 		warn("Try again later");
1025 		code = -1;
1026 	}
1027 	else {
1028 		code = 0;
1029 	}
1030 }
1031 
1032 /*
1033  * Send new user information (re-login)
1034  */
1035 void
1036 user(int argc, char *argv[])
1037 {
1038 	char acctname[80];
1039 	int n, aflag = 0;
1040 
1041 	if (argc < 2)
1042 		(void)another(&argc, &argv, "username");
1043 	if (argc < 2 || argc > 4) {
1044 		fprintf(ttyout, "usage: %s username [password [account]]\n",
1045 		    argv[0]);
1046 		code = -1;
1047 		return;
1048 	}
1049 	n = command("USER %s", argv[1]);
1050 	if (n == CONTINUE) {
1051 		if (argc < 3 )
1052 			argv[2] = getpass("Password:"), argc++;
1053 		n = command("PASS %s", argv[2]);
1054 	}
1055 	if (n == CONTINUE) {
1056 		if (argc < 4) {
1057 			(void)fputs("Account: ", ttyout);
1058 			(void)fflush(ttyout);
1059 			if (fgets(acctname, sizeof(acctname), stdin) == NULL) {
1060 				clearerr(stdin);
1061 				goto fail;
1062 			}
1063 
1064 			acctname[strcspn(acctname, "\n")] = '\0';
1065 
1066 			argv[3] = acctname;
1067 			argc++;
1068 		}
1069 		n = command("ACCT %s", argv[3]);
1070 		aflag++;
1071 	}
1072 	if (n != COMPLETE) {
1073  fail:
1074 		fputs("Login failed.\n", ttyout);
1075 		return;
1076 	}
1077 	if (!aflag && argc == 4) {
1078 		(void)command("ACCT %s", argv[3]);
1079 	}
1080 	connected = -1;
1081 }
1082 
1083 /*
1084  * Print working directory on remote machine.
1085  */
1086 /*ARGSUSED*/
1087 void
1088 pwd(int argc, char *argv[])
1089 {
1090 	int oldverbose = verbose;
1091 
1092 	/*
1093 	 * If we aren't verbose, this doesn't do anything!
1094 	 */
1095 	verbose = 1;
1096 	if (command("PWD") == ERROR && code == 500) {
1097 		fputs("PWD command not recognized, trying XPWD.\n", ttyout);
1098 		(void)command("XPWD");
1099 	}
1100 	verbose = oldverbose;
1101 }
1102 
1103 /*
1104  * Print working directory on local machine.
1105  */
1106 /* ARGSUSED */
1107 void
1108 lpwd(int argc, char *argv[])
1109 {
1110 	char buf[PATH_MAX];
1111 
1112 	if (getcwd(buf, sizeof(buf)) != NULL)
1113 		fprintf(ttyout, "Local directory %s\n", buf);
1114 	else
1115 		warn("getcwd");
1116 	code = 0;
1117 }
1118 
1119 /*
1120  * Make a directory.
1121  */
1122 void
1123 makedir(int argc, char *argv[])
1124 {
1125 
1126 	if ((argc < 2 && !another(&argc, &argv, "directory-name")) ||
1127 	    argc > 2) {
1128 		fprintf(ttyout, "usage: %s directory-name\n", argv[0]);
1129 		code = -1;
1130 		return;
1131 	}
1132 	if (command("MKD %s", argv[1]) == ERROR && code == 500) {
1133 		if (verbose)
1134 			fputs("MKD command not recognized, trying XMKD.\n", ttyout);
1135 		(void)command("XMKD %s", argv[1]);
1136 	}
1137 }
1138 
1139 /*
1140  * Remove a directory.
1141  */
1142 void
1143 removedir(int argc, char *argv[])
1144 {
1145 
1146 	if ((argc < 2 && !another(&argc, &argv, "directory-name")) ||
1147 	    argc > 2) {
1148 		fprintf(ttyout, "usage: %s directory-name\n", argv[0]);
1149 		code = -1;
1150 		return;
1151 	}
1152 	if (command("RMD %s", argv[1]) == ERROR && code == 500) {
1153 		if (verbose)
1154 			fputs("RMD command not recognized, trying XRMD.\n", ttyout);
1155 		(void)command("XRMD %s", argv[1]);
1156 	}
1157 }
1158 
1159 /*
1160  * Send a line, verbatim, to the remote machine.
1161  */
1162 void
1163 quote(int argc, char *argv[])
1164 {
1165 
1166 	if (argc < 2 && !another(&argc, &argv, "command line to send")) {
1167 		fprintf(ttyout, "usage: %s arg ...\n", argv[0]);
1168 		code = -1;
1169 		return;
1170 	}
1171 	quote1("", argc, argv);
1172 }
1173 
1174 /*
1175  * Send a SITE command to the remote machine.  The line
1176  * is sent verbatim to the remote machine, except that the
1177  * word "SITE" is added at the front.
1178  */
1179 void
1180 site(int argc, char *argv[])
1181 {
1182 
1183 	if (argc < 2 && !another(&argc, &argv, "arguments to SITE command")) {
1184 		fprintf(ttyout, "usage: %s arg ...\n", argv[0]);
1185 		code = -1;
1186 		return;
1187 	}
1188 	quote1("SITE", argc, argv);
1189 }
1190 
1191 /*
1192  * Turn argv[1..argc) into a space-separated string, then prepend initial text.
1193  * Send the result as a one-line command and get response.
1194  */
1195 void
1196 quote1(const char *initial, int argc, char *argv[])
1197 {
1198 	int i, len;
1199 	char buf[BUFSIZ];		/* must be >= sizeof(line) */
1200 
1201 	(void)strlcpy(buf, initial, sizeof(buf));
1202 	if (argc > 1) {
1203 		for (i = 1, len = strlen(buf); i < argc && len < sizeof(buf)-1; i++) {
1204 			/* Space for next arg */
1205 			if (len > 1)
1206 				buf[len++] = ' ';
1207 
1208 			/* Sanity check */
1209 			if (len >= sizeof(buf) - 1)
1210 				break;
1211 
1212 			/* Copy next argument, NUL terminate always */
1213 			strlcpy(&buf[len], argv[i], sizeof(buf) - len);
1214 
1215 			/* Update string length */
1216 			len = strlen(buf);
1217 		}
1218 	}
1219 
1220 	/* Make double (triple?) sure the sucker is NUL terminated */
1221 	buf[sizeof(buf) - 1] = '\0';
1222 
1223 	if (command("%s", buf) == PRELIM) {
1224 		while (getreply(0) == PRELIM)
1225 			continue;
1226 	}
1227 }
1228 
1229 void
1230 do_chmod(int argc, char *argv[])
1231 {
1232 
1233 	if (argc < 2 && !another(&argc, &argv, "mode"))
1234 		goto usage;
1235 	if ((argc < 3 && !another(&argc, &argv, "file")) || argc > 3) {
1236 usage:
1237 		fprintf(ttyout, "usage: %s mode file\n", argv[0]);
1238 		code = -1;
1239 		return;
1240 	}
1241 	(void)command("SITE CHMOD %s %s", argv[1], argv[2]);
1242 }
1243 
1244 void
1245 do_umask(int argc, char *argv[])
1246 {
1247 	int oldverbose = verbose;
1248 
1249 	verbose = 1;
1250 	(void)command(argc == 1 ? "SITE UMASK" : "SITE UMASK %s", argv[1]);
1251 	verbose = oldverbose;
1252 }
1253 
1254 void
1255 idle(int argc, char *argv[])
1256 {
1257 	int oldverbose = verbose;
1258 
1259 	verbose = 1;
1260 	(void)command(argc == 1 ? "SITE IDLE" : "SITE IDLE %s", argv[1]);
1261 	verbose = oldverbose;
1262 }
1263 
1264 /*
1265  * Ask the other side for help.
1266  */
1267 void
1268 rmthelp(int argc, char *argv[])
1269 {
1270 	int oldverbose = verbose;
1271 
1272 	verbose = 1;
1273 	(void)command(argc == 1 ? "HELP" : "HELP %s", argv[1]);
1274 	verbose = oldverbose;
1275 }
1276 
1277 /*
1278  * Terminate session and exit.
1279  */
1280 /*ARGSUSED*/
1281 void
1282 quit(int argc, char *argv[])
1283 {
1284 
1285 	if (connected)
1286 		disconnect(0, 0);
1287 	pswitch(1);
1288 	if (connected) {
1289 		disconnect(0, 0);
1290 	}
1291 	exit(0);
1292 }
1293 
1294 void
1295 account(int argc, char *argv[])
1296 {
1297 	char *ap;
1298 
1299 	if (argc > 2) {
1300 		fprintf(ttyout, "usage: %s [password]\n", argv[0]);
1301 		code = -1;
1302 		return;
1303 	}
1304 	else if (argc == 2)
1305 		ap = argv[1];
1306 	else
1307 		ap = getpass("Account:");
1308 	(void)command("ACCT %s", ap);
1309 }
1310 
1311 jmp_buf abortprox;
1312 
1313 /* ARGSUSED */
1314 void
1315 proxabort(int signo)
1316 {
1317 	int save_errno = errno;
1318 
1319 	alarmtimer(0);
1320 	if (!proxy) {
1321 		pswitch(1);
1322 	}
1323 	if (connected) {
1324 		proxflag = 1;
1325 	}
1326 	else {
1327 		proxflag = 0;
1328 	}
1329 	pswitch(0);
1330 	errno = save_errno;
1331 	longjmp(abortprox, 1);
1332 }
1333 
1334 void
1335 doproxy(int argc, char *argv[])
1336 {
1337 	struct cmd *c;
1338 	int cmdpos;
1339 	sig_t oldintr;
1340 
1341 	if (argc < 2 && !another(&argc, &argv, "command")) {
1342 		fprintf(ttyout, "usage: %s command\n", argv[0]);
1343 		code = -1;
1344 		return;
1345 	}
1346 	c = getcmd(argv[1]);
1347 	if (c == (struct cmd *) -1) {
1348 		fputs("?Ambiguous command.\n", ttyout);
1349 		(void)fflush(ttyout);
1350 		code = -1;
1351 		return;
1352 	}
1353 	if (c == 0) {
1354 		fputs("?Invalid command.\n", ttyout);
1355 		(void)fflush(ttyout);
1356 		code = -1;
1357 		return;
1358 	}
1359 	if (!c->c_proxy) {
1360 		fputs("?Invalid proxy command.\n", ttyout);
1361 		(void)fflush(ttyout);
1362 		code = -1;
1363 		return;
1364 	}
1365 	if (setjmp(abortprox)) {
1366 		code = -1;
1367 		return;
1368 	}
1369 	oldintr = signal(SIGINT, proxabort);
1370 	pswitch(1);
1371 	if (c->c_conn && !connected) {
1372 		fputs("Not connected.\n", ttyout);
1373 		(void)fflush(ttyout);
1374 		pswitch(0);
1375 		(void)signal(SIGINT, oldintr);
1376 		code = -1;
1377 		return;
1378 	}
1379 	cmdpos = strcspn(line, " \t");
1380 	if (cmdpos > 0)		/* remove leading "proxy " from input buffer */
1381 		memmove(line, line + cmdpos + 1, strlen(line) - cmdpos + 1);
1382 	(*c->c_handler)(argc-1, argv+1);
1383 	if (connected) {
1384 		proxflag = 1;
1385 	}
1386 	else {
1387 		proxflag = 0;
1388 	}
1389 	pswitch(0);
1390 	(void)signal(SIGINT, oldintr);
1391 }
1392 
1393 void
1394 setcase(int argc, char *argv[])
1395 {
1396 
1397 	code = togglevar(argc, argv, &mcase, "Case mapping");
1398 }
1399 
1400 void
1401 setcr(int argc, char *argv[])
1402 {
1403 
1404 	code = togglevar(argc, argv, &crflag, "Carriage Return stripping");
1405 }
1406 
1407 void
1408 setntrans(int argc, char *argv[])
1409 {
1410 	if (argc == 1) {
1411 		ntflag = 0;
1412 		fputs("Ntrans off.\n", ttyout);
1413 		code = ntflag;
1414 		return;
1415 	}
1416 	ntflag++;
1417 	code = ntflag;
1418 	(void)strlcpy(ntin, argv[1], sizeof(ntin));
1419 	if (argc == 2) {
1420 		ntout[0] = '\0';
1421 		return;
1422 	}
1423 	(void)strlcpy(ntout, argv[2], sizeof(ntout));
1424 }
1425 
1426 void
1427 setnmap(int argc, char *argv[])
1428 {
1429 	char *cp;
1430 
1431 	if (argc == 1) {
1432 		mapflag = 0;
1433 		fputs("Nmap off.\n", ttyout);
1434 		code = mapflag;
1435 		return;
1436 	}
1437 	if ((argc < 3 && !another(&argc, &argv, "outpattern")) || argc > 3) {
1438 		fprintf(ttyout, "usage: %s [inpattern outpattern]\n", argv[0]);
1439 		code = -1;
1440 		return;
1441 	}
1442 	mapflag = 1;
1443 	code = 1;
1444 	cp = strchr(altarg, ' ');
1445 	if (proxy) {
1446 		while(*++cp == ' ')
1447 			continue;
1448 		altarg = cp;
1449 		cp = strchr(altarg, ' ');
1450 	}
1451 	*cp = '\0';
1452 	(void)strncpy(mapin, altarg, PATH_MAX - 1);
1453 	while (*++cp == ' ')
1454 		continue;
1455 	(void)strncpy(mapout, cp, PATH_MAX - 1);
1456 }
1457 
1458 void
1459 setpassive(int argc, char *argv[])
1460 {
1461 
1462 	code = togglevar(argc, argv, &passivemode,
1463 	    verbose ? "Passive mode" : NULL);
1464 }
1465 
1466 void
1467 setsunique(int argc, char *argv[])
1468 {
1469 
1470 	code = togglevar(argc, argv, &sunique, "Store unique");
1471 }
1472 
1473 void
1474 setrunique(int argc, char *argv[])
1475 {
1476 
1477 	code = togglevar(argc, argv, &runique, "Receive unique");
1478 }
1479 
1480 /* change directory to parent directory */
1481 /* ARGSUSED */
1482 void
1483 cdup(int argc, char *argv[])
1484 {
1485 	int r;
1486 
1487 	r = command("CDUP");
1488 	if (r == ERROR && code == 500) {
1489 		if (verbose)
1490 			fputs("CDUP command not recognized, trying XCUP.\n", ttyout);
1491 		r = command("XCUP");
1492 	}
1493 	if (r == COMPLETE)
1494 		dirchange = 1;
1495 }
1496 
1497 /*
1498  * Restart transfer at specific point
1499  */
1500 void
1501 restart(int argc, char *argv[])
1502 {
1503 	off_t nrestart_point;
1504 	char *ep;
1505 
1506 	if (argc != 2)
1507 		fputs("restart: offset not specified.\n", ttyout);
1508 	else {
1509 		nrestart_point = strtoll(argv[1], &ep, 10);
1510 		if (nrestart_point == LLONG_MAX || *ep != '\0')
1511 			fputs("restart: invalid offset.\n", ttyout);
1512 		else {
1513 			fprintf(ttyout, "Restarting at %lld. Execute get, put "
1514 				"or append to initiate transfer\n",
1515 				(long long)nrestart_point);
1516 			restart_point = nrestart_point;
1517 		}
1518 	}
1519 }
1520 
1521 /*
1522  * Show remote system type
1523  */
1524 /* ARGSUSED */
1525 void
1526 syst(int argc, char *argv[])
1527 {
1528 
1529 	(void)command("SYST");
1530 }
1531 
1532 void
1533 macdef(int argc, char *argv[])
1534 {
1535 	char *tmp;
1536 	int c;
1537 
1538 	if (macnum == 16) {
1539 		fputs("Limit of 16 macros have already been defined.\n", ttyout);
1540 		code = -1;
1541 		return;
1542 	}
1543 	if ((argc < 2 && !another(&argc, &argv, "macro-name")) || argc > 2) {
1544 		fprintf(ttyout, "usage: %s macro-name\n", argv[0]);
1545 		code = -1;
1546 		return;
1547 	}
1548 	if (interactive)
1549 		fputs(
1550 "Enter macro line by line, terminating it with a null line.\n", ttyout);
1551 	(void)strlcpy(macros[macnum].mac_name, argv[1],
1552 	    sizeof(macros[macnum].mac_name));
1553 	if (macnum == 0)
1554 		macros[macnum].mac_start = macbuf;
1555 	else
1556 		macros[macnum].mac_start = macros[macnum - 1].mac_end + 1;
1557 	tmp = macros[macnum].mac_start;
1558 	while (tmp != macbuf+4096) {
1559 		if ((c = getchar()) == EOF) {
1560 			fputs("macdef: end of file encountered.\n", ttyout);
1561 			code = -1;
1562 			return;
1563 		}
1564 		if ((*tmp = c) == '\n') {
1565 			if (tmp == macros[macnum].mac_start) {
1566 				macros[macnum++].mac_end = tmp;
1567 				code = 0;
1568 				return;
1569 			}
1570 			if (*(tmp-1) == '\0') {
1571 				macros[macnum++].mac_end = tmp - 1;
1572 				code = 0;
1573 				return;
1574 			}
1575 			*tmp = '\0';
1576 		}
1577 		tmp++;
1578 	}
1579 	while (1) {
1580 		while ((c = getchar()) != '\n' && c != EOF)
1581 			/* LOOP */;
1582 		if (c == EOF || getchar() == '\n') {
1583 			fputs("Macro not defined - 4K buffer exceeded.\n", ttyout);
1584 			code = -1;
1585 			return;
1586 		}
1587 	}
1588 }
1589 
1590 /*
1591  * Get size of file on remote machine
1592  */
1593 void
1594 sizecmd(int argc, char *argv[])
1595 {
1596 	off_t size;
1597 
1598 	if ((argc < 2 && !another(&argc, &argv, "file")) || argc > 2) {
1599 		fprintf(ttyout, "usage: %s file\n", argv[0]);
1600 		code = -1;
1601 		return;
1602 	}
1603 	size = remotesize(argv[1], 1);
1604 	if (size != -1)
1605 		fprintf(ttyout, "%s\t%lld\n", argv[1], (long long)size);
1606 	code = size;
1607 }
1608 
1609 /*
1610  * Get last modification time of file on remote machine
1611  */
1612 void
1613 modtime(int argc, char *argv[])
1614 {
1615 	time_t mtime;
1616 
1617 	if ((argc < 2 && !another(&argc, &argv, "file")) || argc > 2) {
1618 		fprintf(ttyout, "usage: %s file\n", argv[0]);
1619 		code = -1;
1620 		return;
1621 	}
1622 	mtime = remotemodtime(argv[1], 1);
1623 	if (mtime != -1)
1624 		fprintf(ttyout, "%s\t%s", argv[1], asctime(localtime(&mtime)));
1625 	code = mtime;
1626 }
1627 
1628 /*
1629  * Show status on remote machine
1630  */
1631 void
1632 rmtstatus(int argc, char *argv[])
1633 {
1634 
1635 	(void)command(argc > 1 ? "STAT %s" : "STAT" , argv[1]);
1636 }
1637 
1638 /*
1639  * Get file if modtime is more recent than current file
1640  */
1641 void
1642 newer(int argc, char *argv[])
1643 {
1644 
1645 	(void)getit(argc, argv, -1, "w");
1646 }
1647 
1648 /*
1649  * Display one file through $PAGER (defaults to "more").
1650  */
1651 void
1652 page(int argc, char *argv[])
1653 {
1654 	off_t orestart_point;
1655 	int ohash, overbose;
1656 	char *p, *pager, *oldargv1;
1657 
1658 	if ((argc < 2 && !another(&argc, &argv, "file")) || argc > 2) {
1659 		fprintf(ttyout, "usage: %s file\n", argv[0]);
1660 		code = -1;
1661 		return;
1662 	}
1663 	oldargv1 = argv[1];
1664 	if (!globulize(&argv[1])) {
1665 		code = -1;
1666 		return;
1667 	}
1668 	p = getenv("PAGER");
1669 	if (p == NULL || (*p == '\0'))
1670 		p = PAGER;
1671 	if (asprintf(&pager, "|%s", p) == -1)
1672 		errx(1, "Can't allocate memory for $PAGER");
1673 
1674 	orestart_point = restart_point;
1675 	ohash = hash;
1676 	overbose = verbose;
1677 	restart_point = hash = verbose = 0;
1678 	recvrequest("RETR", pager, argv[1], "r+w", 1, 0);
1679 	(void)free(pager);
1680 	restart_point = orestart_point;
1681 	hash = ohash;
1682 	verbose = overbose;
1683 	if (oldargv1 != argv[1])	/* free up after globulize() */
1684 		free(argv[1]);
1685 }
1686 
1687 #endif /* !SMALL */
1688 
1689