xref: /openbsd/usr.bin/ftp/cmds.c (revision cecf84d4)
1 /*	$OpenBSD: cmds.c,v 1.74 2015/01/30 04:45:45 tedu 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(*tp)) {
283 						tp++;
284 					}
285 					if (!*tp) {
286 						tp = cp;
287 						tp2 = tmpbuf;
288 						while ((*tp2 = *tp) != '\0') {
289 						     if (isupper(*tp2)) {
290 							    *tp2 =
291 								tolower(*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]) < 0) {
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 		for (pid = 3; pid < 20; pid++)
991 			(void)close(pid);
992 		(void)signal(SIGINT, SIG_DFL);
993 		(void)signal(SIGQUIT, SIG_DFL);
994 		shellp = getenv("SHELL");
995 		if (shellp == NULL || *shellp == '\0')
996 			shellp = _PATH_BSHELL;
997 		namep = strrchr(shellp, '/');
998 		if (namep == NULL)
999 			namep = shellp;
1000 		shellnam[0] = '-';
1001 		(void)strlcpy(shellnam + 1, ++namep, sizeof(shellnam) - 1);
1002 		if (strcmp(namep, "sh") != 0)
1003 			shellnam[0] = '+';
1004 		if (debug) {
1005 			fputs(shellp, ttyout);
1006 			fputc('\n', ttyout);
1007 			(void)fflush(ttyout);
1008 		}
1009 		if (argc > 1) {
1010 			execl(shellp, shellnam, "-c", altarg, (char *)0);
1011 		}
1012 		else {
1013 			execl(shellp, shellnam, (char *)0);
1014 		}
1015 		warn("%s", shellp);
1016 		code = -1;
1017 		exit(1);
1018 	}
1019 	if (pid > 0)
1020 		while (wait(&wait_status) != pid)
1021 			;
1022 	(void)signal(SIGINT, old1);
1023 	(void)signal(SIGQUIT, old2);
1024 	if (pid == -1) {
1025 		warn("Try again later");
1026 		code = -1;
1027 	}
1028 	else {
1029 		code = 0;
1030 	}
1031 }
1032 
1033 /*
1034  * Send new user information (re-login)
1035  */
1036 void
1037 user(int argc, char *argv[])
1038 {
1039 	char acctname[80];
1040 	int n, aflag = 0;
1041 
1042 	if (argc < 2)
1043 		(void)another(&argc, &argv, "username");
1044 	if (argc < 2 || argc > 4) {
1045 		fprintf(ttyout, "usage: %s username [password [account]]\n",
1046 		    argv[0]);
1047 		code = -1;
1048 		return;
1049 	}
1050 	n = command("USER %s", argv[1]);
1051 	if (n == CONTINUE) {
1052 		if (argc < 3 )
1053 			argv[2] = getpass("Password:"), argc++;
1054 		n = command("PASS %s", argv[2]);
1055 	}
1056 	if (n == CONTINUE) {
1057 		if (argc < 4) {
1058 			(void)fputs("Account: ", ttyout);
1059 			(void)fflush(ttyout);
1060 			if (fgets(acctname, sizeof(acctname), stdin) == NULL) {
1061 				clearerr(stdin);
1062 				goto fail;
1063 			}
1064 
1065 			acctname[strcspn(acctname, "\n")] = '\0';
1066 
1067 			argv[3] = acctname;
1068 			argc++;
1069 		}
1070 		n = command("ACCT %s", argv[3]);
1071 		aflag++;
1072 	}
1073 	if (n != COMPLETE) {
1074  fail:
1075 		fputs("Login failed.\n", ttyout);
1076 		return;
1077 	}
1078 	if (!aflag && argc == 4) {
1079 		(void)command("ACCT %s", argv[3]);
1080 	}
1081 	connected = -1;
1082 }
1083 
1084 /*
1085  * Print working directory on remote machine.
1086  */
1087 /*ARGSUSED*/
1088 void
1089 pwd(int argc, char *argv[])
1090 {
1091 	int oldverbose = verbose;
1092 
1093 	/*
1094 	 * If we aren't verbose, this doesn't do anything!
1095 	 */
1096 	verbose = 1;
1097 	if (command("PWD") == ERROR && code == 500) {
1098 		fputs("PWD command not recognized, trying XPWD.\n", ttyout);
1099 		(void)command("XPWD");
1100 	}
1101 	verbose = oldverbose;
1102 }
1103 
1104 /*
1105  * Print working directory on local machine.
1106  */
1107 /* ARGSUSED */
1108 void
1109 lpwd(int argc, char *argv[])
1110 {
1111 	char buf[PATH_MAX];
1112 
1113 	if (getcwd(buf, sizeof(buf)) != NULL)
1114 		fprintf(ttyout, "Local directory %s\n", buf);
1115 	else
1116 		warn("getcwd");
1117 	code = 0;
1118 }
1119 
1120 /*
1121  * Make a directory.
1122  */
1123 void
1124 makedir(int argc, char *argv[])
1125 {
1126 
1127 	if ((argc < 2 && !another(&argc, &argv, "directory-name")) ||
1128 	    argc > 2) {
1129 		fprintf(ttyout, "usage: %s directory-name\n", argv[0]);
1130 		code = -1;
1131 		return;
1132 	}
1133 	if (command("MKD %s", argv[1]) == ERROR && code == 500) {
1134 		if (verbose)
1135 			fputs("MKD command not recognized, trying XMKD.\n", ttyout);
1136 		(void)command("XMKD %s", argv[1]);
1137 	}
1138 }
1139 
1140 /*
1141  * Remove a directory.
1142  */
1143 void
1144 removedir(int argc, char *argv[])
1145 {
1146 
1147 	if ((argc < 2 && !another(&argc, &argv, "directory-name")) ||
1148 	    argc > 2) {
1149 		fprintf(ttyout, "usage: %s directory-name\n", argv[0]);
1150 		code = -1;
1151 		return;
1152 	}
1153 	if (command("RMD %s", argv[1]) == ERROR && code == 500) {
1154 		if (verbose)
1155 			fputs("RMD command not recognized, trying XRMD.\n", ttyout);
1156 		(void)command("XRMD %s", argv[1]);
1157 	}
1158 }
1159 
1160 /*
1161  * Send a line, verbatim, to the remote machine.
1162  */
1163 void
1164 quote(int argc, char *argv[])
1165 {
1166 
1167 	if (argc < 2 && !another(&argc, &argv, "command line to send")) {
1168 		fprintf(ttyout, "usage: %s arg ...\n", argv[0]);
1169 		code = -1;
1170 		return;
1171 	}
1172 	quote1("", argc, argv);
1173 }
1174 
1175 /*
1176  * Send a SITE command to the remote machine.  The line
1177  * is sent verbatim to the remote machine, except that the
1178  * word "SITE" is added at the front.
1179  */
1180 void
1181 site(int argc, char *argv[])
1182 {
1183 
1184 	if (argc < 2 && !another(&argc, &argv, "arguments to SITE command")) {
1185 		fprintf(ttyout, "usage: %s arg ...\n", argv[0]);
1186 		code = -1;
1187 		return;
1188 	}
1189 	quote1("SITE", argc, argv);
1190 }
1191 
1192 /*
1193  * Turn argv[1..argc) into a space-separated string, then prepend initial text.
1194  * Send the result as a one-line command and get response.
1195  */
1196 void
1197 quote1(const char *initial, int argc, char *argv[])
1198 {
1199 	int i, len;
1200 	char buf[BUFSIZ];		/* must be >= sizeof(line) */
1201 
1202 	(void)strlcpy(buf, initial, sizeof(buf));
1203 	if (argc > 1) {
1204 		for (i = 1, len = strlen(buf); i < argc && len < sizeof(buf)-1; i++) {
1205 			/* Space for next arg */
1206 			if (len > 1)
1207 				buf[len++] = ' ';
1208 
1209 			/* Sanity check */
1210 			if (len >= sizeof(buf) - 1)
1211 				break;
1212 
1213 			/* Copy next argument, NUL terminate always */
1214 			strlcpy(&buf[len], argv[i], sizeof(buf) - len);
1215 
1216 			/* Update string length */
1217 			len = strlen(buf);
1218 		}
1219 	}
1220 
1221 	/* Make double (triple?) sure the sucker is NUL terminated */
1222 	buf[sizeof(buf) - 1] = '\0';
1223 
1224 	if (command("%s", buf) == PRELIM) {
1225 		while (getreply(0) == PRELIM)
1226 			continue;
1227 	}
1228 }
1229 
1230 void
1231 do_chmod(int argc, char *argv[])
1232 {
1233 
1234 	if (argc < 2 && !another(&argc, &argv, "mode"))
1235 		goto usage;
1236 	if ((argc < 3 && !another(&argc, &argv, "file")) || argc > 3) {
1237 usage:
1238 		fprintf(ttyout, "usage: %s mode file\n", argv[0]);
1239 		code = -1;
1240 		return;
1241 	}
1242 	(void)command("SITE CHMOD %s %s", argv[1], argv[2]);
1243 }
1244 
1245 void
1246 do_umask(int argc, char *argv[])
1247 {
1248 	int oldverbose = verbose;
1249 
1250 	verbose = 1;
1251 	(void)command(argc == 1 ? "SITE UMASK" : "SITE UMASK %s", argv[1]);
1252 	verbose = oldverbose;
1253 }
1254 
1255 void
1256 idle(int argc, char *argv[])
1257 {
1258 	int oldverbose = verbose;
1259 
1260 	verbose = 1;
1261 	(void)command(argc == 1 ? "SITE IDLE" : "SITE IDLE %s", argv[1]);
1262 	verbose = oldverbose;
1263 }
1264 
1265 /*
1266  * Ask the other side for help.
1267  */
1268 void
1269 rmthelp(int argc, char *argv[])
1270 {
1271 	int oldverbose = verbose;
1272 
1273 	verbose = 1;
1274 	(void)command(argc == 1 ? "HELP" : "HELP %s", argv[1]);
1275 	verbose = oldverbose;
1276 }
1277 
1278 /*
1279  * Terminate session and exit.
1280  */
1281 /*ARGSUSED*/
1282 void
1283 quit(int argc, char *argv[])
1284 {
1285 
1286 	if (connected)
1287 		disconnect(0, 0);
1288 	pswitch(1);
1289 	if (connected) {
1290 		disconnect(0, 0);
1291 	}
1292 	exit(0);
1293 }
1294 
1295 void
1296 account(int argc, char *argv[])
1297 {
1298 	char *ap;
1299 
1300 	if (argc > 2) {
1301 		fprintf(ttyout, "usage: %s [password]\n", argv[0]);
1302 		code = -1;
1303 		return;
1304 	}
1305 	else if (argc == 2)
1306 		ap = argv[1];
1307 	else
1308 		ap = getpass("Account:");
1309 	(void)command("ACCT %s", ap);
1310 }
1311 
1312 jmp_buf abortprox;
1313 
1314 /* ARGSUSED */
1315 void
1316 proxabort(int signo)
1317 {
1318 	int save_errno = errno;
1319 
1320 	alarmtimer(0);
1321 	if (!proxy) {
1322 		pswitch(1);
1323 	}
1324 	if (connected) {
1325 		proxflag = 1;
1326 	}
1327 	else {
1328 		proxflag = 0;
1329 	}
1330 	pswitch(0);
1331 	errno = save_errno;
1332 	longjmp(abortprox, 1);
1333 }
1334 
1335 void
1336 doproxy(int argc, char *argv[])
1337 {
1338 	struct cmd *c;
1339 	int cmdpos;
1340 	sig_t oldintr;
1341 
1342 	if (argc < 2 && !another(&argc, &argv, "command")) {
1343 		fprintf(ttyout, "usage: %s command\n", argv[0]);
1344 		code = -1;
1345 		return;
1346 	}
1347 	c = getcmd(argv[1]);
1348 	if (c == (struct cmd *) -1) {
1349 		fputs("?Ambiguous command.\n", ttyout);
1350 		(void)fflush(ttyout);
1351 		code = -1;
1352 		return;
1353 	}
1354 	if (c == 0) {
1355 		fputs("?Invalid command.\n", ttyout);
1356 		(void)fflush(ttyout);
1357 		code = -1;
1358 		return;
1359 	}
1360 	if (!c->c_proxy) {
1361 		fputs("?Invalid proxy command.\n", ttyout);
1362 		(void)fflush(ttyout);
1363 		code = -1;
1364 		return;
1365 	}
1366 	if (setjmp(abortprox)) {
1367 		code = -1;
1368 		return;
1369 	}
1370 	oldintr = signal(SIGINT, proxabort);
1371 	pswitch(1);
1372 	if (c->c_conn && !connected) {
1373 		fputs("Not connected.\n", ttyout);
1374 		(void)fflush(ttyout);
1375 		pswitch(0);
1376 		(void)signal(SIGINT, oldintr);
1377 		code = -1;
1378 		return;
1379 	}
1380 	cmdpos = strcspn(line, " \t");
1381 	if (cmdpos > 0)		/* remove leading "proxy " from input buffer */
1382 		memmove(line, line + cmdpos + 1, strlen(line) - cmdpos + 1);
1383 	(*c->c_handler)(argc-1, argv+1);
1384 	if (connected) {
1385 		proxflag = 1;
1386 	}
1387 	else {
1388 		proxflag = 0;
1389 	}
1390 	pswitch(0);
1391 	(void)signal(SIGINT, oldintr);
1392 }
1393 
1394 void
1395 setcase(int argc, char *argv[])
1396 {
1397 
1398 	code = togglevar(argc, argv, &mcase, "Case mapping");
1399 }
1400 
1401 void
1402 setcr(int argc, char *argv[])
1403 {
1404 
1405 	code = togglevar(argc, argv, &crflag, "Carriage Return stripping");
1406 }
1407 
1408 void
1409 setntrans(int argc, char *argv[])
1410 {
1411 	if (argc == 1) {
1412 		ntflag = 0;
1413 		fputs("Ntrans off.\n", ttyout);
1414 		code = ntflag;
1415 		return;
1416 	}
1417 	ntflag++;
1418 	code = ntflag;
1419 	(void)strlcpy(ntin, argv[1], sizeof(ntin));
1420 	if (argc == 2) {
1421 		ntout[0] = '\0';
1422 		return;
1423 	}
1424 	(void)strlcpy(ntout, argv[2], sizeof(ntout));
1425 }
1426 
1427 void
1428 setnmap(int argc, char *argv[])
1429 {
1430 	char *cp;
1431 
1432 	if (argc == 1) {
1433 		mapflag = 0;
1434 		fputs("Nmap off.\n", ttyout);
1435 		code = mapflag;
1436 		return;
1437 	}
1438 	if ((argc < 3 && !another(&argc, &argv, "outpattern")) || argc > 3) {
1439 		fprintf(ttyout, "usage: %s [inpattern outpattern]\n", argv[0]);
1440 		code = -1;
1441 		return;
1442 	}
1443 	mapflag = 1;
1444 	code = 1;
1445 	cp = strchr(altarg, ' ');
1446 	if (proxy) {
1447 		while(*++cp == ' ')
1448 			continue;
1449 		altarg = cp;
1450 		cp = strchr(altarg, ' ');
1451 	}
1452 	*cp = '\0';
1453 	(void)strncpy(mapin, altarg, PATH_MAX - 1);
1454 	while (*++cp == ' ')
1455 		continue;
1456 	(void)strncpy(mapout, cp, PATH_MAX - 1);
1457 }
1458 
1459 void
1460 setpassive(int argc, char *argv[])
1461 {
1462 
1463 	code = togglevar(argc, argv, &passivemode,
1464 	    verbose ? "Passive mode" : NULL);
1465 }
1466 
1467 void
1468 setsunique(int argc, char *argv[])
1469 {
1470 
1471 	code = togglevar(argc, argv, &sunique, "Store unique");
1472 }
1473 
1474 void
1475 setrunique(int argc, char *argv[])
1476 {
1477 
1478 	code = togglevar(argc, argv, &runique, "Receive unique");
1479 }
1480 
1481 /* change directory to parent directory */
1482 /* ARGSUSED */
1483 void
1484 cdup(int argc, char *argv[])
1485 {
1486 	int r;
1487 
1488 	r = command("CDUP");
1489 	if (r == ERROR && code == 500) {
1490 		if (verbose)
1491 			fputs("CDUP command not recognized, trying XCUP.\n", ttyout);
1492 		r = command("XCUP");
1493 	}
1494 	if (r == COMPLETE)
1495 		dirchange = 1;
1496 }
1497 
1498 /*
1499  * Restart transfer at specific point
1500  */
1501 void
1502 restart(int argc, char *argv[])
1503 {
1504 	quad_t nrestart_point;
1505 	char *ep;
1506 
1507 	if (argc != 2)
1508 		fputs("restart: offset not specified.\n", ttyout);
1509 	else {
1510 		nrestart_point = strtoq(argv[1], &ep, 10);
1511 		if (nrestart_point == QUAD_MAX || *ep != '\0')
1512 			fputs("restart: invalid offset.\n", ttyout);
1513 		else {
1514 			fprintf(ttyout, "Restarting at %lld. Execute get, put "
1515 				"or append to initiate transfer\n",
1516 				(long long)nrestart_point);
1517 			restart_point = nrestart_point;
1518 		}
1519 	}
1520 }
1521 
1522 /*
1523  * Show remote system type
1524  */
1525 /* ARGSUSED */
1526 void
1527 syst(int argc, char *argv[])
1528 {
1529 
1530 	(void)command("SYST");
1531 }
1532 
1533 void
1534 macdef(int argc, char *argv[])
1535 {
1536 	char *tmp;
1537 	int c;
1538 
1539 	if (macnum == 16) {
1540 		fputs("Limit of 16 macros have already been defined.\n", ttyout);
1541 		code = -1;
1542 		return;
1543 	}
1544 	if ((argc < 2 && !another(&argc, &argv, "macro-name")) || argc > 2) {
1545 		fprintf(ttyout, "usage: %s macro-name\n", argv[0]);
1546 		code = -1;
1547 		return;
1548 	}
1549 	if (interactive)
1550 		fputs(
1551 "Enter macro line by line, terminating it with a null line.\n", ttyout);
1552 	(void)strlcpy(macros[macnum].mac_name, argv[1],
1553 	    sizeof(macros[macnum].mac_name));
1554 	if (macnum == 0)
1555 		macros[macnum].mac_start = macbuf;
1556 	else
1557 		macros[macnum].mac_start = macros[macnum - 1].mac_end + 1;
1558 	tmp = macros[macnum].mac_start;
1559 	while (tmp != macbuf+4096) {
1560 		if ((c = getchar()) == EOF) {
1561 			fputs("macdef: end of file encountered.\n", ttyout);
1562 			code = -1;
1563 			return;
1564 		}
1565 		if ((*tmp = c) == '\n') {
1566 			if (tmp == macros[macnum].mac_start) {
1567 				macros[macnum++].mac_end = tmp;
1568 				code = 0;
1569 				return;
1570 			}
1571 			if (*(tmp-1) == '\0') {
1572 				macros[macnum++].mac_end = tmp - 1;
1573 				code = 0;
1574 				return;
1575 			}
1576 			*tmp = '\0';
1577 		}
1578 		tmp++;
1579 	}
1580 	while (1) {
1581 		while ((c = getchar()) != '\n' && c != EOF)
1582 			/* LOOP */;
1583 		if (c == EOF || getchar() == '\n') {
1584 			fputs("Macro not defined - 4K buffer exceeded.\n", ttyout);
1585 			code = -1;
1586 			return;
1587 		}
1588 	}
1589 }
1590 
1591 /*
1592  * Get size of file on remote machine
1593  */
1594 void
1595 sizecmd(int argc, char *argv[])
1596 {
1597 	off_t size;
1598 
1599 	if ((argc < 2 && !another(&argc, &argv, "file")) || argc > 2) {
1600 		fprintf(ttyout, "usage: %s file\n", argv[0]);
1601 		code = -1;
1602 		return;
1603 	}
1604 	size = remotesize(argv[1], 1);
1605 	if (size != -1)
1606 		fprintf(ttyout, "%s\t%lld\n", argv[1], (long long)size);
1607 	code = size;
1608 }
1609 
1610 /*
1611  * Get last modification time of file on remote machine
1612  */
1613 void
1614 modtime(int argc, char *argv[])
1615 {
1616 	time_t mtime;
1617 
1618 	if ((argc < 2 && !another(&argc, &argv, "file")) || argc > 2) {
1619 		fprintf(ttyout, "usage: %s file\n", argv[0]);
1620 		code = -1;
1621 		return;
1622 	}
1623 	mtime = remotemodtime(argv[1], 1);
1624 	if (mtime != -1)
1625 		fprintf(ttyout, "%s\t%s", argv[1], asctime(localtime(&mtime)));
1626 	code = mtime;
1627 }
1628 
1629 /*
1630  * Show status on remote machine
1631  */
1632 void
1633 rmtstatus(int argc, char *argv[])
1634 {
1635 
1636 	(void)command(argc > 1 ? "STAT %s" : "STAT" , argv[1]);
1637 }
1638 
1639 /*
1640  * Get file if modtime is more recent than current file
1641  */
1642 void
1643 newer(int argc, char *argv[])
1644 {
1645 
1646 	if (getit(argc, argv, -1, "w"))
1647 		fprintf(ttyout, "Local file \"%s\" is newer than remote file \"%s\".\n",
1648 			argv[2], argv[1]);
1649 }
1650 
1651 /*
1652  * Display one file through $PAGER (defaults to "more").
1653  */
1654 void
1655 page(int argc, char *argv[])
1656 {
1657 	int orestart_point, ohash, overbose;
1658 	char *p, *pager, *oldargv1;
1659 
1660 	if ((argc < 2 && !another(&argc, &argv, "file")) || argc > 2) {
1661 		fprintf(ttyout, "usage: %s file\n", argv[0]);
1662 		code = -1;
1663 		return;
1664 	}
1665 	oldargv1 = argv[1];
1666 	if (!globulize(&argv[1])) {
1667 		code = -1;
1668 		return;
1669 	}
1670 	p = getenv("PAGER");
1671 	if (p == NULL || (*p == '\0'))
1672 		p = PAGER;
1673 	if (asprintf(&pager, "|%s", p) == -1)
1674 		errx(1, "Can't allocate memory for $PAGER");
1675 
1676 	orestart_point = restart_point;
1677 	ohash = hash;
1678 	overbose = verbose;
1679 	restart_point = hash = verbose = 0;
1680 	recvrequest("RETR", pager, argv[1], "r+w", 1, 0);
1681 	(void)free(pager);
1682 	restart_point = orestart_point;
1683 	hash = ohash;
1684 	verbose = overbose;
1685 	if (oldargv1 != argv[1])	/* free up after globulize() */
1686 		free(argv[1]);
1687 }
1688 
1689 #endif /* !SMALL */
1690 
1691