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