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