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