Index: head/libexec/tftpd/tftpd.8 =================================================================== --- head/libexec/tftpd/tftpd.8 (revision 71925) +++ head/libexec/tftpd/tftpd.8 (revision 71926) @@ -1,194 +1,199 @@ .\" Copyright (c) 1983, 1991, 1993 .\" The Regents of the University of California. All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" 3. All advertising materials mentioning features or use of this software .\" must display the following acknowledgement: .\" This product includes software developed by the University of .\" California, Berkeley and its contributors. .\" 4. Neither the name of the University nor the names of its contributors .\" may be used to endorse or promote products derived from this software .\" without specific prior written permission. .\" .\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE .\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" .\" @(#)tftpd.8 8.1 (Berkeley) 6/4/93 .\" $FreeBSD$ .\" .Dd September 14, 2000 .Dt TFTPD 8 .Os BSD 4.2 .Sh NAME .Nm tftpd .Nd Internet Trivial File Transfer Protocol server .Sh SYNOPSIS .Nm /usr/libexec/tftpd .Op Fl cCln .Op Fl s Ar directory .Op Fl u Ar user .Op Ar directory ... .Sh DESCRIPTION .Nm Tftpd is a server which supports the Internet Trivial File Transfer Protocol .Pq Tn RFC 1350 . The .Tn TFTP server operates at the port indicated in the .Ql tftp service description; see .Xr services 5 . The server is normally started by .Xr inetd 8 . .Pp The use of .Xr tftp 1 does not require an account or password on the remote system. Due to the lack of authentication information, .Nm will allow only publicly readable files to be accessed. Files containing the string ``/\|\fB.\|.\fP\|/'' or starting with ``\|\fB.\|.\fP\|/'' are not allowed. Files may be written only if they already exist and are publicly writable. Note that this extends the concept of .Dq public to include all users on all hosts that can be reached through the network; this may not be appropriate on all systems, and its implications should be considered before enabling tftp service. The server should have the user ID with the lowest possible privilege. .Pp Access to files may be restricted by invoking .Nm with a list of directories by including up to 20 pathnames as server program arguments in .Pa /etc/inetd.conf . In this case access is restricted to files whose names are prefixed by the one of the given directories. The given directories are also treated as a search path for relative filename requests. .Pp The .Fl s option provides additional security by changing .Nm Ns No 's root directory, thereby prohibiting accesses outside of the specified .Ar directory . Because .Xr chroot 2 requires super-user privileges, .Nm must be run as root. However, after performing the .Fn chroot , .Nm will set its user id to that of the specified .Ar user , or .Dq nobody if no .Fl u option is specified. .Pp The options are: .Bl -tag -width Ds .It Fl c Changes the default root directory of a connecting host via chroot based on the connecting IP address. This prevents multiple clients from writing to the same file at the same time. If the directory does not exist, the client connection is refused. The .Fl s option is required for .Fl c and the specified .Ar directory is used as a base. .It Fl C Operates the same as .Fl c except it falls back to .Fl s Ns No 's .Ar directory if a directory does not exist for the client's IP. .It Fl l Log all requests using .Xr syslog 3 with the facility of .Dv LOG_FTP . Note: Logging of .Dv LOG_FTP messages must also be enabled in the syslog configuration file, .Xr syslog.conf 5 . .It Fl n Suppress negative acknowledgement of requests for nonexistent relative filenames. .It Fl s Ar directory Cause .Nm to change its root directory to .Pa directory . After changing roots but before accepting commands, .Nm will switch credentials to an unprivileged user. .It Fl u Ar user Switch credentials to .Ar user (default .Dq nobody ) when the .Fl s option is used. The user must be specified by name, not a numeric UID. .El .Sh SEE ALSO .Xr tftp 1 , .Xr chroot 2 , .Xr inetd 8 , .Xr syslogd 8 .Rs .%A K. R. Sollins .%T The TFTP Protocol (Revision 2) .%D July 1992 .%O RFC 1350, STD 33 .Re .Sh HISTORY The .Nm command appeared in .Bx 4.2 ; the .Fl s option was introduced in .Fx 2.2 , the .Fl u option was introduced in .Fx 4.2 , and the .Fl c option was introduced in .Fx 5.0 . +.Sh BUGS +Files larger than 33488896 octets (65535 blocks) cannot be transferred +without client and server supporting blocksize negotiation (RFC1783). +.Pp +Many tftp clients will not transfer files over 16744448 octets (32767 blocks). Index: head/libexec/tftpd/tftpd.c =================================================================== --- head/libexec/tftpd/tftpd.c (revision 71925) +++ head/libexec/tftpd/tftpd.c (revision 71926) @@ -1,702 +1,702 @@ /* * Copyright (c) 1983, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint static const char copyright[] = "@(#) Copyright (c) 1983, 1993\n\ The Regents of the University of California. All rights reserved.\n"; #endif /* not lint */ #ifndef lint #if 0 static char sccsid[] = "@(#)tftpd.c 8.1 (Berkeley) 6/4/93"; #endif static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ /* * Trivial file transfer protocol server. * * This version includes many modifications by Jim Guyton * . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tftpsubs.h" #define TIMEOUT 5 int peer; int rexmtval = TIMEOUT; int maxtimeout = 5*TIMEOUT; #define PKTSIZE SEGSIZE+4 char buf[PKTSIZE]; char ackbuf[PKTSIZE]; struct sockaddr_in from; int fromlen; void tftp __P((struct tftphdr *, int)); /* * Null-terminated directory prefix list for absolute pathname requests and * search list for relative pathname requests. * * MAXDIRS should be at least as large as the number of arguments that * inetd allows (currently 20). */ #define MAXDIRS 20 static struct dirlist { char *name; int len; } dirs[MAXDIRS+1]; static int suppress_naks; static int logging; static int ipchroot; static char *errtomsg __P((int)); static void nak __P((int)); int main(argc, argv) int argc; char *argv[]; { register struct tftphdr *tp; register int n; int ch, on; struct sockaddr_in sin; char *chroot_dir = NULL; struct passwd *nobody; char *chuser = "nobody"; openlog("tftpd", LOG_PID | LOG_NDELAY, LOG_FTP); while ((ch = getopt(argc, argv, "cClns:u:")) != -1) { switch (ch) { case 'c': ipchroot = 1; break; case 'C': ipchroot = 2; break; case 'l': logging = 1; break; case 'n': suppress_naks = 1; break; case 's': chroot_dir = optarg; break; case 'u': chuser = optarg; break; default: syslog(LOG_WARNING, "ignoring unknown option -%c", ch); } } if (optind < argc) { struct dirlist *dirp; /* Get list of directory prefixes. Skip relative pathnames. */ for (dirp = dirs; optind < argc && dirp < &dirs[MAXDIRS]; optind++) { if (argv[optind][0] == '/') { dirp->name = argv[optind]; dirp->len = strlen(dirp->name); dirp++; } } } else if (chroot_dir) { dirs->name = "/"; dirs->len = 1; } if (ipchroot && chroot_dir == NULL) { syslog(LOG_ERR, "-c requires -s"); exit(1); } on = 1; if (ioctl(0, FIONBIO, &on) < 0) { syslog(LOG_ERR, "ioctl(FIONBIO): %m"); exit(1); } fromlen = sizeof (from); n = recvfrom(0, buf, sizeof (buf), 0, (struct sockaddr *)&from, &fromlen); if (n < 0) { syslog(LOG_ERR, "recvfrom: %m"); exit(1); } /* * Now that we have read the message out of the UDP * socket, we fork and exit. Thus, inetd will go back * to listening to the tftp port, and the next request * to come in will start up a new instance of tftpd. * * We do this so that inetd can run tftpd in "wait" mode. * The problem with tftpd running in "nowait" mode is that * inetd may get one or more successful "selects" on the * tftp port before we do our receive, so more than one * instance of tftpd may be started up. Worse, if tftpd * break before doing the above "recvfrom", inetd would * spawn endless instances, clogging the system. */ { int pid; int i, j; for (i = 1; i < 20; i++) { pid = fork(); if (pid < 0) { sleep(i); /* * flush out to most recently sent request. * * This may drop some request, but those * will be resent by the clients when * they timeout. The positive effect of * this flush is to (try to) prevent more * than one tftpd being started up to service * a single request from a single client. */ j = sizeof from; i = recvfrom(0, buf, sizeof (buf), 0, (struct sockaddr *)&from, &j); if (i > 0) { n = i; fromlen = j; } } else { break; } } if (pid < 0) { syslog(LOG_ERR, "fork: %m"); exit(1); } else if (pid != 0) { exit(0); } } /* * Since we exit here, we should do that only after the above * recvfrom to keep inetd from constantly forking should there * be a problem. See the above comment about system clogging. */ if (chroot_dir) { if (ipchroot) { char *tempchroot; struct stat sb; int statret; tempchroot = inet_ntoa(from.sin_addr); asprintf(&tempchroot, "%s/%s", chroot_dir, tempchroot); statret = stat(tempchroot, &sb); if ((sb.st_mode & S_IFDIR) && (statret == 0 || (statret == -1 && ipchroot == 1))) chroot_dir = tempchroot; } /* Must get this before chroot because /etc might go away */ if ((nobody = getpwnam(chuser)) == NULL) { syslog(LOG_ERR, "%s: no such user", chuser); exit(1); } if (chroot(chroot_dir)) { syslog(LOG_ERR, "chroot: %s: %m", chroot_dir); exit(1); } chdir( "/" ); setuid(nobody->pw_uid); } from.sin_family = AF_INET; alarm(0); close(0); close(1); peer = socket(AF_INET, SOCK_DGRAM, 0); if (peer < 0) { syslog(LOG_ERR, "socket: %m"); exit(1); } memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; if (bind(peer, (struct sockaddr *)&sin, sizeof (sin)) < 0) { syslog(LOG_ERR, "bind: %m"); exit(1); } if (connect(peer, (struct sockaddr *)&from, sizeof(from)) < 0) { syslog(LOG_ERR, "connect: %m"); exit(1); } tp = (struct tftphdr *)buf; tp->th_opcode = ntohs(tp->th_opcode); if (tp->th_opcode == RRQ || tp->th_opcode == WRQ) tftp(tp, n); exit(1); } struct formats; int validate_access __P((char **, int)); void xmitfile __P((struct formats *)); void recvfile __P((struct formats *)); struct formats { char *f_mode; int (*f_validate) __P((char **, int)); void (*f_send) __P((struct formats *)); void (*f_recv) __P((struct formats *)); int f_convert; } formats[] = { { "netascii", validate_access, xmitfile, recvfile, 1 }, { "octet", validate_access, xmitfile, recvfile, 0 }, #ifdef notdef { "mail", validate_user, sendmail, recvmail, 1 }, #endif { 0 } }; /* * Handle initial connection protocol. */ void tftp(tp, size) struct tftphdr *tp; int size; { register char *cp; int first = 1, ecode; register struct formats *pf; char *filename, *mode; filename = cp = tp->th_stuff; again: while (cp < buf + size) { if (*cp == '\0') break; cp++; } if (*cp != '\0') { nak(EBADOP); exit(1); } if (first) { mode = ++cp; first = 0; goto again; } for (cp = mode; *cp; cp++) if (isupper(*cp)) *cp = tolower(*cp); for (pf = formats; pf->f_mode; pf++) if (strcmp(pf->f_mode, mode) == 0) break; if (pf->f_mode == 0) { nak(EBADOP); exit(1); } ecode = (*pf->f_validate)(&filename, tp->th_opcode); if (logging) { char host[MAXHOSTNAMELEN]; realhostname(host, sizeof(host) - 1, &from.sin_addr); host[sizeof(host) - 1] = '\0'; syslog(LOG_INFO, "%s: %s request for %s: %s", host, tp->th_opcode == WRQ ? "write" : "read", filename, errtomsg(ecode)); } if (ecode) { /* * Avoid storms of naks to a RRQ broadcast for a relative * bootfile pathname from a diskless Sun. */ if (suppress_naks && *filename != '/' && ecode == ENOTFOUND) exit(0); nak(ecode); exit(1); } if (tp->th_opcode == WRQ) (*pf->f_recv)(pf); else (*pf->f_send)(pf); exit(0); } FILE *file; /* * Validate file access. Since we * have no uid or gid, for now require * file to exist and be publicly * readable/writable. * If we were invoked with arguments * from inetd then the file must also be * in one of the given directory prefixes. * Note also, full path name must be * given as we have no login directory. */ int validate_access(filep, mode) char **filep; int mode; { struct stat stbuf; int fd; struct dirlist *dirp; static char pathname[MAXPATHLEN]; char *filename = *filep; /* * Prevent tricksters from getting around the directory restrictions */ if (strstr(filename, "/../")) return (EACCESS); if (*filename == '/') { /* * Allow the request if it's in one of the approved locations. * Special case: check the null prefix ("/") by looking * for length = 1 and relying on the arg. processing that * it's a /. */ for (dirp = dirs; dirp->name != NULL; dirp++) { if (dirp->len == 1 || (!strncmp(filename, dirp->name, dirp->len) && filename[dirp->len] == '/')) break; } /* If directory list is empty, allow access to any file */ if (dirp->name == NULL && dirp != dirs) return (EACCESS); if (stat(filename, &stbuf) < 0) return (errno == ENOENT ? ENOTFOUND : EACCESS); if ((stbuf.st_mode & S_IFMT) != S_IFREG) return (ENOTFOUND); if (mode == RRQ) { if ((stbuf.st_mode & S_IROTH) == 0) return (EACCESS); } else { if ((stbuf.st_mode & S_IWOTH) == 0) return (EACCESS); } } else { int err; /* * Relative file name: search the approved locations for it. * Don't allow write requests that avoid directory * restrictions. */ if (!strncmp(filename, "../", 3)) return (EACCESS); /* * If the file exists in one of the directories and isn't * readable, continue looking. However, change the error code * to give an indication that the file exists. */ err = ENOTFOUND; for (dirp = dirs; dirp->name != NULL; dirp++) { snprintf(pathname, sizeof(pathname), "%s/%s", dirp->name, filename); if (stat(pathname, &stbuf) == 0 && (stbuf.st_mode & S_IFMT) == S_IFREG) { if ((stbuf.st_mode & S_IROTH) != 0) { break; } err = EACCESS; } } if (dirp->name == NULL) return (err); *filep = filename = pathname; } fd = open(filename, mode == RRQ ? O_RDONLY : O_WRONLY|O_TRUNC); if (fd < 0) return (errno + 100); file = fdopen(fd, (mode == RRQ)? "r":"w"); if (file == NULL) { return errno+100; } return (0); } int timeout; jmp_buf timeoutbuf; void timer() { timeout += rexmtval; if (timeout >= maxtimeout) exit(1); longjmp(timeoutbuf, 1); } /* * Send the requested file. */ void xmitfile(pf) struct formats *pf; { struct tftphdr *dp, *r_init(); register struct tftphdr *ap; /* ack packet */ register int size, n; - volatile int block; + volatile unsigned short block; signal(SIGALRM, timer); dp = r_init(); ap = (struct tftphdr *)ackbuf; block = 1; do { size = readit(file, &dp, pf->f_convert); if (size < 0) { nak(errno + 100); goto abort; } dp->th_opcode = htons((u_short)DATA); dp->th_block = htons((u_short)block); timeout = 0; (void)setjmp(timeoutbuf); send_data: if (send(peer, dp, size + 4, 0) != size + 4) { syslog(LOG_ERR, "write: %m"); goto abort; } read_ahead(file, pf->f_convert); for ( ; ; ) { alarm(rexmtval); /* read the ack */ n = recv(peer, ackbuf, sizeof (ackbuf), 0); alarm(0); if (n < 0) { syslog(LOG_ERR, "read: %m"); goto abort; } ap->th_opcode = ntohs((u_short)ap->th_opcode); ap->th_block = ntohs((u_short)ap->th_block); if (ap->th_opcode == ERROR) goto abort; if (ap->th_opcode == ACK) { if (ap->th_block == block) break; /* Re-synchronize with the other side */ (void) synchnet(peer); if (ap->th_block == (block -1)) goto send_data; } } block++; } while (size == SEGSIZE); abort: (void) fclose(file); } void justquit() { exit(0); } /* * Receive a file. */ void recvfile(pf) struct formats *pf; { struct tftphdr *dp, *w_init(); register struct tftphdr *ap; /* ack buffer */ register int n, size; - volatile int block; + volatile unsigned short block; signal(SIGALRM, timer); dp = w_init(); ap = (struct tftphdr *)ackbuf; block = 0; do { timeout = 0; ap->th_opcode = htons((u_short)ACK); ap->th_block = htons((u_short)block); block++; (void) setjmp(timeoutbuf); send_ack: if (send(peer, ackbuf, 4, 0) != 4) { syslog(LOG_ERR, "write: %m"); goto abort; } write_behind(file, pf->f_convert); for ( ; ; ) { alarm(rexmtval); n = recv(peer, dp, PKTSIZE, 0); alarm(0); if (n < 0) { /* really? */ syslog(LOG_ERR, "read: %m"); goto abort; } dp->th_opcode = ntohs((u_short)dp->th_opcode); dp->th_block = ntohs((u_short)dp->th_block); if (dp->th_opcode == ERROR) goto abort; if (dp->th_opcode == DATA) { if (dp->th_block == block) { break; /* normal */ } /* Re-synchronize with the other side */ (void) synchnet(peer); if (dp->th_block == (block-1)) goto send_ack; /* rexmit */ } } /* size = write(file, dp->th_data, n - 4); */ size = writeit(file, &dp, n - 4, pf->f_convert); if (size != (n-4)) { /* ahem */ if (size < 0) nak(errno + 100); else nak(ENOSPACE); goto abort; } } while (size == SEGSIZE); write_behind(file, pf->f_convert); (void) fclose(file); /* close data file */ ap->th_opcode = htons((u_short)ACK); /* send the "final" ack */ ap->th_block = htons((u_short)(block)); (void) send(peer, ackbuf, 4, 0); signal(SIGALRM, justquit); /* just quit on timeout */ alarm(rexmtval); n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */ alarm(0); if (n >= 4 && /* if read some data */ dp->th_opcode == DATA && /* and got a data block */ block == dp->th_block) { /* then my last ack was lost */ (void) send(peer, ackbuf, 4, 0); /* resend final ack */ } abort: return; } struct errmsg { int e_code; 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 } }; static char * errtomsg(error) int error; { static char buf[20]; register struct errmsg *pe; if (error == 0) return "success"; for (pe = errmsgs; pe->e_code >= 0; pe++) if (pe->e_code == error) return pe->e_msg; snprintf(buf, sizeof(buf), "error %d", error); return buf; } /* * Send a nak packet (error message). * Error code passed in is one of the * standard TFTP codes, or a UNIX errno * offset by 100. */ static void nak(error) int error; { register struct tftphdr *tp; int length; register struct errmsg *pe; tp = (struct tftphdr *)buf; 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; /* set 'undef' errorcode */ } strcpy(tp->th_msg, pe->e_msg); length = strlen(pe->e_msg); tp->th_msg[length] = '\0'; length += 5; if (send(peer, buf, length, 0) != length) syslog(LOG_ERR, "nak: %m"); } Index: head/usr.bin/tftp/tftp.1 =================================================================== --- head/usr.bin/tftp/tftp.1 (revision 71925) +++ head/usr.bin/tftp/tftp.1 (revision 71926) @@ -1,174 +1,177 @@ .\" Copyright (c) 1990, 1993, 1994 .\" The Regents of the University of California. All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" 3. All advertising materials mentioning features or use of this software .\" must display the following acknowledgement: .\" This product includes software developed by the University of .\" California, Berkeley and its contributors. .\" 4. Neither the name of the University nor the names of its contributors .\" may be used to endorse or promote products derived from this software .\" without specific prior written permission. .\" .\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE .\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" .\" @(#)tftp.1 8.2 (Berkeley) 4/18/94 .\" $FreeBSD$ .\" .Dd April 18, 1994 .Dt TFTP 1 .Os BSD 4.3 .Sh NAME .Nm tftp .Nd trivial file transfer program .Sh SYNOPSIS .Nm .Op Ar host .Sh DESCRIPTION .Nm Tftp is the user interface to the Internet .Tn TFTP (Trivial File Transfer Protocol), which allows users to transfer files to and from a remote machine. The remote .Ar host may be specified on the command line, in which case .Nm uses .Ar host as the default host for future transfers (see the .Cm connect command below). .Sh COMMANDS Once .Nm is running, it issues the prompt .Dq Li tftp> and recognizes the following commands: .Pp .Bl -tag -width verbose -compact .It Cm \&? Ar command-name ... Print help information. .Pp .It Cm ascii Shorthand for "mode ascii" .Pp .It Cm binary Shorthand for "mode binary" .Pp .It Cm connect Ar host-name Op Ar port Set the .Ar host (and optionally .Ar port ) for transfers. Note that the .Tn TFTP protocol, unlike the .Tn FTP protocol, does not maintain connections between transfers; thus, the .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 .Cm connect command; the remote host can be specified as part of the .Cm get or .Cm put commands. .Pp .It Cm get Ar filename .It Cm get Ar remotename localname .It Cm get Ar file1 file2 ... fileN Get a file or set of files from the specified .Ar sources . .Ar Source can be in one of two forms: a filename on the remote host, if the host has already been specified, or a string of the form .Ar hosts:filename to specify both a host and filename at the same time. If the latter form is used, the last hostname specified becomes the default for future transfers. .Pp .It Cm mode Ar transfer-mode Set the mode for transfers; .Ar transfer-mode may be one of .Em ascii or .Em binary . The default is .Em ascii . .Pp .It Cm put Ar file .It Cm put Ar localfile remotefile .It Cm put Ar file1 file2 ... fileN remote-directory Put a file or set of files to the specified remote file or directory. The destination can be in one of two forms: a filename on the remote host, if the host has already been specified, or a string of the form .Ar hosts:filename to specify both a host and filename at the same time. If the latter form is used, the hostname specified becomes the default for future transfers. If the remote-directory form is used, the remote host is assumed to be a .Tn UNIX machine. .Pp .It Cm quit Exit .Nm . An end of file also exits. .Pp .It Cm rexmt Ar retransmission-timeout Set the per-packet retransmission timeout, in seconds. .Pp .It Cm status Show current status. .Pp .It Cm timeout Ar total-transmission-timeout Set the total transmission timeout, in seconds. .Pp .It Cm trace Toggle packet tracing. .Pp .It Cm verbose Toggle verbose mode. .El .Sh BUGS .Pp Because there is no user-login or validation within the .Tn TFTP protocol, the remote site will probably have some sort of file-access restrictions in place. The exact methods are specific to each site and therefore difficult to document here. +.Pp +Files larger than 33488896 octets (65535 blocks) cannot be transferred +without client and server supporting blocksize negotiation (RFC1783). .Sh HISTORY The .Nm command appeared in .Bx 4.3 . Index: head/usr.bin/tftp/tftp.c =================================================================== --- head/usr.bin/tftp/tftp.c (revision 71925) +++ head/usr.bin/tftp/tftp.c (revision 71926) @@ -1,457 +1,459 @@ /* * Copyright (c) 1983, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint #if 0 static char sccsid[] = "@(#)tftp.c 8.1 (Berkeley) 6/6/93"; #endif static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ /* Many bug fixes are from Jim Guyton */ /* * TFTP User Program -- Protocol Machines */ #include #include #include #include #include #include #include #include #include #include #include #include #include "extern.h" #include "tftpsubs.h" extern struct sockaddr_in peeraddr; /* filled in by main */ extern int f; /* the opened socket */ extern int trace; extern int verbose; extern int rexmtval; extern int maxtimeout; #define PKTSIZE SEGSIZE+4 char ackbuf[PKTSIZE]; int timeout; jmp_buf toplevel; jmp_buf timeoutbuf; static void nak __P((int)); static int makerequest __P((int, const char *, struct tftphdr *, const char *)); static void printstats __P((const char *, unsigned long)); static void startclock __P((void)); static void stopclock __P((void)); static void timer __P((int)); static void tpacket __P((const char *, struct tftphdr *, int)); /* * Send the requested file. */ void xmitfile(fd, name, mode) int fd; char *name; char *mode; { register struct tftphdr *ap; /* data and ack packets */ struct tftphdr *r_init(), *dp; register int n; - volatile int block, size, convert; + volatile unsigned short block; + volatile int size, convert; volatile unsigned long amount; struct sockaddr_in from; int fromlen; FILE *file; 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; 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); break; } dp->th_opcode = htons((u_short)DATA); dp->th_block = htons((u_short)block); } timeout = 0; (void) setjmp(timeoutbuf); send_data: if (trace) tpacket("sent", dp, size + 4); n = sendto(f, dp, size + 4, 0, (struct sockaddr *)&peeraddr, sizeof(peeraddr)); if (n != size + 4) { warn("sendto"); goto abort; } 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"); goto abort; } peeraddr.sin_port = from.sin_port; /* added */ 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); 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; } } } 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(fd, name, mode) int fd; char *name; char *mode; { register struct tftphdr *ap; struct tftphdr *dp, *w_init(); register int n; - volatile int block, size, firsttrip; + volatile unsigned short block; + volatile int size, firsttrip; volatile unsigned long amount; struct sockaddr_in from; int fromlen; FILE *file; volatile int convert; /* true if converting crlf -> lf */ startclock(); dp = w_init(); ap = (struct tftphdr *)ackbuf; file = fdopen(fd, "w"); convert = !strcmp(mode, "netascii"); block = 1; firsttrip = 1; amount = 0; 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 *)&peeraddr, sizeof(peeraddr)) != size) { alarm(0); warn("sendto"); 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"); goto abort; } peeraddr.sin_port = from.sin_port; /* added */ 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); 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); 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 *)&peeraddr, sizeof(peeraddr)); write_behind(file, convert); /* flush last buffer */ fclose(file); stopclock(); if (amount > 0) printstats("Received", amount); } static int makerequest(request, name, tp, mode) int request; const char *name; struct tftphdr *tp; const char *mode; { register 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); } struct errmsg { int e_code; 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. */ static void nak(error) int error; { register struct errmsg *pe; register struct tftphdr *tp; int length; char *strerror(); 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, (struct sockaddr *)&peeraddr, sizeof(peeraddr)) != length) warn("nak"); } static void tpacket(s, tp, n) const char *s; struct tftphdr *tp; int n; { static char *opcodes[] = { "#0", "RRQ", "WRQ", "DATA", "ACK", "ERROR" }; register char *cp, *file; u_short op = ntohs(tp->th_opcode); char *index(); 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("\n", file, cp + 1); break; case DATA: printf("\n", ntohs(tp->th_block), n - 4); break; case ACK: printf("\n", ntohs(tp->th_block)); break; case ERROR: printf("\n", ntohs(tp->th_code), tp->th_msg); break; } } struct timeval tstart; struct timeval tstop; static void startclock() { (void)gettimeofday(&tstart, NULL); } static void stopclock() { (void)gettimeofday(&tstop, NULL); } static void printstats(direction, amount) 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 %d bytes in %.1f seconds", direction, amount, delta); if (verbose) printf(" [%.0f bits/sec]", (amount*8.)/delta); putchar('\n'); } static void timer(sig) int sig; { timeout += rexmtval; if (timeout >= maxtimeout) { printf("Transfer timed out.\n"); longjmp(toplevel, -1); } longjmp(timeoutbuf, 1); }