xref: /openbsd-src/usr.bin/tftp/main.c (revision 8500990981f885cbe5e6a4958549cacc238b5ae6)
1 /*	$OpenBSD: main.c,v 1.20 2003/09/29 11:23:38 jmc 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.20 2003/09/29 11:23:38 jmc 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 [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 		port = sp->s_port;
344 		strlcpy(hostname, hp->h_name, sizeof hostname);
345 	}
346 	if (!connected) {
347 		printf("No target machine specified.\n");
348 		return;
349 	}
350 	if (argc < 4) {
351 		cp = argc == 2 ? tail(targ) : argv[1];
352 		fd = open(cp, O_RDONLY);
353 		if (fd < 0) {
354 			warn("open: %s", cp);
355 			return;
356 		}
357 		if (verbose)
358 			printf("putting %s to %s:%s [%s]\n",
359 			    cp, hostname, targ, mode);
360 		peeraddr.sin_port = port;
361 		sendfile(fd, targ, mode);
362 		return;
363 	}
364 
365 	/* this assumes the target is a directory */
366 	/* on a remote unix system.  hmmmm.  */
367 	for (n = 1; n < argc - 1; n++) {
368 		if (asprintf(&cp, "%s/%s", targ, tail(argv[n])) == -1)
369 			err(1, "asprintf");
370 		fd = open(argv[n], O_RDONLY);
371 		if (fd < 0) {
372 			warn("open: %s", argv[n]);
373 			continue;
374 		}
375 		if (verbose)
376 			printf("putting %s to %s:%s [%s]\n",
377 			    argv[n], hostname, cp, mode);
378 		peeraddr.sin_port = port;
379 		sendfile(fd, cp, mode);
380 		free(cp);
381 	}
382 }
383 
384 static void
385 putusage(char *s)
386 {
387 	printf("usage: %s file [[host:]remotename]\n", s);
388 	printf("       %s file1 file2 ... fileN [[host:]remote-directory]\n", s);
389 }
390 
391 /*
392  * Receive file(s).
393  */
394 void
395 get(int argc, char *argv[])
396 {
397 	int fd;
398 	int n;
399 	char *cp;
400 	char *src;
401 
402 	if (argc < 2) {
403 		strlcpy(line, "get ", sizeof line);
404 		printf("(files) ");
405 		fgets(&line[strlen(line)], LBUFLEN-strlen(line), stdin);
406 		if (makeargv())
407 			return;
408 		argc = margc;
409 		argv = margv;
410 	}
411 	if (argc < 2) {
412 		getusage(argv[0]);
413 		return;
414 	}
415 	if (!connected) {
416 		for (n = 1; n < argc ; n++)
417 			if (strchr(argv[n], ':') == 0) {
418 				getusage(argv[0]);
419 				return;
420 			}
421 	}
422 	for (n = 1; n < argc ; n++) {
423 		src = strchr(argv[n], ':');
424 		if (src == NULL)
425 			src = argv[n];
426 		else {
427 			struct hostent *hp;
428 
429 			*src++ = 0;
430 			hp = gethostbyname(argv[n]);
431 			if (hp == NULL) {
432 				warnx("%s: %s", argv[n], hstrerror(h_errno));
433 				continue;
434 			}
435 			bcopy(hp->h_addr, (caddr_t)&peeraddr.sin_addr,
436 			    hp->h_length);
437 			peeraddr.sin_family = hp->h_addrtype;
438 			connected = 1;
439 			strlcpy(hostname, hp->h_name, sizeof hostname);
440 		}
441 		if (argc < 4) {
442 			cp = argc == 3 ? argv[2] : tail(src);
443 			fd = creat(cp, 0644);
444 			if (fd < 0) {
445 				warn("create: %s", cp);
446 				return;
447 			}
448 			if (verbose)
449 				printf("getting from %s:%s to %s [%s]\n",
450 				    hostname, src, cp, mode);
451 			peeraddr.sin_port = port;
452 			recvfile(fd, src, mode);
453 			break;
454 		}
455 		cp = tail(src);         /* new .. jdg */
456 		fd = creat(cp, 0644);
457 		if (fd < 0) {
458 			warn("create: %s", cp);
459 			continue;
460 		}
461 		if (verbose)
462 			printf("getting from %s:%s to %s [%s]\n",
463 			    hostname, src, cp, mode);
464 		peeraddr.sin_port = port;
465 		recvfile(fd, src, mode);
466 	}
467 }
468 
469 static void
470 getusage(char *s)
471 {
472 	printf("usage: %s [host:]file [localname]\n", s);
473 	printf("       %s [host1:]file1 [host2:]file2 ... [hostN:]fileN\n", s);
474 }
475 
476 int	rexmtval = TIMEOUT;
477 
478 void
479 setrexmt(int argc, char *argv[])
480 {
481 	int t;
482 
483 	if (argc < 2) {
484 		strlcpy(line, "Rexmt-timeout ", sizeof line);
485 		printf("(value) ");
486 		fgets(&line[strlen(line)], LBUFLEN-strlen(line), stdin);
487 		if (makeargv())
488 			return;
489 		argc = margc;
490 		argv = margv;
491 	}
492 	if (argc != 2) {
493 		printf("usage: %s value\n", argv[0]);
494 		return;
495 	}
496 	t = atoi(argv[1]);
497 	if (t < 0)
498 		printf("%s: bad value\n", argv[1]);
499 	else
500 		rexmtval = t;
501 }
502 
503 int	maxtimeout = 5 * TIMEOUT;
504 
505 void
506 settimeout(int argc, char *argv[])
507 {
508 	int t;
509 
510 	if (argc < 2) {
511 		strlcpy(line, "Maximum-timeout ", sizeof line);
512 		printf("(value) ");
513 		fgets(&line[strlen(line)], LBUFLEN-strlen(line), stdin);
514 		if (makeargv())
515 			return;
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, char *argv[])
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)
545 {
546 
547 	signal(SIGALRM, SIG_IGN);
548 	alarm(0);
549 	longjmp(toplevel, -1);
550 }
551 
552 char *
553 tail(char *filename)
554 {
555 	char *s;
556 
557 	while (*filename) {
558 		s = strrchr(filename, '/');
559 		if (s == NULL)
560 			break;
561 		if (s[1])
562 			return (s + 1);
563 		*s = '\0';
564 	}
565 	return (filename);
566 }
567 
568 /*
569  * Command parser.
570  */
571 static __dead void
572 command(void)
573 {
574 	struct cmd *c;
575 
576 	for (;;) {
577 		printf("%s> ", prompt);
578 		if (fgets(line, LBUFLEN, stdin) == 0) {
579 			if (feof(stdin)) {
580 				exit(0);
581 			} else {
582 				continue;
583 			}
584 		}
585 		if ((line[0] == 0) || (line[0] == '\n'))
586 			continue;
587 		if (makeargv())
588 			continue;
589 		if (margc == 0)
590 			continue;
591 		c = getcmd(margv[0]);
592 		if (c == (struct cmd *)-1) {
593 			printf("?Ambiguous command\n");
594 			continue;
595 		}
596 		if (c == 0) {
597 			printf("?Invalid command\n");
598 			continue;
599 		}
600 		(*c->handler)(margc, margv);
601 	}
602 }
603 
604 struct cmd *
605 getcmd(char *name)
606 {
607 	char *p, *q;
608 	struct cmd *c, *found;
609 	int nmatches, longest;
610 
611 	longest = 0;
612 	nmatches = 0;
613 	found = 0;
614 	for (c = cmdtab; (p = c->name) != NULL; c++) {
615 		for (q = name; *q == *p++; q++)
616 			if (*q == 0)		/* exact match? */
617 				return (c);
618 		if (!*q) {			/* the name was a prefix */
619 			if (q - name > longest) {
620 				longest = q - name;
621 				nmatches = 1;
622 				found = c;
623 			} else if (q - name == longest)
624 				nmatches++;
625 		}
626 	}
627 	if (nmatches > 1)
628 		return ((struct cmd *)-1);
629 	return (found);
630 }
631 
632 /*
633  * Slice a string up into argc/argv.
634  */
635 static int
636 makeargv(void)
637 {
638 	char *cp;
639 	char **argp = margv;
640 	int ret = 0;
641 
642 	margc = 0;
643 	for (cp = line; *cp;) {
644 		if (margc >= MAXARGV) {
645 			printf("too many arguments\n");
646 			ret = 1;
647 			break;
648 		}
649 		while (isspace(*cp))
650 			cp++;
651 		if (*cp == '\0')
652 			break;
653 		*argp++ = cp;
654 		margc += 1;
655 		while (*cp != '\0' && !isspace(*cp))
656 			cp++;
657 		if (*cp == '\0')
658 			break;
659 		*cp++ = '\0';
660 	}
661 	*argp++ = 0;
662 	return (ret);
663 }
664 
665 void
666 quit(int argc, char *argv[])
667 {
668 
669 	exit(0);
670 }
671 
672 /*
673  * Help command.
674  */
675 void
676 help(int argc, char *argv[])
677 {
678 	struct cmd *c;
679 
680 	if (argc == 1) {
681 		printf("Commands may be abbreviated.  Commands are:\n\n");
682 		for (c = cmdtab; c->name != NULL; c++)
683 			printf("%-*s\t%s\n", (int)HELPINDENT, c->name, c->help);
684 		return;
685 	}
686 	while (--argc > 0) {
687 		char *arg;
688 		arg = *++argv;
689 		c = getcmd(arg);
690 		if (c == (struct cmd *)-1)
691 			printf("?Ambiguous help command %s\n", arg);
692 		else if (c == (struct cmd *)0)
693 			printf("?Invalid help command %s\n", arg);
694 		else
695 			printf("%s\n", c->help);
696 	}
697 }
698 
699 void
700 settrace(int argc, char *argv[])
701 {
702 	trace = !trace;
703 	printf("Packet tracing %s.\n", trace ? "on" : "off");
704 }
705 
706 void
707 setverbose(int argc, char *argv[])
708 {
709 	verbose = !verbose;
710 	printf("Verbose mode %s.\n", verbose ? "on" : "off");
711 }
712