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