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