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