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