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