xref: /freebsd/usr.bin/tftp/main.c (revision ee2ea5ceafed78a5bd9810beb9e3ca927180c226)
1 /*
2  * Copyright (c) 1983, 1993
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *	This product includes software developed by the University of
16  *	California, Berkeley and its contributors.
17  * 4. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33 
34 #ifndef lint
35 static const char copyright[] =
36 "@(#) Copyright (c) 1983, 1993\n\
37 	The Regents of the University of California.  All rights reserved.\n";
38 #endif
39 
40 #if 0
41 #ifndef lint
42 static char sccsid[] = "@(#)main.c	8.1 (Berkeley) 6/6/93";
43 #endif
44 #endif
45 
46 #include <sys/cdefs.h>
47 __FBSDID("$FreeBSD$");
48 
49 /* Many bug fixes are from Jim Guyton <guyton@rand-unix> */
50 
51 /*
52  * TFTP User Program -- Command Interface.
53  */
54 #include <sys/param.h>
55 #include <sys/types.h>
56 #include <sys/socket.h>
57 #include <sys/file.h>
58 #include <sys/param.h>
59 
60 #include <netinet/in.h>
61 
62 #include <arpa/inet.h>
63 
64 #include <ctype.h>
65 #include <err.h>
66 #include <histedit.h>
67 #include <netdb.h>
68 #include <setjmp.h>
69 #include <signal.h>
70 #include <stdio.h>
71 #include <stdlib.h>
72 #include <string.h>
73 #include <unistd.h>
74 
75 #include "extern.h"
76 
77 #define	MAXLINE		200
78 #define	TIMEOUT		5		/* secs between rexmt's */
79 
80 struct	sockaddr_storage peeraddr;
81 int	f;
82 int	trace;
83 int	verbose;
84 int	connected;
85 char	mode[32];
86 char	line[MAXLINE];
87 int	margc;
88 char	*margv[20];
89 jmp_buf	toplevel;
90 
91 void	get(int, char **);
92 void	help(int, char **);
93 void	intr(int);
94 void	modecmd(int, char **);
95 void	put(int, char **);
96 void	quit(int, char **);
97 void	setascii(int, char **);
98 void	setbinary(int, char **);
99 void	setpeer0(char *, const char *);
100 void	setpeer(int, char **);
101 void	setrexmt(int, char **);
102 void	settimeout(int, char **);
103 void	settrace(int, char **);
104 void	setverbose(int, char **);
105 void	status(int, char **);
106 
107 static void command(void) __dead2;
108 static const char *command_prompt(void);
109 
110 static void getusage(char *);
111 static void makeargv(void);
112 static void putusage(char *);
113 static void settftpmode(const char *);
114 
115 char	*tail(char *);
116 struct	cmd *getcmd(char *);
117 
118 #define HELPINDENT (sizeof("connect"))
119 
120 struct cmd {
121 	const char	*name;
122 	char	*help;
123 	void	(*handler)(int, char **);
124 };
125 
126 char	vhelp[] = "toggle verbose mode";
127 char	thelp[] = "toggle packet tracing";
128 char	chelp[] = "connect to remote tftp";
129 char	qhelp[] = "exit tftp";
130 char	hhelp[] = "print help information";
131 char	shelp[] = "send file";
132 char	rhelp[] = "receive file";
133 char	mhelp[] = "set file transfer mode";
134 char	sthelp[] = "show current status";
135 char	xhelp[] = "set per-packet retransmission timeout";
136 char	ihelp[] = "set total retransmission timeout";
137 char    ashelp[] = "set mode to netascii";
138 char    bnhelp[] = "set mode to octet";
139 
140 struct cmd cmdtab[] = {
141 	{ "connect",	chelp,		setpeer },
142 	{ "mode",       mhelp,          modecmd },
143 	{ "put",	shelp,		put },
144 	{ "get",	rhelp,		get },
145 	{ "quit",	qhelp,		quit },
146 	{ "verbose",	vhelp,		setverbose },
147 	{ "trace",	thelp,		settrace },
148 	{ "status",	sthelp,		status },
149 	{ "binary",     bnhelp,         setbinary },
150 	{ "ascii",      ashelp,         setascii },
151 	{ "rexmt",	xhelp,		setrexmt },
152 	{ "timeout",	ihelp,		settimeout },
153 	{ "?",		hhelp,		help },
154 	{ NULL, NULL, NULL }
155 };
156 
157 int
158 main(argc, argv)
159 	int argc;
160 	char *argv[];
161 {
162 	f = -1;
163 	strcpy(mode, "netascii");
164 	signal(SIGINT, intr);
165 	if (argc > 1) {
166 		if (setjmp(toplevel) != 0)
167 			exit(0);
168 		setpeer(argc, argv);
169 	}
170 	if (setjmp(toplevel) != 0)
171 		(void)putchar('\n');
172 	command();
173 }
174 
175 char    hostname[MAXHOSTNAMELEN];
176 
177 void
178 setpeer0(host, port)
179 	char *host;
180 	const char *port;
181 {
182 	struct addrinfo hints, *res0, *res;
183 	int error;
184 	struct sockaddr_storage ss;
185 	const char *cause = "unknown";
186 
187 	if (connected) {
188 		close(f);
189 		f = -1;
190 	}
191 	connected = 0;
192 
193 	memset(&hints, 0, sizeof(hints));
194 	hints.ai_family = PF_UNSPEC;
195 	hints.ai_socktype = SOCK_DGRAM;
196 	hints.ai_protocol = IPPROTO_UDP;
197 	hints.ai_flags = AI_CANONNAME;
198 	if (!port)
199 		port = "tftp";
200 	error = getaddrinfo(host, port, &hints, &res0);
201 	if (error) {
202 		warnx("%s", gai_strerror(error));
203 		return;
204 	}
205 
206 	for (res = res0; res; res = res->ai_next) {
207 		if (res->ai_addrlen > sizeof(peeraddr))
208 			continue;
209 		f = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
210 		if (f < 0) {
211 			cause = "socket";
212 			continue;
213 		}
214 
215 		memset(&ss, 0, sizeof(ss));
216 		ss.ss_family = res->ai_family;
217 		ss.ss_len = res->ai_addrlen;
218 		if (bind(f, (struct sockaddr *)&ss, ss.ss_len) < 0) {
219 			cause = "bind";
220 			close(f);
221 			f = -1;
222 			continue;
223 		}
224 
225 		break;
226 	}
227 
228 	if (f < 0)
229 		warn("%s", cause);
230 	else {
231 		/* res->ai_addr <= sizeof(peeraddr) is guaranteed */
232 		memcpy(&peeraddr, res->ai_addr, res->ai_addrlen);
233 		if (res->ai_canonname) {
234 			(void) strncpy(hostname, res->ai_canonname,
235 				sizeof(hostname));
236 		} else
237 			(void) strncpy(hostname, host, sizeof(hostname));
238 		hostname[sizeof(hostname)-1] = 0;
239 		connected = 1;
240 	}
241 
242 	freeaddrinfo(res0);
243 }
244 
245 void
246 setpeer(argc, argv)
247 	int argc;
248 	char *argv[];
249 {
250 
251 	if (argc < 2) {
252 		strcpy(line, "Connect ");
253 		printf("(to) ");
254 		fgets(&line[strlen(line)], sizeof line - strlen(line), stdin);
255 		makeargv();
256 		argc = margc;
257 		argv = margv;
258 	}
259 	if ((argc < 2) || (argc > 3)) {
260 		printf("usage: %s host-name [port]\n", argv[0]);
261 		return;
262 	}
263 	if (argc == 3)
264 		setpeer0(argv[1], NULL);
265 	else
266 		setpeer0(argv[1], argv[2]);
267 }
268 
269 struct	modes {
270 	const char *m_name;
271 	const char *m_mode;
272 } modes[] = {
273 	{ "ascii",	"netascii" },
274 	{ "netascii",   "netascii" },
275 	{ "binary",     "octet" },
276 	{ "image",      "octet" },
277 	{ "octet",     "octet" },
278 /*      { "mail",       "mail" },       */
279 	{ 0,		0 }
280 };
281 
282 void
283 modecmd(argc, argv)
284 	int argc;
285 	char *argv[];
286 {
287 	struct modes *p;
288 	const char *sep;
289 
290 	if (argc < 2) {
291 		printf("Using %s mode to transfer files.\n", mode);
292 		return;
293 	}
294 	if (argc == 2) {
295 		for (p = modes; p->m_name; p++)
296 			if (strcmp(argv[1], p->m_name) == 0)
297 				break;
298 		if (p->m_name) {
299 			settftpmode(p->m_mode);
300 			return;
301 		}
302 		printf("%s: unknown mode\n", argv[1]);
303 		/* drop through and print usage message */
304 	}
305 
306 	printf("usage: %s [", argv[0]);
307 	sep = " ";
308 	for (p = modes; p->m_name; p++) {
309 		printf("%s%s", sep, p->m_name);
310 		if (*sep == ' ')
311 			sep = " | ";
312 	}
313 	printf(" ]\n");
314 	return;
315 }
316 
317 void
318 setbinary(argc, argv)
319 	int argc __unused;
320 	char *argv[] __unused;
321 {
322 
323 	settftpmode("octet");
324 }
325 
326 void
327 setascii(argc, argv)
328 	int argc __unused;
329 	char *argv[] __unused;
330 {
331 
332 	settftpmode("netascii");
333 }
334 
335 static void
336 settftpmode(newmode)
337 	const char *newmode;
338 {
339 	strcpy(mode, newmode);
340 	if (verbose)
341 		printf("mode set to %s\n", mode);
342 }
343 
344 
345 /*
346  * Send file(s).
347  */
348 void
349 put(argc, argv)
350 	int argc;
351 	char *argv[];
352 {
353 	int fd;
354 	int n;
355 	char *cp, *targ;
356 
357 	if (argc < 2) {
358 		strcpy(line, "send ");
359 		printf("(file) ");
360 		fgets(&line[strlen(line)], sizeof line - strlen(line), stdin);
361 		makeargv();
362 		argc = margc;
363 		argv = margv;
364 	}
365 	if (argc < 2) {
366 		putusage(argv[0]);
367 		return;
368 	}
369 	targ = argv[argc - 1];
370 	if (rindex(argv[argc - 1], ':')) {
371 		char *lcp;
372 
373 		for (n = 1; n < argc - 1; n++)
374 			if (index(argv[n], ':')) {
375 				putusage(argv[0]);
376 				return;
377 			}
378 		lcp = argv[argc - 1];
379 		targ = rindex(lcp, ':');
380 		*targ++ = 0;
381 		if (lcp[0] == '[' && lcp[strlen(lcp) - 1] == ']') {
382 			lcp[strlen(lcp) - 1] = '\0';
383 			lcp++;
384 		}
385 		setpeer0(lcp, NULL);
386 	}
387 	if (!connected) {
388 		printf("No target machine specified.\n");
389 		return;
390 	}
391 	if (argc < 4) {
392 		cp = argc == 2 ? tail(targ) : argv[1];
393 		fd = open(cp, O_RDONLY);
394 		if (fd < 0) {
395 			warn("%s", cp);
396 			return;
397 		}
398 		if (verbose)
399 			printf("putting %s to %s:%s [%s]\n",
400 				cp, hostname, targ, mode);
401 		xmitfile(fd, targ, mode);
402 		return;
403 	}
404 				/* this assumes the target is a directory */
405 				/* on a remote unix system.  hmmmm.  */
406 	cp = index(targ, '\0');
407 	*cp++ = '/';
408 	for (n = 1; n < argc - 1; n++) {
409 		strcpy(cp, tail(argv[n]));
410 		fd = open(argv[n], O_RDONLY);
411 		if (fd < 0) {
412 			warn("%s", argv[n]);
413 			continue;
414 		}
415 		if (verbose)
416 			printf("putting %s to %s:%s [%s]\n",
417 				argv[n], hostname, targ, mode);
418 		xmitfile(fd, targ, mode);
419 	}
420 }
421 
422 static void
423 putusage(s)
424 	char *s;
425 {
426 	printf("usage: %s file ... host:target, or\n", s);
427 	printf("       %s file ... target (when already connected)\n", s);
428 }
429 
430 /*
431  * Receive file(s).
432  */
433 void
434 get(argc, argv)
435 	int argc;
436 	char *argv[];
437 {
438 	int fd;
439 	int n;
440 	char *cp;
441 	char *src;
442 
443 	if (argc < 2) {
444 		strcpy(line, "get ");
445 		printf("(files) ");
446 		fgets(&line[strlen(line)], sizeof line - strlen(line), stdin);
447 		makeargv();
448 		argc = margc;
449 		argv = margv;
450 	}
451 	if (argc < 2) {
452 		getusage(argv[0]);
453 		return;
454 	}
455 	if (!connected) {
456 		for (n = 1; n < argc ; n++)
457 			if (rindex(argv[n], ':') == 0) {
458 				getusage(argv[0]);
459 				return;
460 			}
461 	}
462 	for (n = 1; n < argc ; n++) {
463 		src = rindex(argv[n], ':');
464 		if (src == NULL)
465 			src = argv[n];
466 		else {
467 			char *lcp;
468 
469 			*src++ = 0;
470 			lcp = argv[n];
471 			if (lcp[0] == '[' && lcp[strlen(lcp) - 1] == ']') {
472 				lcp[strlen(lcp) - 1] = '\0';
473 				lcp++;
474 			}
475 			setpeer0(lcp, NULL);
476 			if (!connected)
477 				continue;
478 		}
479 		if (argc < 4) {
480 			cp = argc == 3 ? argv[2] : tail(src);
481 			fd = creat(cp, 0644);
482 			if (fd < 0) {
483 				warn("%s", cp);
484 				return;
485 			}
486 			if (verbose)
487 				printf("getting from %s:%s to %s [%s]\n",
488 					hostname, src, cp, mode);
489 			recvfile(fd, src, mode);
490 			break;
491 		}
492 		cp = tail(src);         /* new .. jdg */
493 		fd = creat(cp, 0644);
494 		if (fd < 0) {
495 			warn("%s", cp);
496 			continue;
497 		}
498 		if (verbose)
499 			printf("getting from %s:%s to %s [%s]\n",
500 				hostname, src, cp, mode);
501 		recvfile(fd, src, mode);
502 	}
503 }
504 
505 static void
506 getusage(s)
507 	char *s;
508 {
509 	printf("usage: %s host:file host:file ... file, or\n", s);
510 	printf("       %s file file ... file if connected\n", s);
511 }
512 
513 int	rexmtval = TIMEOUT;
514 
515 void
516 setrexmt(argc, argv)
517 	int argc;
518 	char *argv[];
519 {
520 	int t;
521 
522 	if (argc < 2) {
523 		strcpy(line, "Rexmt-timeout ");
524 		printf("(value) ");
525 		fgets(&line[strlen(line)], sizeof line - strlen(line), stdin);
526 		makeargv();
527 		argc = margc;
528 		argv = margv;
529 	}
530 	if (argc != 2) {
531 		printf("usage: %s value\n", argv[0]);
532 		return;
533 	}
534 	t = atoi(argv[1]);
535 	if (t < 0)
536 		printf("%s: bad value\n", argv[1]);
537 	else
538 		rexmtval = t;
539 }
540 
541 int	maxtimeout = 5 * TIMEOUT;
542 
543 void
544 settimeout(argc, argv)
545 	int argc;
546 	char *argv[];
547 {
548 	int t;
549 
550 	if (argc < 2) {
551 		strcpy(line, "Maximum-timeout ");
552 		printf("(value) ");
553 		fgets(&line[strlen(line)], sizeof line - strlen(line), stdin);
554 		makeargv();
555 		argc = margc;
556 		argv = margv;
557 	}
558 	if (argc != 2) {
559 		printf("usage: %s value\n", argv[0]);
560 		return;
561 	}
562 	t = atoi(argv[1]);
563 	if (t < 0)
564 		printf("%s: bad value\n", argv[1]);
565 	else
566 		maxtimeout = t;
567 }
568 
569 void
570 status(argc, argv)
571 	int argc __unused;
572 	char *argv[] __unused;
573 {
574 	if (connected)
575 		printf("Connected to %s.\n", hostname);
576 	else
577 		printf("Not connected.\n");
578 	printf("Mode: %s Verbose: %s Tracing: %s\n", mode,
579 		verbose ? "on" : "off", trace ? "on" : "off");
580 	printf("Rexmt-interval: %d seconds, Max-timeout: %d seconds\n",
581 		rexmtval, maxtimeout);
582 }
583 
584 void
585 intr(dummy)
586 	int dummy __unused;
587 {
588 
589 	signal(SIGALRM, SIG_IGN);
590 	alarm(0);
591 	longjmp(toplevel, -1);
592 }
593 
594 char *
595 tail(filename)
596 	char *filename;
597 {
598 	char *s;
599 
600 	while (*filename) {
601 		s = rindex(filename, '/');
602 		if (s == NULL)
603 			break;
604 		if (s[1])
605 			return (s + 1);
606 		*s = '\0';
607 	}
608 	return (filename);
609 }
610 
611 static const char *
612 command_prompt()
613 {
614 
615 	return ("tftp> ");
616 }
617 
618 /*
619  * Command parser.
620  */
621 static void
622 command()
623 {
624 	HistEvent he;
625 	struct cmd *c;
626 	static EditLine *el;
627 	static History *hist;
628 	const char *bp;
629 	char *cp;
630 	int len, num, vrbose;
631 
632 	vrbose = isatty(0);
633 	if (vrbose) {
634 		el = el_init("tftp", stdin, stdout, stderr);
635 		hist = history_init();
636 		history(hist, &he, H_EVENT, 100);
637 		el_set(el, EL_HIST, history, hist);
638 		el_set(el, EL_EDITOR, "emacs");
639 		el_set(el, EL_PROMPT, command_prompt);
640 		el_set(el, EL_SIGNAL, 1);
641 		el_source(el, NULL);
642 	}
643 	for (;;) {
644 		if (vrbose) {
645                         if ((bp = el_gets(el, &num)) == NULL || num == 0)
646                                 exit(0);
647                         len = (num > MAXLINE) ? MAXLINE : num;
648                         memcpy(line, bp, len);
649                         line[len] = '\0';
650                         history(hist, &he, H_ENTER, bp);
651 		} else {
652 			if (fgets(line, sizeof line , stdin) == 0) {
653 				if (feof(stdin)) {
654 					exit(0);
655 				} else {
656 					continue;
657 				}
658 			}
659 		}
660 		if ((cp = strchr(line, '\n')))
661 			*cp = '\0';
662 		if (line[0] == 0)
663 			continue;
664 		makeargv();
665 		if (margc == 0)
666 			continue;
667 		c = getcmd(margv[0]);
668 		if (c == (struct cmd *)-1) {
669 			printf("?Ambiguous command\n");
670 			continue;
671 		}
672 		if (c == 0) {
673 			printf("?Invalid command\n");
674 			continue;
675 		}
676 		(*c->handler)(margc, margv);
677 	}
678 }
679 
680 struct cmd *
681 getcmd(name)
682 	char *name;
683 {
684 	const char *p, *q;
685 	struct cmd *c, *found;
686 	int nmatches, longest;
687 
688 	longest = 0;
689 	nmatches = 0;
690 	found = 0;
691 	for (c = cmdtab; (p = c->name) != NULL; c++) {
692 		for (q = name; *q == *p++; q++)
693 			if (*q == 0)		/* exact match? */
694 				return (c);
695 		if (!*q) {			/* the name was a prefix */
696 			if (q - name > longest) {
697 				longest = q - name;
698 				nmatches = 1;
699 				found = c;
700 			} else if (q - name == longest)
701 				nmatches++;
702 		}
703 	}
704 	if (nmatches > 1)
705 		return ((struct cmd *)-1);
706 	return (found);
707 }
708 
709 /*
710  * Slice a string up into argc/argv.
711  */
712 static void
713 makeargv()
714 {
715 	char *cp;
716 	char **argp = margv;
717 
718 	margc = 0;
719 	if ((cp = strchr(line, '\n')))
720 		*cp = '\0';
721 	for (cp = line; *cp;) {
722 		while (isspace(*cp))
723 			cp++;
724 		if (*cp == '\0')
725 			break;
726 		*argp++ = cp;
727 		margc += 1;
728 		while (*cp != '\0' && !isspace(*cp))
729 			cp++;
730 		if (*cp == '\0')
731 			break;
732 		*cp++ = '\0';
733 	}
734 	*argp++ = 0;
735 }
736 
737 void
738 quit(argc, argv)
739 	int argc __unused;
740 	char *argv[] __unused;
741 {
742 
743 	exit(0);
744 }
745 
746 /*
747  * Help command.
748  */
749 void
750 help(argc, argv)
751 	int argc;
752 	char *argv[];
753 {
754 	struct cmd *c;
755 
756 	if (argc == 1) {
757 		printf("Commands may be abbreviated.  Commands are:\n\n");
758 		for (c = cmdtab; c->name; c++)
759 			printf("%-*s\t%s\n", (int)HELPINDENT, c->name, c->help);
760 		return;
761 	}
762 	while (--argc > 0) {
763 		char *arg;
764 		arg = *++argv;
765 		c = getcmd(arg);
766 		if (c == (struct cmd *)-1)
767 			printf("?Ambiguous help command %s\n", arg);
768 		else if (c == (struct cmd *)0)
769 			printf("?Invalid help command %s\n", arg);
770 		else
771 			printf("%s\n", c->help);
772 	}
773 }
774 
775 void
776 settrace(argc, argv)
777 	int argc __unused;
778 	char **argv __unused;
779 {
780 	trace = !trace;
781 	printf("Packet tracing %s.\n", trace ? "on" : "off");
782 }
783 
784 void
785 setverbose(argc, argv)
786 	int argc __unused;
787 	char **argv __unused;
788 {
789 	verbose = !verbose;
790 	printf("Verbose mode %s.\n", verbose ? "on" : "off");
791 }
792