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