xref: /openbsd-src/usr.bin/ftp/small.c (revision fb8aa7497fded39583f40e800732f9c046411717)
1 /*	$OpenBSD: small.c,v 1.6 2016/05/25 15:36:01 krw 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 
86 jmp_buf	jabort;
87 char   *mname;
88 char   *home = "/";
89 
90 struct	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  */
104 void
105 settype(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  */
148 void
149 changetype(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 
180 char *stype[] = {
181 	"type",
182 	"",
183 	0
184 };
185 
186 /*
187  * Set binary transfer type.
188  */
189 /*ARGSUSED*/
190 void
191 setbinary(int argc, char *argv[])
192 {
193 
194 	stype[1] = "binary";
195 	settype(2, stype);
196 }
197 
198 void
199 get(int argc, char *argv[])
200 {
201 
202 	(void)getit(argc, argv, 0, restart_point ? "a+w" : "w" );
203 }
204 
205 /*
206  * Receive one file.
207  */
208 int
209 getit(int argc, char *argv[], int restartit, const char *mode)
210 {
211 	int loc = 0;
212 	int rval = 0;
213 	char *oldargv1, *oldargv2, *globargv2;
214 
215 	if (argc == 2) {
216 		argc++;
217 		argv[2] = argv[1];
218 		loc++;
219 	}
220 #ifndef SMALL
221 	if (argc < 2 && !another(&argc, &argv, "remote-file"))
222 		goto usage;
223 	if ((argc < 3 && !another(&argc, &argv, "local-file")) || argc > 3) {
224 usage:
225 		fprintf(ttyout, "usage: %s remote-file [local-file]\n",
226 		    argv[0]);
227 		code = -1;
228 		return (0);
229 	}
230 #endif /* !SMALL */
231 	oldargv1 = argv[1];
232 	oldargv2 = argv[2];
233 	if (!globulize(&argv[2])) {
234 		code = -1;
235 		return (0);
236 	}
237 	globargv2 = argv[2];
238 	if (loc && mcase) {
239 		char *tp = argv[1], *tp2, tmpbuf[PATH_MAX];
240 
241 		while (*tp && !islower((unsigned char)*tp)) {
242 			tp++;
243 		}
244 		if (!*tp) {
245 			tp = argv[2];
246 			tp2 = tmpbuf;
247 			while ((*tp2 = *tp) != '\0') {
248 				if (isupper((unsigned char)*tp2)) {
249 					*tp2 = tolower((unsigned char)*tp2);
250 				}
251 				tp++;
252 				tp2++;
253 			}
254 			argv[2] = tmpbuf;
255 		}
256 	}
257 	if (loc && ntflag)
258 		argv[2] = dotrans(argv[2]);
259 	if (loc && mapflag)
260 		argv[2] = domap(argv[2]);
261 #ifndef SMALL
262 	if (restartit) {
263 		struct stat stbuf;
264 		int ret;
265 
266 		ret = stat(argv[2], &stbuf);
267 		if (restartit == 1) {
268 			restart_point = (ret < 0) ? 0 : stbuf.st_size;
269 		} else {
270 			if (ret == 0) {
271 				time_t mtime;
272 
273 				mtime = remotemodtime(argv[1], 0);
274 				if (mtime == -1)
275 					goto freegetit;
276 				if (stbuf.st_mtime >= mtime) {
277 					rval = 1;
278 					fprintf(ttyout,
279 						"Local file \"%s\" is newer "\
280 						"than remote file \"%s\".\n",
281 						argv[2], argv[1]);
282 					goto freegetit;
283 				}
284 			}
285 		}
286 	}
287 #endif /* !SMALL */
288 
289 	recvrequest("RETR", argv[2], argv[1], mode,
290 	    argv[1] != oldargv1 || argv[2] != oldargv2 || !interactive, loc);
291 	restart_point = 0;
292 freegetit:
293 	if (oldargv2 != globargv2)	/* free up after globulize() */
294 		free(globargv2);
295 	return (rval);
296 }
297 
298 /* XXX - Signal race. */
299 /* ARGSUSED */
300 void
301 mabort(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  */
324 void
325 mget(int argc, char *argv[])
326 {
327 	extern int optind, optreset;
328 	sig_t oldintr;
329 	int ch, 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 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")) {
371 usage:
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 
444 out:
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  */
479 void
480 cd(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  */
509 /* ARGSUSED */
510 void
511 disconnect(int argc, char *argv[])
512 {
513 
514 	if (!connected)
515 		return;
516 	(void)command("QUIT");
517 	if (cout) {
518 		(void)fclose(cout);
519 	}
520 	cout = NULL;
521 	connected = 0;
522 	data = -1;
523 #ifndef SMALL
524 	if (!proxy) {
525 		macnum = 0;
526 	}
527 #endif /* !SMALL */
528 }
529 
530 char *
531 dotrans(char *name)
532 {
533 	static char new[PATH_MAX];
534 	char *cp1, *cp2 = new;
535 	int i, ostop, found;
536 
537 	for (ostop = 0; *(ntout + ostop) && ostop < 16; ostop++)
538 		continue;
539 	for (cp1 = name; *cp1; cp1++) {
540 		found = 0;
541 		for (i = 0; *(ntin + i) && i < 16; i++) {
542 			if (*cp1 == *(ntin + i)) {
543 				found++;
544 				if (i < ostop) {
545 					*cp2++ = *(ntout + i);
546 				}
547 				break;
548 			}
549 		}
550 		if (!found) {
551 			*cp2++ = *cp1;
552 		}
553 	}
554 	*cp2 = '\0';
555 	return (new);
556 }
557 
558 char *
559 domap(char *name)
560 {
561 	static char new[PATH_MAX];
562 	char *cp1 = name, *cp2 = mapin;
563 	char *tp[9], *te[9];
564 	int i, toks[9], toknum = 0, match = 1;
565 
566 	for (i=0; i < 9; ++i) {
567 		toks[i] = 0;
568 	}
569 	while (match && *cp1 && *cp2) {
570 		switch (*cp2) {
571 			case '\\':
572 				if (*++cp2 != *cp1) {
573 					match = 0;
574 				}
575 				break;
576 			case '$':
577 				if (*(cp2+1) >= '1' && (*cp2+1) <= '9') {
578 					if (*cp1 != *(++cp2+1)) {
579 						toks[toknum = *cp2 - '1']++;
580 						tp[toknum] = cp1;
581 						while (*++cp1 && *(cp2+1)
582 							!= *cp1);
583 						te[toknum] = cp1;
584 					}
585 					cp2++;
586 					break;
587 				}
588 				/* FALLTHROUGH */
589 			default:
590 				if (*cp2 != *cp1) {
591 					match = 0;
592 				}
593 				break;
594 		}
595 		if (match && *cp1) {
596 			cp1++;
597 		}
598 		if (match && *cp2) {
599 			cp2++;
600 		}
601 	}
602 	if (!match && *cp1) /* last token mismatch */
603 	{
604 		toks[toknum] = 0;
605 	}
606 	cp1 = new;
607 	*cp1 = '\0';
608 	cp2 = mapout;
609 	while (*cp2) {
610 		match = 0;
611 		switch (*cp2) {
612 			case '\\':
613 				if (*(cp2 + 1)) {
614 					*cp1++ = *++cp2;
615 				}
616 				break;
617 			case '[':
618 LOOP:
619 				if (*++cp2 == '$' && isdigit((unsigned char)*(cp2 + 1))) {
620 					if (*++cp2 == '0') {
621 						char *cp3 = name;
622 
623 						while (*cp3) {
624 							*cp1++ = *cp3++;
625 						}
626 						match = 1;
627 					}
628 					else if (toks[toknum = *cp2 - '1']) {
629 						char *cp3 = tp[toknum];
630 
631 						while (cp3 != te[toknum]) {
632 							*cp1++ = *cp3++;
633 						}
634 						match = 1;
635 					}
636 				}
637 				else {
638 					while (*cp2 && *cp2 != ',' &&
639 					    *cp2 != ']') {
640 						if (*cp2 == '\\') {
641 							cp2++;
642 						}
643 						else if (*cp2 == '$' &&
644    						        isdigit((unsigned char)*(cp2 + 1))) {
645 							if (*++cp2 == '0') {
646 							   char *cp3 = name;
647 
648 							   while (*cp3) {
649 								*cp1++ = *cp3++;
650 							   }
651 							}
652 							else if (toks[toknum =
653 							    *cp2 - '1']) {
654 							   char *cp3=tp[toknum];
655 
656 							   while (cp3 !=
657 								  te[toknum]) {
658 								*cp1++ = *cp3++;
659 							   }
660 							}
661 						}
662 						else if (*cp2) {
663 							*cp1++ = *cp2++;
664 						}
665 					}
666 					if (!*cp2) {
667 						fputs(
668 "nmap: unbalanced brackets.\n", ttyout);
669 						return (name);
670 					}
671 					match = 1;
672 					cp2--;
673 				}
674 				if (match) {
675 					while (*++cp2 && *cp2 != ']') {
676 					      if (*cp2 == '\\' && *(cp2 + 1)) {
677 							cp2++;
678 					      }
679 					}
680 					if (!*cp2) {
681 						fputs(
682 "nmap: unbalanced brackets.\n", ttyout);
683 						return (name);
684 					}
685 					break;
686 				}
687 				switch (*++cp2) {
688 					case ',':
689 						goto LOOP;
690 					case ']':
691 						break;
692 					default:
693 						cp2--;
694 						goto LOOP;
695 				}
696 				break;
697 			case '$':
698 				if (isdigit((unsigned char)*(cp2 + 1))) {
699 					if (*++cp2 == '0') {
700 						char *cp3 = name;
701 
702 						while (*cp3) {
703 							*cp1++ = *cp3++;
704 						}
705 					}
706 					else if (toks[toknum = *cp2 - '1']) {
707 						char *cp3 = tp[toknum];
708 
709 						while (cp3 != te[toknum]) {
710 							*cp1++ = *cp3++;
711 						}
712 					}
713 					break;
714 				}
715 				/* FALLTHROUGH */
716 			default:
717 				*cp1++ = *cp2;
718 				break;
719 		}
720 		cp2++;
721 	}
722 	*cp1 = '\0';
723 	if (!*new) {
724 		return (name);
725 	}
726 	return (new);
727 }
728 
729