xref: /freebsd/usr.bin/tftp/main.c (revision 1e413cf93298b5b97441a21d9a50fdcd0ee9945e)
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 #define	MAX_MARGV	20
89 char	*margv[MAX_MARGV];
90 jmp_buf	toplevel;
91 volatile int txrx_error;
92 
93 void	get(int, char **);
94 void	help(int, char **);
95 void	intr(int);
96 void	modecmd(int, char **);
97 void	put(int, char **);
98 void	quit(int, char **);
99 void	setascii(int, char **);
100 void	setbinary(int, char **);
101 void	setpeer0(char *, const char *);
102 void	setpeer(int, char **);
103 void	setrexmt(int, char **);
104 void	settimeout(int, char **);
105 void	settrace(int, char **);
106 void	setverbose(int, char **);
107 void	status(int, char **);
108 
109 static void command(void) __dead2;
110 static const char *command_prompt(void);
111 
112 static void getusage(char *);
113 static void makeargv(void);
114 static void putusage(char *);
115 static void settftpmode(const char *);
116 
117 char	*tail(char *);
118 struct	cmd *getcmd(char *);
119 
120 #define HELPINDENT (sizeof("connect"))
121 
122 struct cmd {
123 	const char	*name;
124 	char	*help;
125 	void	(*handler)(int, char **);
126 };
127 
128 char	vhelp[] = "toggle verbose mode";
129 char	thelp[] = "toggle packet tracing";
130 char	chelp[] = "connect to remote tftp";
131 char	qhelp[] = "exit tftp";
132 char	hhelp[] = "print help information";
133 char	shelp[] = "send file";
134 char	rhelp[] = "receive file";
135 char	mhelp[] = "set file transfer mode";
136 char	sthelp[] = "show current status";
137 char	xhelp[] = "set per-packet retransmission timeout";
138 char	ihelp[] = "set total retransmission timeout";
139 char    ashelp[] = "set mode to netascii";
140 char    bnhelp[] = "set mode to octet";
141 
142 struct cmd cmdtab[] = {
143 	{ "connect",	chelp,		setpeer },
144 	{ "mode",       mhelp,          modecmd },
145 	{ "put",	shelp,		put },
146 	{ "get",	rhelp,		get },
147 	{ "quit",	qhelp,		quit },
148 	{ "verbose",	vhelp,		setverbose },
149 	{ "trace",	thelp,		settrace },
150 	{ "status",	sthelp,		status },
151 	{ "binary",     bnhelp,         setbinary },
152 	{ "ascii",      ashelp,         setascii },
153 	{ "rexmt",	xhelp,		setrexmt },
154 	{ "timeout",	ihelp,		settimeout },
155 	{ "?",		hhelp,		help },
156 	{ NULL, NULL, NULL }
157 };
158 
159 int
160 main(argc, argv)
161 	int argc;
162 	char *argv[];
163 {
164 	f = -1;
165 	strcpy(mode, "netascii");
166 	signal(SIGINT, intr);
167 	if (argc > 1) {
168 		if (setjmp(toplevel) != 0)
169 			exit(txrx_error);
170 		setpeer(argc, argv);
171 	}
172 	if (setjmp(toplevel) != 0)
173 		(void)putchar('\n');
174 	command();
175 }
176 
177 char    hostname[MAXHOSTNAMELEN];
178 
179 void
180 setpeer0(host, port)
181 	char *host;
182 	const char *port;
183 {
184 	struct addrinfo hints, *res0, *res;
185 	int error;
186 	struct sockaddr_storage ss;
187 	const char *cause = "unknown";
188 
189 	if (connected) {
190 		close(f);
191 		f = -1;
192 	}
193 	connected = 0;
194 
195 	memset(&hints, 0, sizeof(hints));
196 	hints.ai_family = PF_UNSPEC;
197 	hints.ai_socktype = SOCK_DGRAM;
198 	hints.ai_protocol = IPPROTO_UDP;
199 	hints.ai_flags = AI_CANONNAME;
200 	if (!port)
201 		port = "tftp";
202 	error = getaddrinfo(host, port, &hints, &res0);
203 	if (error) {
204 		warnx("%s", gai_strerror(error));
205 		return;
206 	}
207 
208 	for (res = res0; res; res = res->ai_next) {
209 		if (res->ai_addrlen > sizeof(peeraddr))
210 			continue;
211 		f = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
212 		if (f < 0) {
213 			cause = "socket";
214 			continue;
215 		}
216 
217 		memset(&ss, 0, sizeof(ss));
218 		ss.ss_family = res->ai_family;
219 		ss.ss_len = res->ai_addrlen;
220 		if (bind(f, (struct sockaddr *)&ss, ss.ss_len) < 0) {
221 			cause = "bind";
222 			close(f);
223 			f = -1;
224 			continue;
225 		}
226 
227 		break;
228 	}
229 
230 	if (f < 0)
231 		warn("%s", cause);
232 	else {
233 		/* res->ai_addr <= sizeof(peeraddr) is guaranteed */
234 		memcpy(&peeraddr, res->ai_addr, res->ai_addrlen);
235 		if (res->ai_canonname) {
236 			(void) strncpy(hostname, res->ai_canonname,
237 				sizeof(hostname));
238 		} else
239 			(void) strncpy(hostname, host, sizeof(hostname));
240 		hostname[sizeof(hostname)-1] = 0;
241 		connected = 1;
242 	}
243 
244 	freeaddrinfo(res0);
245 }
246 
247 void
248 setpeer(argc, argv)
249 	int argc;
250 	char *argv[];
251 {
252 
253 	if (argc < 2) {
254 		strcpy(line, "Connect ");
255 		printf("(to) ");
256 		fgets(&line[strlen(line)], sizeof line - strlen(line), stdin);
257 		makeargv();
258 		argc = margc;
259 		argv = margv;
260 	}
261 	if ((argc < 2) || (argc > 3)) {
262 		printf("usage: %s [host [port]]\n", argv[0]);
263 		return;
264 	}
265 	if (argc == 3)
266 		setpeer0(argv[1], argv[2]);
267 	else
268 		setpeer0(argv[1], NULL);
269 }
270 
271 struct	modes {
272 	const char *m_name;
273 	const char *m_mode;
274 } modes[] = {
275 	{ "ascii",	"netascii" },
276 	{ "netascii",   "netascii" },
277 	{ "binary",     "octet" },
278 	{ "image",      "octet" },
279 	{ "octet",     "octet" },
280 /*      { "mail",       "mail" },       */
281 	{ 0,		0 }
282 };
283 
284 void
285 modecmd(argc, argv)
286 	int argc;
287 	char *argv[];
288 {
289 	struct modes *p;
290 	const char *sep;
291 
292 	if (argc < 2) {
293 		printf("Using %s mode to transfer files.\n", mode);
294 		return;
295 	}
296 	if (argc == 2) {
297 		for (p = modes; p->m_name; p++)
298 			if (strcmp(argv[1], p->m_name) == 0)
299 				break;
300 		if (p->m_name) {
301 			settftpmode(p->m_mode);
302 			return;
303 		}
304 		printf("%s: unknown mode\n", argv[1]);
305 		/* drop through and print usage message */
306 	}
307 
308 	printf("usage: %s [", argv[0]);
309 	sep = " ";
310 	for (p = modes; p->m_name; p++) {
311 		printf("%s%s", sep, p->m_name);
312 		if (*sep == ' ')
313 			sep = " | ";
314 	}
315 	printf(" ]\n");
316 	return;
317 }
318 
319 void
320 setbinary(argc, argv)
321 	int argc __unused;
322 	char *argv[] __unused;
323 {
324 
325 	settftpmode("octet");
326 }
327 
328 void
329 setascii(argc, argv)
330 	int argc __unused;
331 	char *argv[] __unused;
332 {
333 
334 	settftpmode("netascii");
335 }
336 
337 static void
338 settftpmode(newmode)
339 	const char *newmode;
340 {
341 	strcpy(mode, newmode);
342 	if (verbose)
343 		printf("mode set to %s\n", mode);
344 }
345 
346 
347 /*
348  * Send file(s).
349  */
350 void
351 put(argc, argv)
352 	int argc;
353 	char *argv[];
354 {
355 	int fd;
356 	int n;
357 	char *cp, *targ;
358 
359 	if (argc < 2) {
360 		strcpy(line, "send ");
361 		printf("(file) ");
362 		fgets(&line[strlen(line)], sizeof line - strlen(line), stdin);
363 		makeargv();
364 		argc = margc;
365 		argv = margv;
366 	}
367 	if (argc < 2) {
368 		putusage(argv[0]);
369 		return;
370 	}
371 	targ = argv[argc - 1];
372 	if (rindex(argv[argc - 1], ':')) {
373 		char *lcp;
374 
375 		for (n = 1; n < argc - 1; n++)
376 			if (index(argv[n], ':')) {
377 				putusage(argv[0]);
378 				return;
379 			}
380 		lcp = argv[argc - 1];
381 		targ = rindex(lcp, ':');
382 		*targ++ = 0;
383 		if (lcp[0] == '[' && lcp[strlen(lcp) - 1] == ']') {
384 			lcp[strlen(lcp) - 1] = '\0';
385 			lcp++;
386 		}
387 		setpeer0(lcp, NULL);
388 	}
389 	if (!connected) {
390 		printf("No target machine specified.\n");
391 		return;
392 	}
393 	if (argc < 4) {
394 		cp = argc == 2 ? tail(targ) : argv[1];
395 		fd = open(cp, O_RDONLY);
396 		if (fd < 0) {
397 			warn("%s", cp);
398 			return;
399 		}
400 		if (verbose)
401 			printf("putting %s to %s:%s [%s]\n",
402 				cp, hostname, targ, mode);
403 		xmitfile(fd, targ, mode);
404 		return;
405 	}
406 				/* this assumes the target is a directory */
407 				/* on a remote unix system.  hmmmm.  */
408 	cp = index(targ, '\0');
409 	*cp++ = '/';
410 	for (n = 1; n < argc - 1; n++) {
411 		strcpy(cp, tail(argv[n]));
412 		fd = open(argv[n], O_RDONLY);
413 		if (fd < 0) {
414 			warn("%s", argv[n]);
415 			continue;
416 		}
417 		if (verbose)
418 			printf("putting %s to %s:%s [%s]\n",
419 				argv[n], hostname, targ, mode);
420 		xmitfile(fd, targ, mode);
421 	}
422 }
423 
424 static void
425 putusage(s)
426 	char *s;
427 {
428 	printf("usage: %s file [[host:]remotename]\n", s);
429 	printf("       %s file1 file2 ... fileN [[host:]remote-directory]\n", s);
430 }
431 
432 /*
433  * Receive file(s).
434  */
435 void
436 get(argc, argv)
437 	int argc;
438 	char *argv[];
439 {
440 	int fd;
441 	int n;
442 	char *cp;
443 	char *src;
444 
445 	if (argc < 2) {
446 		strcpy(line, "get ");
447 		printf("(files) ");
448 		fgets(&line[strlen(line)], sizeof line - strlen(line), stdin);
449 		makeargv();
450 		argc = margc;
451 		argv = margv;
452 	}
453 	if (argc < 2) {
454 		getusage(argv[0]);
455 		return;
456 	}
457 	if (!connected) {
458 		for (n = 1; n < argc ; n++)
459 			if (rindex(argv[n], ':') == 0) {
460 				getusage(argv[0]);
461 				return;
462 			}
463 	}
464 	for (n = 1; n < argc ; n++) {
465 		src = rindex(argv[n], ':');
466 		if (src == NULL)
467 			src = argv[n];
468 		else {
469 			char *lcp;
470 
471 			*src++ = 0;
472 			lcp = argv[n];
473 			if (lcp[0] == '[' && lcp[strlen(lcp) - 1] == ']') {
474 				lcp[strlen(lcp) - 1] = '\0';
475 				lcp++;
476 			}
477 			setpeer0(lcp, NULL);
478 			if (!connected)
479 				continue;
480 		}
481 		if (argc < 4) {
482 			cp = argc == 3 ? argv[2] : tail(src);
483 			fd = creat(cp, 0644);
484 			if (fd < 0) {
485 				warn("%s", cp);
486 				return;
487 			}
488 			if (verbose)
489 				printf("getting from %s:%s to %s [%s]\n",
490 					hostname, src, cp, mode);
491 			recvfile(fd, src, mode);
492 			break;
493 		}
494 		cp = tail(src);         /* new .. jdg */
495 		fd = creat(cp, 0644);
496 		if (fd < 0) {
497 			warn("%s", cp);
498 			continue;
499 		}
500 		if (verbose)
501 			printf("getting from %s:%s to %s [%s]\n",
502 				hostname, src, cp, mode);
503 		recvfile(fd, src, mode);
504 	}
505 }
506 
507 static void
508 getusage(s)
509 	char *s;
510 {
511 	printf("usage: %s [host:]file [localname]\n", s);
512 	printf("       %s [host1:]file1 [host2:]file2 ... [hostN:]fileN\n", s);
513 }
514 
515 int	rexmtval = TIMEOUT;
516 
517 void
518 setrexmt(argc, argv)
519 	int argc;
520 	char *argv[];
521 {
522 	int t;
523 
524 	if (argc < 2) {
525 		strcpy(line, "Rexmt-timeout ");
526 		printf("(value) ");
527 		fgets(&line[strlen(line)], sizeof line - strlen(line), stdin);
528 		makeargv();
529 		argc = margc;
530 		argv = margv;
531 	}
532 	if (argc != 2) {
533 		printf("usage: %s value\n", argv[0]);
534 		return;
535 	}
536 	t = atoi(argv[1]);
537 	if (t < 0)
538 		printf("%s: bad value\n", argv[1]);
539 	else
540 		rexmtval = t;
541 }
542 
543 int	maxtimeout = 5 * TIMEOUT;
544 
545 void
546 settimeout(argc, argv)
547 	int argc;
548 	char *argv[];
549 {
550 	int t;
551 
552 	if (argc < 2) {
553 		strcpy(line, "Maximum-timeout ");
554 		printf("(value) ");
555 		fgets(&line[strlen(line)], sizeof line - strlen(line), stdin);
556 		makeargv();
557 		argc = margc;
558 		argv = margv;
559 	}
560 	if (argc != 2) {
561 		printf("usage: %s value\n", argv[0]);
562 		return;
563 	}
564 	t = atoi(argv[1]);
565 	if (t < 0)
566 		printf("%s: bad value\n", argv[1]);
567 	else
568 		maxtimeout = t;
569 }
570 
571 void
572 status(argc, argv)
573 	int argc __unused;
574 	char *argv[] __unused;
575 {
576 	if (connected)
577 		printf("Connected to %s.\n", hostname);
578 	else
579 		printf("Not connected.\n");
580 	printf("Mode: %s Verbose: %s Tracing: %s\n", mode,
581 		verbose ? "on" : "off", trace ? "on" : "off");
582 	printf("Rexmt-interval: %d seconds, Max-timeout: %d seconds\n",
583 		rexmtval, maxtimeout);
584 }
585 
586 void
587 intr(dummy)
588 	int dummy __unused;
589 {
590 
591 	signal(SIGALRM, SIG_IGN);
592 	alarm(0);
593 	longjmp(toplevel, -1);
594 }
595 
596 char *
597 tail(filename)
598 	char *filename;
599 {
600 	char *s;
601 
602 	while (*filename) {
603 		s = rindex(filename, '/');
604 		if (s == NULL)
605 			break;
606 		if (s[1])
607 			return (s + 1);
608 		*s = '\0';
609 	}
610 	return (filename);
611 }
612 
613 static const char *
614 command_prompt()
615 {
616 
617 	return ("tftp> ");
618 }
619 
620 /*
621  * Command parser.
622  */
623 static void
624 command()
625 {
626 	HistEvent he;
627 	struct cmd *c;
628 	static EditLine *el;
629 	static History *hist;
630 	const char *bp;
631 	char *cp;
632 	int len, num, vrbose;
633 
634 	vrbose = isatty(0);
635 	if (vrbose) {
636 		el = el_init("tftp", stdin, stdout, stderr);
637 		hist = history_init();
638 		history(hist, &he, H_SETSIZE, 100);
639 		el_set(el, EL_HIST, history, hist);
640 		el_set(el, EL_EDITOR, "emacs");
641 		el_set(el, EL_PROMPT, command_prompt);
642 		el_set(el, EL_SIGNAL, 1);
643 		el_source(el, NULL);
644 	}
645 	for (;;) {
646 		if (vrbose) {
647                         if ((bp = el_gets(el, &num)) == NULL || num == 0)
648                                 exit(0);
649                         len = (num > MAXLINE) ? MAXLINE : num;
650                         memcpy(line, bp, len);
651                         line[len] = '\0';
652                         history(hist, &he, H_ENTER, bp);
653 		} else {
654 			if (fgets(line, sizeof line , stdin) == 0) {
655 				if (feof(stdin)) {
656 					exit(txrx_error);
657 				} else {
658 					continue;
659 				}
660 			}
661 		}
662 		if ((cp = strchr(line, '\n')))
663 			*cp = '\0';
664 		if (line[0] == 0)
665 			continue;
666 		makeargv();
667 		if (margc == 0)
668 			continue;
669 		c = getcmd(margv[0]);
670 		if (c == (struct cmd *)-1) {
671 			printf("?Ambiguous command\n");
672 			continue;
673 		}
674 		if (c == 0) {
675 			printf("?Invalid command\n");
676 			continue;
677 		}
678 		(*c->handler)(margc, margv);
679 	}
680 }
681 
682 struct cmd *
683 getcmd(name)
684 	char *name;
685 {
686 	const char *p, *q;
687 	struct cmd *c, *found;
688 	int nmatches, longest;
689 
690 	longest = 0;
691 	nmatches = 0;
692 	found = 0;
693 	for (c = cmdtab; (p = c->name) != NULL; c++) {
694 		for (q = name; *q == *p++; q++)
695 			if (*q == 0)		/* exact match? */
696 				return (c);
697 		if (!*q) {			/* the name was a prefix */
698 			if (q - name > longest) {
699 				longest = q - name;
700 				nmatches = 1;
701 				found = c;
702 			} else if (q - name == longest)
703 				nmatches++;
704 		}
705 	}
706 	if (nmatches > 1)
707 		return ((struct cmd *)-1);
708 	return (found);
709 }
710 
711 /*
712  * Slice a string up into argc/argv.
713  */
714 static void
715 makeargv()
716 {
717 	char *cp;
718 	char **argp = margv;
719 
720 	margc = 0;
721 	if ((cp = strchr(line, '\n')))
722 		*cp = '\0';
723 	for (cp = line; margc < MAX_MARGV - 1 && *cp;) {
724 		while (isspace(*cp))
725 			cp++;
726 		if (*cp == '\0')
727 			break;
728 		*argp++ = cp;
729 		margc += 1;
730 		while (*cp != '\0' && !isspace(*cp))
731 			cp++;
732 		if (*cp == '\0')
733 			break;
734 		*cp++ = '\0';
735 	}
736 	*argp++ = 0;
737 }
738 
739 void
740 quit(argc, argv)
741 	int argc __unused;
742 	char *argv[] __unused;
743 {
744 	exit(txrx_error);
745 }
746 
747 /*
748  * Help command.
749  */
750 void
751 help(argc, argv)
752 	int argc;
753 	char *argv[];
754 {
755 	struct cmd *c;
756 
757 	if (argc == 1) {
758 		printf("Commands may be abbreviated.  Commands are:\n\n");
759 		for (c = cmdtab; c->name; c++)
760 			printf("%-*s\t%s\n", (int)HELPINDENT, c->name, c->help);
761 		return;
762 	}
763 	while (--argc > 0) {
764 		char *arg;
765 		arg = *++argv;
766 		c = getcmd(arg);
767 		if (c == (struct cmd *)-1)
768 			printf("?Ambiguous help command %s\n", arg);
769 		else if (c == (struct cmd *)0)
770 			printf("?Invalid help command %s\n", arg);
771 		else
772 			printf("%s\n", c->help);
773 	}
774 }
775 
776 void
777 settrace(argc, argv)
778 	int argc __unused;
779 	char **argv __unused;
780 {
781 	trace = !trace;
782 	printf("Packet tracing %s.\n", trace ? "on" : "off");
783 }
784 
785 void
786 setverbose(argc, argv)
787 	int argc __unused;
788 	char **argv __unused;
789 {
790 	verbose = !verbose;
791 	printf("Verbose mode %s.\n", verbose ? "on" : "off");
792 }
793