xref: /dragonfly/usr.bin/tftp/main.c (revision 9bb2a92d)
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  * $DragonFly: src/usr.bin/tftp/main.c,v 1.4 2003/11/04 16:52:01 drhodus Exp $
37  */
38 
39 /* Many bug fixes are from Jim Guyton <guyton@rand-unix> */
40 
41 /*
42  * TFTP User Program -- Command Interface.
43  */
44 #include <sys/param.h>
45 #include <sys/types.h>
46 #include <sys/socket.h>
47 #include <sys/file.h>
48 #include <sys/param.h>
49 
50 #include <netinet/in.h>
51 
52 #include <arpa/inet.h>
53 
54 #include <ctype.h>
55 #include <err.h>
56 #include <netdb.h>
57 #include <setjmp.h>
58 #include <signal.h>
59 #include <stdio.h>
60 #include <stdlib.h>
61 #include <string.h>
62 #include <unistd.h>
63 
64 #include "extern.h"
65 
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[200];
75 int	margc;
76 #define	MAX_MARGV	20
77 char	*margv[MAX_MARGV];
78 char	*prompt = "tftp";
79 jmp_buf	toplevel;
80 volatile int txrx_error;
81 void	intr();
82 
83 void	get(int, char **);
84 void	help(int, char **);
85 void	modecmd(int, char **);
86 void	put(int, char **);
87 void	quit(int, char **);
88 void	setascii(int, char **);
89 void	setbinary(int, char **);
90 void	setpeer0(char *, char *);
91 void	setpeer(int, char **);
92 void	setrexmt(int, char **);
93 void	settimeout(int, char **);
94 void	settrace(int, char **);
95 void	setverbose(int, char **);
96 void	status(int, char **);
97 
98 static void command(void) __dead2;
99 
100 static void getusage(char *);
101 static void makeargv(void);
102 static void putusage(char *);
103 static void settftpmode(char *);
104 
105 #define HELPINDENT (sizeof("connect"))
106 
107 struct cmd {
108 	char	*name;
109 	char	*help;
110 	void	(*handler)(int, char **);
111 };
112 
113 char	vhelp[] = "toggle verbose mode";
114 char	thelp[] = "toggle packet tracing";
115 char	chelp[] = "connect to remote tftp";
116 char	qhelp[] = "exit tftp";
117 char	hhelp[] = "print help information";
118 char	shelp[] = "send file";
119 char	rhelp[] = "receive file";
120 char	mhelp[] = "set file transfer mode";
121 char	sthelp[] = "show current status";
122 char	xhelp[] = "set per-packet retransmission timeout";
123 char	ihelp[] = "set total retransmission timeout";
124 char    ashelp[] = "set mode to netascii";
125 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 	{ 0 }
142 };
143 
144 struct	cmd *getcmd();
145 char	*tail();
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, char *port)
167 {
168 	struct addrinfo hints, *res0, *res;
169 	int error;
170 	struct sockaddr_storage ss;
171 	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 	char *m_name;
255 	char *m_mode;
256 } modes[] = {
257 	{ "ascii",	"netascii" },
258 	{ "netascii",   "netascii" },
259 	{ "binary",     "octet" },
260 	{ "image",      "octet" },
261 	{ "octet",     "octet" },
262 /*      { "mail",       "mail" },       */
263 	{ 0,		0 }
264 };
265 
266 void
267 modecmd(int argc, char **argv)
268 {
269 	register struct modes *p;
270 	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, char **argv)
301 {
302 
303 	settftpmode("octet");
304 }
305 
306 void
307 setascii(int argc, char **argv)
308 {
309 
310 	settftpmode("netascii");
311 }
312 
313 static void
314 settftpmode(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 	register int n;
330 	register 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 (rindex(argv[argc - 1], ':')) {
346 		char *cp;
347 
348 		for (n = 1; n < argc - 1; n++)
349 			if (index(argv[n], ':')) {
350 				putusage(argv[0]);
351 				return;
352 			}
353 		cp = argv[argc - 1];
354 		targ = rindex(cp, ':');
355 		*targ++ = 0;
356 		if (cp[0] == '[' && cp[strlen(cp) - 1] == ']') {
357 			cp[strlen(cp) - 1] = '\0';
358 			cp++;
359 		}
360 		setpeer0(cp, NULL);
361 	}
362 	if (!connected) {
363 		printf("No target machine specified.\n");
364 		return;
365 	}
366 	if (argc < 4) {
367 		cp = argc == 2 ? tail(targ) : argv[1];
368 		fd = open(cp, O_RDONLY);
369 		if (fd < 0) {
370 			warn("%s", cp);
371 			return;
372 		}
373 		if (verbose)
374 			printf("putting %s to %s:%s [%s]\n",
375 				cp, hostname, targ, mode);
376 		xmitfile(fd, targ, mode);
377 		return;
378 	}
379 				/* this assumes the target is a directory */
380 				/* on a remote unix system.  hmmmm.  */
381 	cp = index(targ, '\0');
382 	*cp++ = '/';
383 	for (n = 1; n < argc - 1; n++) {
384 		strcpy(cp, tail(argv[n]));
385 		fd = open(argv[n], O_RDONLY);
386 		if (fd < 0) {
387 			warn("%s", argv[n]);
388 			continue;
389 		}
390 		if (verbose)
391 			printf("putting %s to %s:%s [%s]\n",
392 				argv[n], hostname, targ, mode);
393 		xmitfile(fd, targ, mode);
394 	}
395 }
396 
397 static void
398 putusage(char *s)
399 {
400 	printf("usage: %s file ... host:target, or\n", s);
401 	printf("       %s file ... target (when already connected)\n", s);
402 }
403 
404 /*
405  * Receive file(s).
406  */
407 void
408 get(int argc, char **argv)
409 {
410 	int fd;
411 	register int n;
412 	register char *cp;
413 	char *src;
414 
415 	if (argc < 2) {
416 		strcpy(line, "get ");
417 		printf("(files) ");
418 		fgets(&line[strlen(line)], sizeof line - strlen(line), stdin);
419 		makeargv();
420 		argc = margc;
421 		argv = margv;
422 	}
423 	if (argc < 2) {
424 		getusage(argv[0]);
425 		return;
426 	}
427 	if (!connected) {
428 		for (n = 1; n < argc ; n++)
429 			if (rindex(argv[n], ':') == 0) {
430 				getusage(argv[0]);
431 				return;
432 			}
433 	}
434 	for (n = 1; n < argc ; n++) {
435 		src = rindex(argv[n], ':');
436 		if (src == NULL)
437 			src = argv[n];
438 		else {
439 			char *cp;
440 			*src++ = 0;
441 			cp = argv[n];
442 			if (cp[0] == '[' && cp[strlen(cp) - 1] == ']') {
443 				cp[strlen(cp) - 1] = '\0';
444 				cp++;
445 			}
446 			setpeer0(cp, NULL);
447 			if (!connected)
448 				continue;
449 		}
450 		if (argc < 4) {
451 			cp = argc == 3 ? argv[2] : tail(src);
452 			fd = creat(cp, 0644);
453 			if (fd < 0) {
454 				warn("%s", cp);
455 				return;
456 			}
457 			if (verbose)
458 				printf("getting from %s:%s to %s [%s]\n",
459 					hostname, src, cp, mode);
460 			recvfile(fd, src, mode);
461 			break;
462 		}
463 		cp = tail(src);         /* new .. jdg */
464 		fd = creat(cp, 0644);
465 		if (fd < 0) {
466 			warn("%s", cp);
467 			continue;
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 	}
474 }
475 
476 static void
477 getusage(char *s)
478 {
479 	printf("usage: %s host:file host:file ... file, or\n", s);
480 	printf("       %s file file ... file if connected\n", s);
481 }
482 
483 int	rexmtval = TIMEOUT;
484 
485 void
486 setrexmt(int argc, char **argv)
487 {
488 	int t;
489 
490 	if (argc < 2) {
491 		strcpy(line, "Rexmt-timeout ");
492 		printf("(value) ");
493 		fgets(&line[strlen(line)], sizeof line - strlen(line), stdin);
494 		makeargv();
495 		argc = margc;
496 		argv = margv;
497 	}
498 	if (argc != 2) {
499 		printf("usage: %s value\n", argv[0]);
500 		return;
501 	}
502 	t = atoi(argv[1]);
503 	if (t < 0)
504 		printf("%s: bad value\n", argv[1]);
505 	else
506 		rexmtval = t;
507 }
508 
509 int	maxtimeout = 5 * TIMEOUT;
510 
511 void
512 settimeout(int argc, char **argv)
513 {
514 	int t;
515 
516 	if (argc < 2) {
517 		strcpy(line, "Maximum-timeout ");
518 		printf("(value) ");
519 		fgets(&line[strlen(line)], sizeof line - strlen(line), stdin);
520 		makeargv();
521 		argc = margc;
522 		argv = margv;
523 	}
524 	if (argc != 2) {
525 		printf("usage: %s value\n", argv[0]);
526 		return;
527 	}
528 	t = atoi(argv[1]);
529 	if (t < 0)
530 		printf("%s: bad value\n", argv[1]);
531 	else
532 		maxtimeout = t;
533 }
534 
535 void
536 status(int argc, char **argv)
537 {
538 	if (connected)
539 		printf("Connected to %s.\n", hostname);
540 	else
541 		printf("Not connected.\n");
542 	printf("Mode: %s Verbose: %s Tracing: %s\n", mode,
543 		verbose ? "on" : "off", trace ? "on" : "off");
544 	printf("Rexmt-interval: %d seconds, Max-timeout: %d seconds\n",
545 		rexmtval, maxtimeout);
546 }
547 
548 void
549 intr(void)
550 {
551 
552 	signal(SIGALRM, SIG_IGN);
553 	alarm(0);
554 	longjmp(toplevel, -1);
555 }
556 
557 char *
558 tail(char *filename)
559 {
560 	register char *s;
561 
562 	while (*filename) {
563 		s = rindex(filename, '/');
564 		if (s == NULL)
565 			break;
566 		if (s[1])
567 			return (s + 1);
568 		*s = '\0';
569 	}
570 	return (filename);
571 }
572 
573 /*
574  * Command parser.
575  */
576 static void
577 command(void)
578 {
579 	register struct cmd *c;
580 	char *cp;
581 
582 	for (;;) {
583 		printf("%s> ", prompt);
584 		if (fgets(line, sizeof line , stdin) == 0) {
585 			if (feof(stdin)) {
586 				exit(txrx_error);
587 			} else {
588 				continue;
589 			}
590 		}
591 		if ((cp = strchr(line, '\n')))
592 			*cp = '\0';
593 		if (line[0] == 0)
594 			continue;
595 		makeargv();
596 		if (margc == 0)
597 			continue;
598 		c = getcmd(margv[0]);
599 		if (c == (struct cmd *)-1) {
600 			printf("?Ambiguous command\n");
601 			continue;
602 		}
603 		if (c == 0) {
604 			printf("?Invalid command\n");
605 			continue;
606 		}
607 		(*c->handler)(margc, margv);
608 	}
609 }
610 
611 struct cmd *
612 getcmd(register char *name)
613 {
614 	register char *p, *q;
615 	register struct cmd *c, *found;
616 	register int nmatches, longest;
617 
618 	longest = 0;
619 	nmatches = 0;
620 	found = 0;
621 	for (c = cmdtab; (p = c->name) != NULL; c++) {
622 		for (q = name; *q == *p++; q++)
623 			if (*q == 0)		/* exact match? */
624 				return (c);
625 		if (!*q) {			/* the name was a prefix */
626 			if (q - name > longest) {
627 				longest = q - name;
628 				nmatches = 1;
629 				found = c;
630 			} else if (q - name == longest)
631 				nmatches++;
632 		}
633 	}
634 	if (nmatches > 1)
635 		return ((struct cmd *)-1);
636 	return (found);
637 }
638 
639 /*
640  * Slice a string up into argc/argv.
641  */
642 static void
643 makeargv(void)
644 {
645 	register char *cp;
646 	register char **argp = margv;
647 
648 	margc = 0;
649 	if ((cp = strchr(line, '\n')))
650 		*cp = '\0';
651 	for (cp = line; margc < MAX_MARGV -1 && *cp;) {
652 		while (isspace(*cp))
653 			cp++;
654 		if (*cp == '\0')
655 			break;
656 		*argp++ = cp;
657 		margc += 1;
658 		while (*cp != '\0' && !isspace(*cp))
659 			cp++;
660 		if (*cp == '\0')
661 			break;
662 		*cp++ = '\0';
663 	}
664 	*argp++ = 0;
665 }
666 
667 void
668 quit(int argc, char **argv)
669 {
670 
671 	exit(txrx_error);
672 }
673 
674 /*
675  * Help command.
676  */
677 void
678 help(int argc, char **argv)
679 {
680 	register struct cmd *c;
681 
682 	if (argc == 1) {
683 		printf("Commands may be abbreviated.  Commands are:\n\n");
684 		for (c = cmdtab; c->name; c++)
685 			printf("%-*s\t%s\n", (int)HELPINDENT, c->name, c->help);
686 		return;
687 	}
688 	while (--argc > 0) {
689 		register char *arg;
690 		arg = *++argv;
691 		c = getcmd(arg);
692 		if (c == (struct cmd *)-1)
693 			printf("?Ambiguous help command %s\n", arg);
694 		else if (c == (struct cmd *)0)
695 			printf("?Invalid help command %s\n", arg);
696 		else
697 			printf("%s\n", c->help);
698 	}
699 }
700 
701 void
702 settrace(int argc, char **argv)
703 {
704 	trace = !trace;
705 	printf("Packet tracing %s.\n", trace ? "on" : "off");
706 }
707 
708 void
709 setverbose(int argc, char **argv)
710 {
711 	verbose = !verbose;
712 	printf("Verbose mode %s.\n", verbose ? "on" : "off");
713 }
714