diff options
Diffstat (limited to 'usr.bin')
-rw-r--r-- | usr.bin/tftp/Makefile | 6 | ||||
-rw-r--r-- | usr.bin/tftp/main.c | 656 | ||||
-rw-r--r-- | usr.bin/tftp/tftp.1 | 60 | ||||
-rw-r--r-- | usr.bin/tftp/tftp.c | 576 |
4 files changed, 696 insertions, 602 deletions
diff --git a/usr.bin/tftp/Makefile b/usr.bin/tftp/Makefile index a51afed..9be599a 100644 --- a/usr.bin/tftp/Makefile +++ b/usr.bin/tftp/Makefile @@ -1,9 +1,13 @@ # @(#)Makefile 8.1 (Berkeley) 6/6/93 # $FreeBSD$ +CFLAGS=-g -Wall +WARNS= 3 PROG= tftp -SRCS= main.c tftp.c tftpsubs.c +SRCS= main.c tftp.c tftp-utils.c tftp-io.c tftp-file.c tftp-transfer.c tftp-options.c DPADD= ${LIBEDIT} ${LIBTERMCAP} LDADD= -ledit -ltermcap +CFLAGS+=-I${.CURDIR}/../../libexec/tftpd -I${.CURDIR}/../../usr.bin/tftp +.PATH: ${.CURDIR}/../../libexec/tftpd .include <bsd.prog.mk> diff --git a/usr.bin/tftp/main.c b/usr.bin/tftp/main.c index f01d3d8..26ca0244 100644 --- a/usr.bin/tftp/main.c +++ b/usr.bin/tftp/main.c @@ -54,12 +54,14 @@ __FBSDID("$FreeBSD$"); #include <sys/param.h> #include <sys/types.h> #include <sys/socket.h> +#include <sys/sysctl.h> #include <sys/file.h> #include <sys/param.h> +#include <sys/stat.h> #include <netinet/in.h> - #include <arpa/inet.h> +#include <arpa/tftp.h> #include <ctype.h> #include <err.h> @@ -72,119 +74,212 @@ __FBSDID("$FreeBSD$"); #include <string.h> #include <unistd.h> -#include "extern.h" +#include "tftp-utils.h" +#include "tftp-io.h" +#include "tftp-options.h" +#include "tftp.h" #define MAXLINE 200 #define TIMEOUT 5 /* secs between rexmt's */ -struct sockaddr_storage peeraddr; -int f; -int trace; -int verbose; -int connected; -char mode[32]; -char line[MAXLINE]; -int margc; +static struct sockaddr_storage peeraddr; +static int connected; +static char mode[32]; +jmp_buf toplevel; +volatile int txrx_error; +static int peer; + #define MAX_MARGV 20 -char *margv[MAX_MARGV]; -jmp_buf toplevel; -volatile int txrx_error; - -void get(int, char **); -void help(int, char **); -void intr(int); -void modecmd(int, char **); -void put(int, char **); -void quit(int, char **); -void setascii(int, char **); -void setbinary(int, char **); -void setpeer0(char *, const char *); -void setpeer(int, char **); -void setrexmt(int, char **); -void settimeout(int, char **); -void settrace(int, char **); -void setverbose(int, char **); -void status(int, char **); +static int margc; +static char *margv[MAX_MARGV]; + +int verbose; +char *port = NULL; + +static void get(int, char **); +static void help(int, char **); +static void intr(int); +static void modecmd(int, char **); +static void put(int, char **); +static void quit(int, char **); +static void setascii(int, char **); +static void setbinary(int, char **); +static void setpeer0(char *, const char *); +static void setpeer(int, char **); +static void settimeoutpacket(int, char **); +static void settimeoutnetwork(int, char **); +static void setdebug(int, char **); +static void setverbose(int, char **); +static void showstatus(int, char **); +static void setblocksize(int, char **); +static void setblocksize2(int, char **); +static void setoptions(int, char **); +static void setrollover(int, char **); +static void setpacketdrop(int, char **); static void command(void) __dead2; static const char *command_prompt(void); -static void getusage(const char *); -static void makeargv(void); -static void putusage(const char *); +static void urihandling(char *URI); +static void getusage(char *); +static void makeargv(char *line); +static void putusage(char *); static void settftpmode(const char *); -char *tail(char *); -struct cmd *getcmd(char *); +static char *tail(char *); +static struct cmd *getcmd(char *); #define HELPINDENT (sizeof("connect")) struct cmd { const char *name; - char *help; void (*handler)(int, char **); + const char *help; }; -char vhelp[] = "toggle verbose mode"; -char thelp[] = "toggle packet tracing"; -char chelp[] = "connect to remote tftp"; -char qhelp[] = "exit tftp"; -char hhelp[] = "print help information"; -char shelp[] = "send file"; -char rhelp[] = "receive file"; -char mhelp[] = "set file transfer mode"; -char sthelp[] = "show current status"; -char xhelp[] = "set per-packet retransmission timeout"; -char ihelp[] = "set total retransmission timeout"; -char ashelp[] = "set mode to netascii"; -char bnhelp[] = "set mode to octet"; - -struct cmd cmdtab[] = { - { "connect", chelp, setpeer }, - { "mode", mhelp, modecmd }, - { "put", shelp, put }, - { "get", rhelp, get }, - { "quit", qhelp, quit }, - { "verbose", vhelp, setverbose }, - { "trace", thelp, settrace }, - { "status", sthelp, status }, - { "binary", bnhelp, setbinary }, - { "ascii", ashelp, setascii }, - { "rexmt", xhelp, setrexmt }, - { "timeout", ihelp, settimeout }, - { "?", hhelp, help }, - { NULL, NULL, NULL } +static struct cmd cmdtab[] = { + { "connect", setpeer, "connect to remote tftp" }, + { "mode", modecmd, "set file transfer mode" }, + { "put", put, "send file" }, + { "get", get, "receive file" }, + { "quit", quit, "exit tftp" }, + { "verbose", setverbose, "toggle verbose mode" }, + { "status", showstatus, "show current status" }, + { "binary", setbinary, "set mode to octet" }, + { "ascii", setascii, "set mode to netascii" }, + { "rexmt", settimeoutpacket, + "set per-packet retransmission timeout[-]" }, + { "timeout", settimeoutnetwork, + "set total retransmission timeout" }, + { "trace", setdebug, "enable 'debug packet'[-]" }, + { "debug", setdebug, "enable verbose output" }, + { "blocksize", setblocksize, "set blocksize[*]" }, + { "blocksize2", setblocksize2, "set blocksize as a power of 2[**]" }, + { "rollover", setrollover, "rollover after 64K packets[**]" }, + { "options", setoptions, + "enable or disable RFC2347 style options" }, + { "help", help, "print help information" }, + { "packetdrop", setpacketdrop, "artifical packetloss feature" }, + { "?", help, "print help information" }, + { NULL, NULL, NULL } +}; + +static struct modes { + const char *m_name; + const char *m_mode; +} modes[] = { + { "ascii", "netascii" }, + { "netascii", "netascii" }, + { "binary", "octet" }, + { "image", "octet" }, + { "octet", "octet" }, + { NULL, NULL } }; int main(int argc, char *argv[]) { - f = -1; + + acting_as_client = 1; + peer = -1; strcpy(mode, "netascii"); signal(SIGINT, intr); if (argc > 1) { if (setjmp(toplevel) != 0) exit(txrx_error); + + if (strncmp(argv[1], "tftp://", 7) == 0) { + urihandling(argv[1]); + exit(txrx_error); + } + setpeer(argc, argv); } if (setjmp(toplevel) != 0) (void)putchar('\n'); + + init_options(); command(); } -char hostname[MAXHOSTNAMELEN]; +/* + * RFC3617 handling of TFTP URIs: + * + * tftpURI = "tftp://" host "/" file [ mode ] + * mode = ";" "mode=" ( "netascii" / "octet" ) + * file = *( unreserved / escaped ) + * host = <as specified by RFC 2732> + * unreserved = <as specified in RFC 2396> + * escaped = <as specified in RFC 2396> + * + * We are cheating a little bit by allowing any mode as specified in the + * modes table defined earlier on in this file and mapping it on the real + * mode. + */ +static void +urihandling(char *URI) +{ + char uri[ARG_MAX]; + char *host = NULL; + char *path = NULL; + char *options = NULL; + char *mode = "octet"; + char *s; + char line[MAXLINE]; + int i; + + strncpy(uri, URI, ARG_MAX); + host = uri + 7; + + if ((s = strchr(host, '/')) == NULL) { + fprintf(stderr, + "Invalid URI: Couldn't find / after hostname\n"); + exit(1); + } + *s = '\0'; + path = s + 1; + + if ((s = strchr(path, ';')) != NULL) { + *s = '\0'; + options = s + 1; -void -setpeer0(char *host, const char *port) + if (strncmp(options, "mode=", 5) == 0) { + mode = options; + mode += 5; + + for (i = 0; modes[i].m_name != NULL; i++) { + if (strcmp(modes[i].m_name, mode) == 0) + break; + } + if (modes[i].m_name == NULL) { + fprintf(stderr, "Invalid mode: '%s'\n", mode); + exit(1); + } + settftpmode(modes[i].m_mode); + } + } else { + settftpmode("octet"); + } + + setpeer0(host, NULL); + + sprintf(line, "get %s", path); + makeargv(line); + get(margc, margv); +} + +static char hostname[MAXHOSTNAMELEN]; + +static void +setpeer0(char *host, const char *lport) { struct addrinfo hints, *res0, *res; int error; - struct sockaddr_storage ss; const char *cause = "unknown"; if (connected) { - close(f); - f = -1; + close(peer); + peer = -1; } connected = 0; @@ -193,9 +288,9 @@ setpeer0(char *host, const char *port) hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; hints.ai_flags = AI_CANONNAME; - if (!port) - port = "tftp"; - error = getaddrinfo(host, port, &hints, &res0); + if (!lport) + lport = "tftp"; + error = getaddrinfo(host, lport, &hints, &res0); if (error) { warnx("%s", gai_strerror(error)); return; @@ -204,50 +299,53 @@ setpeer0(char *host, const char *port) for (res = res0; res; res = res->ai_next) { if (res->ai_addrlen > sizeof(peeraddr)) continue; - f = socket(res->ai_family, res->ai_socktype, res->ai_protocol); - if (f < 0) { + peer = socket(res->ai_family, res->ai_socktype, + res->ai_protocol); + if (peer < 0) { cause = "socket"; continue; } - memset(&ss, 0, sizeof(ss)); - ss.ss_family = res->ai_family; - ss.ss_len = res->ai_addrlen; - if (bind(f, (struct sockaddr *)&ss, ss.ss_len) < 0) { + memset(&peer_sock, 0, sizeof(peer_sock)); + peer_sock.ss_family = res->ai_family; + peer_sock.ss_len = res->ai_addrlen; + if (bind(peer, (struct sockaddr *)&peer_sock, peer_sock.ss_len) < 0) { cause = "bind"; - close(f); - f = -1; + close(peer); + peer = -1; continue; } break; } - if (f < 0) + if (peer < 0) warn("%s", cause); else { /* res->ai_addr <= sizeof(peeraddr) is guaranteed */ - memcpy(&peeraddr, res->ai_addr, res->ai_addrlen); + memcpy(&peer_sock, res->ai_addr, res->ai_addrlen); if (res->ai_canonname) { - (void) strlcpy(hostname, res->ai_canonname, + (void) strncpy(hostname, res->ai_canonname, sizeof(hostname)); } else - (void) strlcpy(hostname, host, sizeof(hostname)); + (void) strncpy(hostname, host, sizeof(hostname)); + hostname[sizeof(hostname)-1] = 0; connected = 1; } freeaddrinfo(res0); } -void +static void setpeer(int argc, char *argv[]) { + char line[MAXLINE]; if (argc < 2) { strcpy(line, "Connect "); printf("(to) "); fgets(&line[strlen(line)], sizeof line - strlen(line), stdin); - makeargv(); + makeargv(line); argc = margc; argv = margv; } @@ -255,26 +353,14 @@ setpeer(int argc, char *argv[]) printf("usage: %s [host [port]]\n", argv[0]); return; } - if (argc == 3) + if (argc == 3) { + port = argv[2]; setpeer0(argv[1], argv[2]); - else + } else setpeer0(argv[1], NULL); } -struct modes { - const char *m_name; - const char *m_mode; -} modes[] = { - { "ascii", "netascii" }, - { "netascii", "netascii" }, - { "binary", "octet" }, - { "image", "octet" }, - { "octet", "octet" }, -/* { "mail", "mail" }, */ - { 0, 0 } -}; - -void +static void modecmd(int argc, char *argv[]) { struct modes *p; @@ -298,7 +384,7 @@ modecmd(int argc, char *argv[]) printf("usage: %s [", argv[0]); sep = " "; - for (p = modes; p->m_name; p++) { + for (p = modes; p->m_name != NULL; p++) { printf("%s%s", sep, p->m_name); if (*sep == ' ') sep = " | "; @@ -307,14 +393,14 @@ modecmd(int argc, char *argv[]) return; } -void +static void setbinary(int argc __unused, char *argv[] __unused) { settftpmode("octet"); } -void +static void setascii(int argc __unused, char *argv[] __unused) { @@ -324,6 +410,7 @@ setascii(int argc __unused, char *argv[] __unused) static void settftpmode(const char *newmode) { + strcpy(mode, newmode); if (verbose) printf("mode set to %s\n", mode); @@ -333,18 +420,20 @@ settftpmode(const char *newmode) /* * Send file(s). */ -void +static void put(int argc, char *argv[]) { - int fd; - int n; - char *cp, *targ; + int fd; + int n; + char *cp, *targ; + char line[MAXLINE]; + struct stat sb; if (argc < 2) { strcpy(line, "send "); printf("(file) "); fgets(&line[strlen(line)], sizeof line - strlen(line), stdin); - makeargv(); + makeargv(line); argc = margc; argv = margv; } @@ -381,10 +470,14 @@ put(int argc, char *argv[]) warn("%s", cp); return; } + + stat(cp, &sb); + asprintf(&options[OPT_TSIZE].o_request, "%ju", sb.st_size); + if (verbose) printf("putting %s to %s:%s [%s]\n", - cp, hostname, targ, mode); - xmitfile(fd, targ, mode); + cp, hostname, targ, mode); + xmitfile(peer, port, fd, targ, mode); return; } /* this assumes the target is a directory */ @@ -398,36 +491,43 @@ put(int argc, char *argv[]) warn("%s", argv[n]); continue; } + + stat(cp, &sb); + asprintf(&options[OPT_TSIZE].o_request, "%ju", sb.st_size); + if (verbose) printf("putting %s to %s:%s [%s]\n", - argv[n], hostname, targ, mode); - xmitfile(fd, targ, mode); + argv[n], hostname, targ, mode); + xmitfile(peer, port, fd, targ, mode); } } static void -putusage(const char *s) +putusage(char *s) { - printf("usage: %s file [[host:]remotename]\n", s); + + printf("usage: %s file [remotename]\n", s); + printf(" %s file host:remotename\n", s); printf(" %s file1 file2 ... fileN [[host:]remote-directory]\n", s); } /* * Receive file(s). */ -void +static void get(int argc, char *argv[]) { int fd; int n; char *cp; char *src; + char line[MAXLINE]; if (argc < 2) { strcpy(line, "get "); printf("(files) "); fgets(&line[strlen(line)], sizeof line - strlen(line), stdin); - makeargv(); + makeargv(line); argc = margc; argv = margv; } @@ -438,6 +538,8 @@ get(int argc, char *argv[]) if (!connected) { for (n = 1; n < argc ; n++) if (rindex(argv[n], ':') == 0) { + printf("No remote host specified and " + "no host given for file '%s'\n", argv[n]); getusage(argv[0]); return; } @@ -468,8 +570,8 @@ get(int argc, char *argv[]) } if (verbose) printf("getting from %s:%s to %s [%s]\n", - hostname, src, cp, mode); - recvfile(fd, src, mode); + hostname, src, cp, mode); + recvfile(peer, port, fd, src, mode); break; } cp = tail(src); /* new .. jdg */ @@ -480,30 +582,31 @@ get(int argc, char *argv[]) } if (verbose) printf("getting from %s:%s to %s [%s]\n", - hostname, src, cp, mode); - recvfile(fd, src, mode); + hostname, src, cp, mode); + recvfile(peer, port, fd, src, mode); } } static void -getusage(const char *s) +getusage(char *s) { - printf("usage: %s [host:]file [localname]\n", s); + + printf("usage: %s file [localname]\n", s); + printf(" %s [host:]file [localname]\n", s); printf(" %s [host1:]file1 [host2:]file2 ... [hostN:]fileN\n", s); } -int rexmtval = TIMEOUT; - -void -setrexmt(int argc, char *argv[]) +static void +settimeoutpacket(int argc, char *argv[]) { int t; + char line[MAXLINE]; if (argc < 2) { - strcpy(line, "Rexmt-timeout "); + strcpy(line, "Packet timeout "); printf("(value) "); fgets(&line[strlen(line)], sizeof line - strlen(line), stdin); - makeargv(); + makeargv(line); argc = margc; argv = margv; } @@ -512,24 +615,25 @@ setrexmt(int argc, char *argv[]) return; } t = atoi(argv[1]); - if (t < 0) + if (t < 0) { printf("%s: bad value\n", argv[1]); - else - rexmtval = t; -} + return; + } -int maxtimeout = 5 * TIMEOUT; + settimeouts(t, timeoutnetwork, maxtimeouts); +} -void -settimeout(int argc, char *argv[]) +static void +settimeoutnetwork(int argc, char *argv[]) { int t; + char line[MAXLINE]; if (argc < 2) { - strcpy(line, "Maximum-timeout "); + strcpy(line, "Network timeout "); printf("(value) "); fgets(&line[strlen(line)], sizeof line - strlen(line), stdin); - makeargv(); + makeargv(line); argc = margc; argv = margv; } @@ -538,26 +642,36 @@ settimeout(int argc, char *argv[]) return; } t = atoi(argv[1]); - if (t < 0) + if (t < 0) { printf("%s: bad value\n", argv[1]); - else - maxtimeout = t; + return; + } + + settimeouts(timeoutpacket, t, maxtimeouts); } -void -status(int argc __unused, char *argv[] __unused) +static void +showstatus(int argc __unused, char *argv[] __unused) { - if (connected) - printf("Connected to %s.\n", hostname); - else - printf("Not connected.\n"); - printf("Mode: %s Verbose: %s Tracing: %s\n", mode, - verbose ? "on" : "off", trace ? "on" : "off"); - printf("Rexmt-interval: %d seconds, Max-timeout: %d seconds\n", - rexmtval, maxtimeout); + + printf("Remote host: %s\n", + connected ? hostname : "none specified yet"); + printf("RFC2347 Options support: %s\n", + options_rfc_enabled ? "enabled" : "disabled"); + printf("Non-RFC defined options support: %s\n", + options_extra_enabled ? "enabled" : "disabled"); + printf("Mode: %s\n", mode); + printf("Verbose: %s\n", verbose ? "on" : "off"); + printf("Debug: %s\n", debug_show(debug)); + printf("Artificial packetloss: %d in 100 packets\n", + packetdroppercentage); + printf("Segment size: %d bytes\n", segsize); + printf("Network timeout: %d seconds\n", timeoutpacket); + printf("Maximum network timeout: %d seconds\n", timeoutnetwork); + printf("Maximum timeouts: %d \n", maxtimeouts); } -void +static void intr(int dummy __unused) { @@ -566,7 +680,7 @@ intr(int dummy __unused) longjmp(toplevel, -1); } -char * +static char * tail(char *filename) { char *s; @@ -583,7 +697,7 @@ tail(char *filename) } static const char * -command_prompt(void) +command_prompt() { return ("tftp> "); @@ -602,6 +716,7 @@ command(void) const char *bp; char *cp; int len, num, vrbose; + char line[MAXLINE]; vrbose = isatty(0); if (vrbose) { @@ -623,6 +738,7 @@ command(void) line[len] = '\0'; history(hist, &he, H_ENTER, bp); } else { + line[0] = 0; if (fgets(line, sizeof line , stdin) == 0) { if (feof(stdin)) { exit(txrx_error); @@ -635,7 +751,7 @@ command(void) *cp = '\0'; if (line[0] == 0) continue; - makeargv(); + makeargv(line); if (margc == 0) continue; c = getcmd(margv[0]); @@ -651,7 +767,7 @@ command(void) } } -struct cmd * +static struct cmd * getcmd(char *name) { const char *p, *q; @@ -683,15 +799,15 @@ getcmd(char *name) * Slice a string up into argc/argv. */ static void -makeargv(void) +makeargv(char *line) { char *cp; char **argp = margv; margc = 0; - if ((cp = strchr(line, '\n'))) + if ((cp = strchr(line, '\n')) != NULL) *cp = '\0'; - for (cp = line; margc < MAX_MARGV - 1 && *cp;) { + for (cp = line; margc < MAX_MARGV - 1 && *cp != '\0';) { while (isspace(*cp)) cp++; if (*cp == '\0') @@ -707,16 +823,17 @@ makeargv(void) *argp++ = 0; } -void +static void quit(int argc __unused, char *argv[] __unused) { + exit(txrx_error); } /* * Help command. */ -void +static void help(int argc, char *argv[]) { struct cmd *c; @@ -725,6 +842,10 @@ help(int argc, char *argv[]) printf("Commands may be abbreviated. Commands are:\n\n"); for (c = cmdtab; c->name; c++) printf("%-*s\t%s\n", (int)HELPINDENT, c->name, c->help); + + printf("\n[-] : You shouldn't use these ones anymore.\n"); + printf("[*] : RFC2834 options support required.\n"); + printf("[**] : Non-standard RFC2834 option.\n"); return; } while (--argc > 0) { @@ -732,24 +853,211 @@ help(int argc, char *argv[]) arg = *++argv; c = getcmd(arg); if (c == (struct cmd *)-1) - printf("?Ambiguous help command %s\n", arg); + printf("?Ambiguous help command: %s\n", arg); else if (c == (struct cmd *)0) - printf("?Invalid help command %s\n", arg); + printf("?Invalid help command: %s\n", arg); else printf("%s\n", c->help); } } -void -settrace(int argc __unused, char **argv __unused) +static void +setverbose(int argc __unused, char *argv[] __unused) { - trace = !trace; - printf("Packet tracing %s.\n", trace ? "on" : "off"); -} -void -setverbose(int argc __unused, char **argv __unused) -{ verbose = !verbose; printf("Verbose mode %s.\n", verbose ? "on" : "off"); } + +static void +setoptions(int argc, char *argv[]) +{ + + if (argc == 2) { + if (strcasecmp(argv[1], "enable") == 0 || + strcasecmp(argv[1], "on") == 0) { + options_extra_enabled = 1; + options_rfc_enabled = 1; + } + if (strcasecmp(argv[1], "disable") == 0 || + strcasecmp(argv[1], "off") == 0) { + options_extra_enabled = 0; + options_rfc_enabled = 0; + } + if (strcasecmp(argv[1], "extra") == 0) + options_extra_enabled = !options_extra_enabled; + } + printf("Support for RFC2347 style options are now %s.\n", + options_rfc_enabled ? "enabled" : "disabled"); + printf("Support for non-RFC defined options are now %s.\n", + options_extra_enabled ? "enabled" : "disabled"); + + printf("\nThe following options are available:\n" + "\toptions on : enable support for RFC2347 style options\n" + "\toptions off : disable support for RFC2347 style options\n" + "\toptions extra : toggle support for non-RFC defined options\n" + ); +} + +static void +setrollover(int argc, char *argv[]) +{ + + if (argc == 2) { + if (strcasecmp(argv[1], "never") == 0 || + strcasecmp(argv[1], "none") == 0) { + free(options[OPT_ROLLOVER].o_request); + options[OPT_ROLLOVER].o_request = NULL; + } + if (strcasecmp(argv[1], "1") == 0) { + free(options[OPT_ROLLOVER].o_request); + options[OPT_ROLLOVER].o_request = strdup("1"); + } + if (strcasecmp(argv[1], "0") == 0) { + free(options[OPT_ROLLOVER].o_request); + options[OPT_ROLLOVER].o_request = strdup("0"); + } + } + printf("Support for the rollover options is %s.\n", + options[OPT_ROLLOVER].o_request != NULL ? "enabled" : "disabled"); + if (options[OPT_ROLLOVER].o_request != NULL) + printf("Block rollover will be to block %s.\n", + options[OPT_ROLLOVER].o_request); + + + printf("\nThe following rollover options are available:\n" + "\trollover 0 : rollover to block zero (default)\n" + "\trollover 1 : rollover to block one\n" + "\trollover never : do not support the rollover option\n" + "\trollover none : do not support the rollover option\n" + ); +} + +static void +setdebug(int argc, char *argv[]) +{ + int i; + + if (argc != 1) { + i = 1; + while (i < argc) + debug ^= debug_find(argv[i++]); + } + printf("The following debugging is enabled: %s\n", debug_show(debug)); + + printf("\nThe following debugs are available:\n"); + i = 0; + while (debugs[i].name != NULL) { + printf("\t%s\t%s\n", debugs[i].name, debugs[i].desc); + i++; + } +} + +static void +setblocksize(int argc, char *argv[]) +{ + + if (!options_rfc_enabled) + printf("RFC2347 style options are not enabled " + "(but proceding anyway)\n"); + + if (argc != 1) { + int size = atoi(argv[1]); + size_t max; + char maxbuffer[100]; + int *maxdgram; + + max = sizeof(maxbuffer); + if (sysctlbyname("net.inet.udp.maxdgram", + maxbuffer, &max, NULL, 0) < 0) { + perror("sysctl: net.inet.udp.maxdgram"); + return; + } + maxdgram = (int *)maxbuffer; + + if (size < BLKSIZE_MIN || size > BLKSIZE_MAX) { + printf("Blocksize should be between %d and %d bytes.\n", + BLKSIZE_MIN, BLKSIZE_MAX); + return; + } else if (size > *maxdgram - 4) { + printf("Blocksize can't be bigger than %d bytes due " + "to the net.inet.udp.maxdgram sysctl limitation.\n", + *maxdgram - 4); + asprintf(&options[OPT_BLKSIZE].o_request, + "%d", *maxdgram - 4); + } else { + asprintf(&options[OPT_BLKSIZE].o_request, "%d", size); + } + } + printf("Blocksize is now %s bytes.\n", options[OPT_BLKSIZE].o_request); +} + +static void +setblocksize2(int argc, char *argv[]) +{ + + if (!options_rfc_enabled || !options_extra_enabled) + printf( + "RFC2347 style or non-RFC defined options are not enabled " + "(but proceding anyway)\n"); + + if (argc != 1) { + int size = atoi(argv[1]); + int i; + size_t max; + char maxbuffer[100]; + int *maxdgram; + + int sizes[] = { + 8, 16, 32, 64, 128, 256, 512, 1024, + 2048, 4096, 8192, 16384, 32768, 0 + }; + + max = sizeof(maxbuffer); + if (sysctlbyname("net.inet.udp.maxdgram", + maxbuffer, &max, NULL, 0) < 0) { + perror("sysctl: net.inet.udp.maxdgram"); + return; + } + maxdgram = (int *)maxbuffer; + + for (i = 0; sizes[i] != 0; i++) { + if (sizes[i] == size) break; + } + if (sizes[i] == 0) { + printf("Blocksize2 should be a power of two between " + "8 and 32768.\n"); + return; + } + + if (size < BLKSIZE_MIN || size > BLKSIZE_MAX) { + printf("Blocksize2 should be between " + "%d and %d bytes.\n", BLKSIZE_MIN, BLKSIZE_MAX); + return; + } else if (size > *maxdgram - 4) { + printf("Blocksize2 can't be bigger than %d bytes due " + "to the net.inet.udp.maxdgram sysctl limitation.\n", + *maxdgram - 4); + for (i = 0; sizes[i+1] != 0; i++) { + if (*maxdgram < sizes[i+1]) break; + } + asprintf(&options[OPT_BLKSIZE2].o_request, + "%d", sizes[i]); + } else { + asprintf(&options[OPT_BLKSIZE2].o_request, "%d", size); + } + } + printf("Blocksize2 is now %s bytes.\n", + options[OPT_BLKSIZE2].o_request); +} + +static void +setpacketdrop(int argc, char *argv[]) +{ + + if (argc != 1) + packetdroppercentage = atoi(argv[1]); + + printf("Randomly %d in 100 packets will be dropped\n", + packetdroppercentage); +} diff --git a/usr.bin/tftp/tftp.1 b/usr.bin/tftp/tftp.1 index 8924eb5..04daa87 100644 --- a/usr.bin/tftp/tftp.1 +++ b/usr.bin/tftp/tftp.1 @@ -37,7 +37,7 @@ .Os .Sh NAME .Nm tftp -.Nd "trivial file transfer program" +.Nd trivial file transfer program .Sh SYNOPSIS .Nm .Op Ar host Op Ar port @@ -55,28 +55,26 @@ may be specified on the command line, in which case uses .Ar host as the default host for future transfers (see the -.Ic connect +.Cm connect command below). .Sh COMMANDS Once .Nm is running, it issues the prompt -.Dq Li "tftp> " +.Dq Li tftp> and recognizes the following commands: .Pp -.Bl -tag -width ".Ic verbose" -compact -.It Ic \&? Ar command-name ... +.Bl -tag -width verbose -compact +.It Cm \&? Ar command-name ... Print help information. .Pp -.It Ic ascii -Shorthand for -.Ic mode Cm ascii . +.It Cm ascii +Shorthand for "mode ascii" .Pp -.It Ic binary -Shorthand for -.Ic mode Cm binary . +.It Cm binary +Shorthand for "mode binary" .Pp -.It Ic connect Ar host Op Ar port +.It Cm connect Ar host Op Ar port Set the .Ar host (and optionally @@ -88,19 +86,19 @@ protocol, unlike the .Tn FTP protocol, does not maintain connections between transfers; thus, the -.Ic connect +.Cm connect command does not actually create a connection, but merely remembers what host is to be used for transfers. You do not have to use the -.Ic connect +.Cm connect command; the remote host can be specified as part of the -.Ic get +.Cm get or -.Ic put +.Cm put commands. .Pp -.It Ic get Oo Ar host : Oc Ns Ar file Op Ar localname -.It Ic get Xo +.It Cm get Oo Ar host : Oc Ns Ar file Op Ar localname +.It Cm get Xo .Oo Ar host1 : Oc Ns Ar file1 .Oo Ar host2 : Oc Ns Ar file2 ... .Oo Ar hostN : Oc Ns Ar fileN @@ -126,18 +124,18 @@ to disambiguate the colons used in the IPv6 address from the colon separating the host and the filename. .Pp -.It Ic mode Ar transfer-mode +.It Cm mode Ar transfer-mode Set the mode for transfers; .Ar transfer-mode may be one of -.Cm ascii +.Em ascii or -.Cm binary . +.Em binary . The default is -.Cm ascii . +.Em ascii . .Pp -.It Ic put Ar file Op Oo Ar host : Oc Ns Ar remotename -.It Ic put Ar file1 file2 ... fileN Op Oo Ar host : Oc Ns Ar remote-directory +.It Cm put Ar file Op Oo Ar host : Oc Ns Ar remotename +.It Cm put Ar file1 file2 ... fileN Op Oo Ar host : Oc Ns Ar remote-directory Put a file or set of files to the remote host. When .Ar remotename @@ -152,27 +150,27 @@ machine. To specify an IPv6 numeric address for a .Ar host , see the example under the -.Ic get +.Cm get command. .Pp -.It Ic quit +.It Cm quit Exit .Nm . An end of file also exits. .Pp -.It Ic rexmt Ar retransmission-timeout +.It Cm rexmt Ar retransmission-timeout Set the per-packet retransmission timeout, in seconds. .Pp -.It Ic status +.It Cm status Show current status. .Pp -.It Ic timeout Ar total-transmission-timeout +.It Cm timeout Ar total-transmission-timeout Set the total transmission timeout, in seconds. .Pp -.It Ic trace +.It Cm trace Toggle packet tracing. .Pp -.It Ic verbose +.It Cm verbose Toggle verbose mode. .El .Sh HISTORY diff --git a/usr.bin/tftp/tftp.c b/usr.bin/tftp/tftp.c index 4300902..29ba2b2 100644 --- a/usr.bin/tftp/tftp.c +++ b/usr.bin/tftp/tftp.c @@ -45,446 +45,230 @@ __FBSDID("$FreeBSD$"); /* * TFTP User Program -- Protocol Machines */ -#include <sys/types.h> #include <sys/socket.h> -#include <sys/time.h> +#include <sys/stat.h> #include <netinet/in.h> -#include <arpa/inet.h> #include <arpa/tftp.h> #include <err.h> -#include <errno.h> -#include <setjmp.h> -#include <signal.h> +#include <netdb.h> #include <stdio.h> +#include <stdlib.h> #include <string.h> -#include <unistd.h> -#include <netdb.h> +#include <syslog.h> -#include "extern.h" -#include "tftpsubs.h" - -extern struct sockaddr_storage peeraddr; /* filled in by main */ -extern int f; /* the opened socket */ -extern int trace; -extern int verbose; -extern int rexmtval; -extern int maxtimeout; -extern volatile int txrx_error; - -#define PKTSIZE SEGSIZE+4 -char ackbuf[PKTSIZE]; -int timeout; -jmp_buf toplevel; -jmp_buf timeoutbuf; - -static void nak(int, const struct sockaddr *); -static int makerequest(int, const char *, struct tftphdr *, const char *); -static void printstats(const char *, unsigned long); -static void startclock(void); -static void stopclock(void); -static void timer(int); -static void tpacket(const char *, struct tftphdr *, int); -static int cmpport(const struct sockaddr *, const struct sockaddr *); +#include "tftp.h" +#include "tftp-file.h" +#include "tftp-utils.h" +#include "tftp-io.h" +#include "tftp-transfer.h" +#include "tftp-options.h" /* * Send the requested file. */ void -xmitfile(int fd, const char *name, const char *mode) +xmitfile(int peer, char *port, int fd, char *name, char *mode) { - struct tftphdr *ap; /* data and ack packets */ - struct tftphdr *dp; - int n; - volatile unsigned short block; - volatile int size, convert; - volatile unsigned long amount; - struct sockaddr_storage from; - socklen_t fromlen; - FILE *file; - struct sockaddr_storage peer; + struct tftphdr *rp; + int n, i; + uint16_t block; + uint32_t amount; struct sockaddr_storage serv; /* valid server port number */ + char recvbuffer[MAXPKTSIZE]; + struct tftp_stats tftp_stats; + + stats_init(&tftp_stats); - startclock(); /* start stat's clock */ - dp = r_init(); /* reset fillbuf/read-ahead code */ - ap = (struct tftphdr *)ackbuf; - file = fdopen(fd, "r"); - convert = !strcmp(mode, "netascii"); - block = 0; - amount = 0; - memcpy(&peer, &peeraddr, peeraddr.ss_len); memset(&serv, 0, sizeof(serv)); + rp = (struct tftphdr *)recvbuffer; + + if (port == NULL) { + struct servent *se; + se = getservbyname("tftp", "udp"); + ((struct sockaddr_in *)&peer_sock)->sin_port = se->s_port; + } else + ((struct sockaddr_in *)&peer_sock)->sin_port = + htons(atoi(port)); + + for (i = 0; i < 12; i++) { + struct sockaddr_storage from; + + /* Tell the other side what we want to do */ + if (debug&DEBUG_SIMPLE) + printf("Sending %s\n", name); + + n = send_wrq(peer, name, mode); + if (n > 0) { + printf("Cannot send WRQ packet\n"); + return; + } - signal(SIGALRM, timer); - do { - if (block == 0) - size = makerequest(WRQ, name, dp, mode) - 4; - else { - /* size = read(fd, dp->th_data, SEGSIZE); */ - size = readit(file, &dp, convert); - if (size < 0) { - nak(errno + 100, (struct sockaddr *)&peer); - break; - } - dp->th_opcode = htons((u_short)DATA); - dp->th_block = htons((u_short)block); + /* + * The first packet we receive has the new destination port + * we have to send the next packets to. + */ + n = receive_packet(peer, recvbuffer, + MAXPKTSIZE, &from, timeoutpacket); + + /* We got some data! */ + if (n >= 0) { + ((struct sockaddr_in *)&peer_sock)->sin_port = + ((struct sockaddr_in *)&from)->sin_port; + break; } - timeout = 0; - (void) setjmp(timeoutbuf); -send_data: - if (trace) - tpacket("sent", dp, size + 4); - n = sendto(f, dp, size + 4, 0, - (struct sockaddr *)&peer, peer.ss_len); - if (n != size + 4) { - warn("sendto"); - txrx_error = 1; - goto abort; + + /* This should be retried */ + if (n == RP_TIMEOUT) { + printf("Try %d, didn't receive answer from remote.\n", + i + 1); + continue; } - read_ahead(file, convert); - for ( ; ; ) { - alarm(rexmtval); - do { - fromlen = sizeof(from); - n = recvfrom(f, ackbuf, sizeof(ackbuf), 0, - (struct sockaddr *)&from, &fromlen); - } while (n <= 0); - alarm(0); - if (n < 0) { - warn("recvfrom"); - txrx_error = 1; - goto abort; - } - if (!serv.ss_family) - serv = from; - else if (!cmpport((struct sockaddr *)&serv, - (struct sockaddr *)&from)) { - warn("server port mismatch"); - txrx_error = 1; - goto abort; - } - peer = from; - if (trace) - tpacket("received", ap, n); - /* should verify packet came from server */ - ap->th_opcode = ntohs(ap->th_opcode); - ap->th_block = ntohs(ap->th_block); - if (ap->th_opcode == ERROR) { - printf("Error code %d: %s\n", ap->th_code, - ap->th_msg); - txrx_error = 1; - goto abort; - } - if (ap->th_opcode == ACK) { - int j; - - if (ap->th_block == block) { - break; - } - /* On an error, try to synchronize - * both sides. - */ - j = synchnet(f); - if (j && trace) { - printf("discarded %d packets\n", - j); - } - if (ap->th_block == (block-1)) { - goto send_data; - } - } + + /* Everything else is fatal */ + break; + } + if (i == 12) { + printf("Transfer timed out.\n"); + return; + } + if (rp->th_opcode == ERROR) { + printf("Got ERROR, aborted\n"); + return; + } + + /* + * If the first packet is an OACK instead of an ACK packet, + * handle it different. + */ + if (rp->th_opcode == OACK) { + if (!options_rfc_enabled) { + printf("Got OACK while options are not enabled!\n"); + send_error(peer, EBADOP); + return; } - if (block > 0) - amount += size; - block++; - } while (size == SEGSIZE || block == 1); -abort: - fclose(file); - stopclock(); - if (amount > 0) - printstats("Sent", amount); -} -/* - * Receive a file. - */ -void -recvfile(int fd, const char *name, const char *mode) -{ - struct tftphdr *ap; - struct tftphdr *dp; - int n; - volatile unsigned short block; - volatile int size, firsttrip; - volatile unsigned long amount; - struct sockaddr_storage from; - socklen_t fromlen; - FILE *file; - volatile int convert; /* true if converting crlf -> lf */ - struct sockaddr_storage peer; - struct sockaddr_storage serv; /* valid server port number */ + parse_options(peer, rp->th_stuff, n + 2); + } + + if (read_init(fd, NULL, mode) < 0) { + warn("read_init()"); + return; + } - startclock(); - dp = w_init(); - ap = (struct tftphdr *)ackbuf; - file = fdopen(fd, "w"); - convert = !strcmp(mode, "netascii"); block = 1; - firsttrip = 1; - amount = 0; - memcpy(&peer, &peeraddr, peeraddr.ss_len); - memset(&serv, 0, sizeof(serv)); + tftp_send(peer, &block, &tftp_stats); - signal(SIGALRM, timer); - do { - if (firsttrip) { - size = makerequest(RRQ, name, ap, mode); - firsttrip = 0; - } else { - ap->th_opcode = htons((u_short)ACK); - ap->th_block = htons((u_short)(block)); - size = 4; - block++; - } - timeout = 0; - (void) setjmp(timeoutbuf); -send_ack: - if (trace) - tpacket("sent", ap, size); - if (sendto(f, ackbuf, size, 0, (struct sockaddr *)&peer, - peer.ss_len) != size) { - alarm(0); - warn("sendto"); - txrx_error = 1; - goto abort; - } - write_behind(file, convert); - for ( ; ; ) { - alarm(rexmtval); - do { - fromlen = sizeof(from); - n = recvfrom(f, dp, PKTSIZE, 0, - (struct sockaddr *)&from, &fromlen); - } while (n <= 0); - alarm(0); - if (n < 0) { - warn("recvfrom"); - txrx_error = 1; - goto abort; - } - if (!serv.ss_family) - serv = from; - else if (!cmpport((struct sockaddr *)&serv, - (struct sockaddr *)&from)) { - warn("server port mismatch"); - txrx_error = 1; - goto abort; - } - peer = from; - if (trace) - tpacket("received", dp, n); - /* should verify client address */ - dp->th_opcode = ntohs(dp->th_opcode); - dp->th_block = ntohs(dp->th_block); - if (dp->th_opcode == ERROR) { - printf("Error code %d: %s\n", dp->th_code, - dp->th_msg); - txrx_error = 1; - goto abort; - } - if (dp->th_opcode == DATA) { - int j; - - if (dp->th_block == block) { - break; /* have next packet */ - } - /* On an error, try to synchronize - * both sides. - */ - j = synchnet(f); - if (j && trace) { - printf("discarded %d packets\n", j); - } - if (dp->th_block == (block-1)) { - goto send_ack; /* resend ack */ - } - } - } - /* size = write(fd, dp->th_data, n - 4); */ - size = writeit(file, &dp, n - 4, convert); - if (size < 0) { - nak(errno + 100, (struct sockaddr *)&peer); - break; - } - amount += size; - } while (size == SEGSIZE); -abort: /* ok to ack, since user */ - ap->th_opcode = htons((u_short)ACK); /* has seen err msg */ - ap->th_block = htons((u_short)block); - (void) sendto(f, ackbuf, 4, 0, (struct sockaddr *)&peer, - peer.ss_len); - write_behind(file, convert); /* flush last buffer */ - fclose(file); - stopclock(); + read_close(); if (amount > 0) - printstats("Received", amount); -} + printstats("Sent", verbose, &tftp_stats); -static int -makerequest(int request, const char *name, struct tftphdr *tp, const char *mode) -{ - char *cp; - - tp->th_opcode = htons((u_short)request); - cp = tp->th_stuff; - strcpy(cp, name); - cp += strlen(name); - *cp++ = '\0'; - strcpy(cp, mode); - cp += strlen(mode); - *cp++ = '\0'; - return (cp - (char *)tp); + txrx_error = 1; } -struct errmsg { - int e_code; - const char *e_msg; -} errmsgs[] = { - { EUNDEF, "Undefined error code" }, - { ENOTFOUND, "File not found" }, - { EACCESS, "Access violation" }, - { ENOSPACE, "Disk full or allocation exceeded" }, - { EBADOP, "Illegal TFTP operation" }, - { EBADID, "Unknown transfer ID" }, - { EEXISTS, "File already exists" }, - { ENOUSER, "No such user" }, - { -1, 0 } -}; - /* - * Send a nak packet (error message). - * Error code passed in is one of the - * standard TFTP codes, or a UNIX errno - * offset by 100. + * Receive a file. */ -static void -nak(int error, const struct sockaddr *peer) -{ - struct errmsg *pe; - struct tftphdr *tp; - int length; - - tp = (struct tftphdr *)ackbuf; - tp->th_opcode = htons((u_short)ERROR); - tp->th_code = htons((u_short)error); - for (pe = errmsgs; pe->e_code >= 0; pe++) - if (pe->e_code == error) - break; - if (pe->e_code < 0) { - pe->e_msg = strerror(error - 100); - tp->th_code = EUNDEF; - } - strcpy(tp->th_msg, pe->e_msg); - length = strlen(pe->e_msg) + 4; - if (trace) - tpacket("sent", tp, length); - if (sendto(f, ackbuf, length, 0, peer, peer->sa_len) != length) - warn("nak"); -} - -static void -tpacket(const char *s, struct tftphdr *tp, int n) +void +recvfile(int peer, char *port, int fd, char *name, char *mode) { - static const char *opcodes[] = - { "#0", "RRQ", "WRQ", "DATA", "ACK", "ERROR" }; - char *cp, *file; - u_short op = ntohs(tp->th_opcode); - - if (op < RRQ || op > ERROR) - printf("%s opcode=%x ", s, op); - else - printf("%s %s ", s, opcodes[op]); - switch (op) { - - case RRQ: - case WRQ: - n -= 2; - file = cp = tp->th_stuff; - cp = index(cp, '\0'); - printf("<file=%s, mode=%s>\n", file, cp + 1); - break; + struct tftphdr *rp; + uint16_t block; + char recvbuffer[MAXPKTSIZE]; + int n, i; + struct tftp_stats tftp_stats; + + stats_init(&tftp_stats); + + rp = (struct tftphdr *)recvbuffer; + + if (port == NULL) { + struct servent *se; + se = getservbyname("tftp", "udp"); + ((struct sockaddr_in *)&peer_sock)->sin_port = se->s_port; + } else + ((struct sockaddr_in *)&peer_sock)->sin_port = + htons(atoi(port)); + + for (i = 0; i < 12; i++) { + struct sockaddr_storage from; + + /* Tell the other side what we want to do */ + if (debug&DEBUG_SIMPLE) + printf("Requesting %s\n", name); + + n = send_rrq(peer, name, mode); + if (n > 0) { + printf("Cannot send RRQ packet\n"); + return; + } - case DATA: - printf("<block=%d, %d bytes>\n", ntohs(tp->th_block), n - 4); - break; + /* + * The first packet we receive has the new destination port + * we have to send the next packets to. + */ + n = receive_packet(peer, recvbuffer, + MAXPKTSIZE, &from, timeoutpacket); + + /* We got something useful! */ + if (n >= 0) { + ((struct sockaddr_in *)&peer_sock)->sin_port = + ((struct sockaddr_in *)&from)->sin_port; + break; + } - case ACK: - printf("<block=%d>\n", ntohs(tp->th_block)); - break; + /* We should retry if this happens */ + if (n == RP_TIMEOUT) { + printf("Try %d, didn't receive answer from remote.\n", + i + 1); + continue; + } - case ERROR: - printf("<code=%d, msg=%s>\n", ntohs(tp->th_code), tp->th_msg); + /* Otherwise it is a fatal error */ break; } -} - -struct timeval tstart; -struct timeval tstop; - -static void -startclock(void) -{ - (void)gettimeofday(&tstart, NULL); -} - -static void -stopclock(void) -{ + if (rp->th_opcode == ERROR) { + tftp_log(LOG_ERR, "Error code %d: %s", rp->th_code, rp->th_msg); + return; + } - (void)gettimeofday(&tstop, NULL); -} + if (write_init(fd, NULL, mode) < 0) { + warn("write_init"); + return; + } -static void -printstats(const char *direction, unsigned long amount) -{ - double delta; - /* compute delta in 1/10's second units */ - delta = ((tstop.tv_sec*10.)+(tstop.tv_usec/100000)) - - ((tstart.tv_sec*10.)+(tstart.tv_usec/100000)); - delta = delta/10.; /* back to seconds */ - printf("%s %ld bytes in %.1f seconds", direction, amount, delta); - if (verbose) - printf(" [%.0f bits/sec]", (amount*8.)/delta); - putchar('\n'); -} + stats_init(&tftp_stats); + + /* + * If the first packet is an OACK packet instead of an DATA packet, + * handle it different. + */ + if (rp->th_opcode == OACK) { + if (!options_rfc_enabled) { + printf("Got OACK while options are not enabled!\n"); + send_error(peer, EBADOP); + return; + } -static void -timer(int sig __unused) -{ + parse_options(peer, rp->th_stuff, n + 2); - timeout += rexmtval; - if (timeout >= maxtimeout) { - printf("Transfer timed out.\n"); - longjmp(toplevel, -1); + n = send_ack(peer, 0); + if (n > 0) { + printf("Cannot send ACK on OACK.\n"); + return; + } + block = 0; + tftp_receive(peer, &block, &tftp_stats, NULL, 0); + } else { + block = 1; + tftp_receive(peer, &block, &tftp_stats, rp, n); } - txrx_error = 1; - longjmp(timeoutbuf, 1); -} - -static int -cmpport(const struct sockaddr *sa, const struct sockaddr *sb) -{ - char a[NI_MAXSERV], b[NI_MAXSERV]; - - if (getnameinfo(sa, sa->sa_len, NULL, 0, a, sizeof(a), NI_NUMERICSERV)) - return 0; - if (getnameinfo(sb, sb->sa_len, NULL, 0, b, sizeof(b), NI_NUMERICSERV)) - return 0; - if (strcmp(a, b) != 0) - return 0; - return 1; + write_close(); + if (tftp_stats.amount > 0) + printstats("Received", verbose, &tftp_stats); + return; } |