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