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