1/*	$OpenBSD: small.c,v 1.13 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/*
63 * FTP User Program -- Command Routines.
64 */
65#include <sys/types.h>
66#include <sys/socket.h>
67#include <sys/stat.h>
68#include <sys/wait.h>
69#include <arpa/ftp.h>
70
71#include <ctype.h>
72#include <err.h>
73#include <fnmatch.h>
74#include <glob.h>
75#include <netdb.h>
76#include <stdio.h>
77#include <stdlib.h>
78#include <string.h>
79#include <unistd.h>
80#include <errno.h>
81
82#include "ftp_var.h"
83#include "pathnames.h"
84#include "small.h"
85
86jmp_buf	jabort;
87char   *mname;
88char   *home = "/";
89
90struct	types {
91	char	*t_name;
92	char	*t_mode;
93	int	t_type;
94} types[] = {
95	{ "ascii",	"A",	TYPE_A },
96	{ "binary",	"I",	TYPE_I },
97	{ "image",	"I",	TYPE_I },
98	{ NULL }
99};
100
101/*
102 * Set transfer type.
103 */
104void
105settype(int argc, char *argv[])
106{
107	struct types *p;
108	int comret;
109
110	if (argc > 2) {
111		char *sep;
112
113		fprintf(ttyout, "usage: %s [", argv[0]);
114		sep = "";
115		for (p = types; p->t_name; p++) {
116			fprintf(ttyout, "%s%s", sep, p->t_name);
117			sep = " | ";
118		}
119		fputs("]\n", ttyout);
120		code = -1;
121		return;
122	}
123	if (argc < 2) {
124		fprintf(ttyout, "Using %s mode to transfer files.\n", typename);
125		code = 0;
126		return;
127	}
128	for (p = types; p->t_name; p++)
129		if (strcmp(argv[1], p->t_name) == 0)
130			break;
131	if (p->t_name == 0) {
132		fprintf(ttyout, "%s: unknown mode.\n", argv[1]);
133		code = -1;
134		return;
135	}
136	comret = command("TYPE %s", p->t_mode);
137	if (comret == COMPLETE) {
138		(void)strlcpy(typename, p->t_name, sizeof typename);
139		curtype = type = p->t_type;
140	}
141}
142
143/*
144 * Internal form of settype; changes current type in use with server
145 * without changing our notion of the type for data transfers.
146 * Used to change to and from ascii for listings.
147 */
148void
149changetype(int newtype, int show)
150{
151	struct types *p;
152	int comret, oldverbose = verbose;
153
154	if (newtype == 0)
155		newtype = TYPE_I;
156	if (newtype == curtype)
157		return;
158	if (
159#ifndef SMALL
160	    !debug &&
161#endif /* !SMALL */
162	    show == 0)
163		verbose = 0;
164	for (p = types; p->t_name; p++)
165		if (newtype == p->t_type)
166			break;
167	if (p->t_name == 0) {
168		warnx("internal error: unknown type %d.", newtype);
169		return;
170	}
171	if (newtype == TYPE_L && bytename[0] != '\0')
172		comret = command("TYPE %s %s", p->t_mode, bytename);
173	else
174		comret = command("TYPE %s", p->t_mode);
175	if (comret == COMPLETE)
176		curtype = newtype;
177	verbose = oldverbose;
178}
179
180char *stype[] = {
181	"type",
182	"",
183	0
184};
185
186/*
187 * Set binary transfer type.
188 */
189void
190setbinary(int argc, char *argv[])
191{
192
193	stype[1] = "binary";
194	settype(2, stype);
195}
196
197void
198get(int argc, char *argv[])
199{
200
201	(void)getit(argc, argv, 0, restart_point ? "a+w" : "w" );
202}
203
204/*
205 * Receive one file.
206 */
207int
208getit(int argc, char *argv[], int restartit, const char *mode)
209{
210	int loc = 0;
211	int rval = 0;
212	char *oldargv1, *oldargv2, *globargv2;
213
214	if (argc == 2) {
215		argc++;
216		argv[2] = argv[1];
217		loc++;
218	}
219#ifndef SMALL
220	if (argc < 2 && !another(&argc, &argv, "remote-file"))
221		goto usage;
222	if ((argc < 3 && !another(&argc, &argv, "local-file")) || argc > 3) {
223usage:
224		fprintf(ttyout, "usage: %s remote-file [local-file]\n",
225		    argv[0]);
226		code = -1;
227		return (0);
228	}
229#endif /* !SMALL */
230	oldargv1 = argv[1];
231	oldargv2 = argv[2];
232	if (!globulize(&argv[2])) {
233		code = -1;
234		return (0);
235	}
236	globargv2 = argv[2];
237	if (loc && mcase) {
238		char *tp = argv[1], *tp2, tmpbuf[PATH_MAX];
239
240		while (*tp && !islower((unsigned char)*tp)) {
241			tp++;
242		}
243		if (!*tp) {
244			tp = argv[2];
245			tp2 = tmpbuf;
246			while ((*tp2 = *tp) != '\0') {
247				if (isupper((unsigned char)*tp2)) {
248					*tp2 = tolower((unsigned char)*tp2);
249				}
250				tp++;
251				tp2++;
252			}
253			argv[2] = tmpbuf;
254		}
255	}
256	if (loc && ntflag)
257		argv[2] = dotrans(argv[2]);
258	if (loc && mapflag)
259		argv[2] = domap(argv[2]);
260#ifndef SMALL
261	if (restartit) {
262		struct stat stbuf;
263		int ret;
264
265		ret = stat(argv[2], &stbuf);
266		if (restartit == 1) {
267			restart_point = (ret < 0) ? 0 : stbuf.st_size;
268		} else {
269			if (ret == 0) {
270				time_t mtime;
271
272				mtime = remotemodtime(argv[1], 0);
273				if (mtime == -1)
274					goto freegetit;
275				if (stbuf.st_mtime >= mtime) {
276					rval = 1;
277					fprintf(ttyout,
278						"Local file \"%s\" is newer "\
279						"than remote file \"%s\".\n",
280						argv[2], argv[1]);
281					goto freegetit;
282				}
283			}
284		}
285	}
286#endif /* !SMALL */
287
288	recvrequest("RETR", argv[2], argv[1], mode,
289	    argv[1] != oldargv1 || argv[2] != oldargv2 || !interactive, loc);
290	restart_point = 0;
291#ifndef SMALL
292freegetit:
293#endif
294	if (oldargv2 != globargv2)	/* free up after globulize() */
295		free(globargv2);
296	return (rval);
297}
298
299/* XXX - Signal race. */
300void
301mabort(int signo)
302{
303	int save_errno = errno;
304
305	alarmtimer(0);
306	(void) write(fileno(ttyout), "\n\r", 2);
307#ifndef SMALL
308	if (mflag && fromatty) {
309		/* XXX signal race, crazy unbelievable stdio misuse */
310		if (confirm(mname, NULL)) {
311			errno = save_errno;
312			longjmp(jabort, 1);
313		}
314	}
315#endif /* !SMALL */
316	mflag = 0;
317	errno = save_errno;
318	longjmp(jabort, 1);
319}
320
321/*
322 * Get multiple files.
323 */
324void
325mget(int argc, char *argv[])
326{
327	extern int optind, optreset;
328	sig_t oldintr;
329	int xargc = 2;
330	char *cp, localcwd[PATH_MAX], *xargv[] = { argv[0], NULL, NULL };
331	static int restartit = 0;
332#ifndef SMALL
333	extern char *optarg;
334	const char *errstr;
335	int ch, i = 1;
336	char type = 0, *dummyargv[] = { argv[0], ".", NULL };
337	FILE *ftemp = NULL;
338	static int depth = 0, max_depth = 0;
339
340	optind = optreset = 1;
341
342	if (depth)
343		depth++;
344
345	while ((ch = getopt(argc, argv, "cd:nr")) != -1) {
346		switch(ch) {
347		case 'c':
348			restartit = 1;
349			break;
350		case 'd':
351			max_depth = strtonum(optarg, 0, INT_MAX, &errstr);
352			if (errstr != NULL) {
353				fprintf(ttyout, "bad depth value, %s: %s\n",
354				    errstr, optarg);
355				code = -1;
356				return;
357			}
358			break;
359		case 'n':
360			restartit = -1;
361			break;
362		case 'r':
363			depth = 1;
364			break;
365		default:
366			goto usage;
367		}
368	}
369
370	if (argc - optind < 1 && !another(&argc, &argv, "remote-files")) {
371usage:
372		fprintf(ttyout, "usage: %s [-cnr] [-d depth] remote-files\n",
373		    argv[0]);
374		code = -1;
375		return;
376	}
377
378	argv[optind - 1] = argv[0];
379	argc -= optind - 1;
380	argv += optind - 1;
381#endif /* !SMALL */
382
383	mname = argv[0];
384	mflag = 1;
385	if (getcwd(localcwd, sizeof(localcwd)) == NULL)
386		err(1, "can't get cwd");
387
388	oldintr = signal(SIGINT, mabort);
389	(void)setjmp(jabort);
390	while ((cp =
391#ifdef SMALL
392	    remglob(argv, proxy, NULL)) != NULL
393	    ) {
394#else /* SMALL */
395	    depth ? remglob2(dummyargv, proxy, NULL, &ftemp, &type) :
396	    remglob(argv, proxy, NULL)) != NULL
397	    || (mflag && depth && ++i < argc)
398	    ) {
399		if (cp == NULL)
400			continue;
401#endif /* SMALL */
402		if (*cp == '\0') {
403			mflag = 0;
404			continue;
405		}
406		if (!mflag)
407			continue;
408#ifndef SMALL
409		if (depth && fnmatch(argv[i], cp, FNM_PATHNAME) != 0)
410			continue;
411#endif /* !SMALL */
412		if (!fileindir(cp, localcwd)) {
413			fprintf(ttyout, "Skipping non-relative filename `%s'\n",
414			    cp);
415			continue;
416		}
417#ifndef SMALL
418		if (type == 'd' && depth == max_depth)
419			continue;
420		if (!confirm(argv[0], cp))
421			continue;
422		if (type == 'd') {
423			mkdir(cp, 0755);
424			if (chdir(cp) != 0) {
425				warn("local: %s", cp);
426				continue;
427			}
428
429			xargv[1] = cp;
430			cd(xargc, xargv);
431			if (dirchange != 1)
432				goto out;
433
434			xargv[1] = "*";
435			mget(xargc, xargv);
436
437			xargv[1] = "..";
438			cd(xargc, xargv);
439			if (dirchange != 1) {
440				mflag = 0;
441				goto out;
442			}
443
444out:
445			if (chdir("..") != 0) {
446				warn("local: %s", cp);
447				mflag = 0;
448			}
449			continue;
450		}
451		if (type == 's')
452			/* Currently ignored. */
453			continue;
454#endif /* !SMALL */
455		xargv[1] = cp;
456		(void)getit(xargc, xargv, restartit,
457		    (restartit == 1 || restart_point) ? "a+w" : "w");
458#ifndef SMALL
459		if (!mflag && fromatty) {
460			if (confirm(argv[0], NULL))
461				mflag = 1;
462		}
463#endif /* !SMALL */
464	}
465	(void)signal(SIGINT, oldintr);
466#ifndef SMALL
467	if (depth)
468		depth--;
469	if (depth == 0 || mflag == 0)
470		depth = max_depth = mflag = restartit = 0;
471#else /* !SMALL */
472	mflag = 0;
473#endif /* !SMALL */
474}
475
476/*
477 * Set current working directory on remote machine.
478 */
479void
480cd(int argc, char *argv[])
481{
482	int r;
483
484#ifndef SMALL
485	if ((argc < 2 && !another(&argc, &argv, "remote-directory")) ||
486	    argc > 2) {
487		fprintf(ttyout, "usage: %s remote-directory\n", argv[0]);
488		code = -1;
489		return;
490	}
491#endif /* !SMALL */
492	r = command("CWD %s", argv[1]);
493	if (r == ERROR && code == 500) {
494		if (verbose)
495			fputs("CWD command not recognized, trying XCWD.\n", ttyout);
496		r = command("XCWD %s", argv[1]);
497	}
498	if (r == ERROR && code == 550) {
499		dirchange = 0;
500		return;
501	}
502	if (r == COMPLETE)
503		dirchange = 1;
504}
505
506/*
507 * Terminate session, but don't exit.
508 */
509void
510disconnect(int argc, char *argv[])
511{
512
513	if (!connected)
514		return;
515	(void)command("QUIT");
516	if (cout) {
517		(void)fclose(cout);
518	}
519	cout = NULL;
520	connected = 0;
521	data = -1;
522#ifndef SMALL
523	if (!proxy) {
524		macnum = 0;
525	}
526#endif /* !SMALL */
527}
528
529char *
530dotrans(char *name)
531{
532	static char new[PATH_MAX];
533	char *cp1, *cp2 = new;
534	int i, ostop, found;
535
536	for (ostop = 0; *(ntout + ostop) && ostop < 16; ostop++)
537		continue;
538	for (cp1 = name; *cp1; cp1++) {
539		found = 0;
540		for (i = 0; *(ntin + i) && i < 16; i++) {
541			if (*cp1 == *(ntin + i)) {
542				found++;
543				if (i < ostop) {
544					*cp2++ = *(ntout + i);
545				}
546				break;
547			}
548		}
549		if (!found) {
550			*cp2++ = *cp1;
551		}
552	}
553	*cp2 = '\0';
554	return (new);
555}
556
557char *
558domap(char *name)
559{
560	static char new[PATH_MAX];
561	char *cp1 = name, *cp2 = mapin;
562	char *tp[9], *te[9];
563	int i, toks[9], toknum = 0, match = 1;
564
565	for (i=0; i < 9; ++i) {
566		toks[i] = 0;
567	}
568	while (match && *cp1 && *cp2) {
569		switch (*cp2) {
570			case '\\':
571				if (*++cp2 != *cp1) {
572					match = 0;
573				}
574				break;
575			case '$':
576				if (*(cp2+1) >= '1' && (*cp2+1) <= '9') {
577					if (*cp1 != *(++cp2+1)) {
578						toks[toknum = *cp2 - '1']++;
579						tp[toknum] = cp1;
580						while (*++cp1 && *(cp2+1)
581							!= *cp1);
582						te[toknum] = cp1;
583					}
584					cp2++;
585					break;
586				}
587				/* FALLTHROUGH */
588			default:
589				if (*cp2 != *cp1) {
590					match = 0;
591				}
592				break;
593		}
594		if (match && *cp1) {
595			cp1++;
596		}
597		if (match && *cp2) {
598			cp2++;
599		}
600	}
601	if (!match && *cp1) /* last token mismatch */
602	{
603		toks[toknum] = 0;
604	}
605	cp1 = new;
606	*cp1 = '\0';
607	cp2 = mapout;
608	while (*cp2) {
609		match = 0;
610		switch (*cp2) {
611			case '\\':
612				if (*(cp2 + 1)) {
613					*cp1++ = *++cp2;
614				}
615				break;
616			case '[':
617LOOP:
618				if (*++cp2 == '$' && isdigit((unsigned char)*(cp2 + 1))) {
619					if (*++cp2 == '0') {
620						char *cp3 = name;
621
622						while (*cp3) {
623							*cp1++ = *cp3++;
624						}
625						match = 1;
626					} else if (toks[toknum = *cp2 - '1']) {
627						char *cp3 = tp[toknum];
628
629						while (cp3 != te[toknum]) {
630							*cp1++ = *cp3++;
631						}
632						match = 1;
633					}
634				} else {
635					while (*cp2 && *cp2 != ',' &&
636					    *cp2 != ']') {
637						if (*cp2 == '\\') {
638							cp2++;
639						} else if (*cp2 == '$' &&
640						    isdigit((unsigned char)*(cp2 + 1))) {
641							if (*++cp2 == '0') {
642							   char *cp3 = name;
643
644							   while (*cp3) {
645								*cp1++ = *cp3++;
646							   }
647							} else if (toks[toknum =
648							    *cp2 - '1']) {
649								char *cp3=tp[toknum];
650
651								while (cp3 !=
652								    te[toknum]) {
653									*cp1++ = *cp3++;
654								}
655							}
656						} else if (*cp2) {
657							*cp1++ = *cp2++;
658						}
659					}
660					if (!*cp2) {
661						fputs(
662"nmap: unbalanced brackets.\n", ttyout);
663						return (name);
664					}
665					match = 1;
666					cp2--;
667				}
668				if (match) {
669					while (*++cp2 && *cp2 != ']') {
670						if (*cp2 == '\\' && *(cp2 + 1)) {
671							cp2++;
672						}
673					}
674					if (!*cp2) {
675						fputs(
676"nmap: unbalanced brackets.\n", ttyout);
677						return (name);
678					}
679					break;
680				}
681				switch (*++cp2) {
682					case ',':
683						goto LOOP;
684					case ']':
685						break;
686					default:
687						cp2--;
688						goto LOOP;
689				}
690				break;
691			case '$':
692				if (isdigit((unsigned char)*(cp2 + 1))) {
693					if (*++cp2 == '0') {
694						char *cp3 = name;
695
696						while (*cp3) {
697							*cp1++ = *cp3++;
698						}
699					} else if (toks[toknum = *cp2 - '1']) {
700						char *cp3 = tp[toknum];
701
702						while (cp3 != te[toknum]) {
703							*cp1++ = *cp3++;
704						}
705					}
706					break;
707				}
708				/* FALLTHROUGH */
709			default:
710				*cp1++ = *cp2;
711				break;
712		}
713		cp2++;
714	}
715	*cp1 = '\0';
716	if (!*new) {
717		return (name);
718	}
719	return (new);
720}
721
722