* Copyright (c) 1983, 1993 Regents of the University of California.
* %sccs.include.redist.c%
"@(#) Copyright (c) 1983, 1993 Regents of the University of California.\n\
static char sccsid
[] = "@(#)tftpd.c 5.14 (Berkeley) %G%";
* Trivial file transfer protocol server.
* This version includes many modifications by Jim Guyton
struct sockaddr_in sin
= { AF_INET
};
int maxtimeout
= 5*TIMEOUT
;
#define PKTSIZE SEGSIZE+4
* 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).
static int suppress_naks
;
static char *verifyhost();
register struct tftphdr
*tp
;
openlog("tftpd", LOG_PID
, LOG_FTP
);
while ((ch
= getopt(argc
, argv
, "ln")) != EOF
) {
syslog(LOG_WARNING
, "ignoring unknown option -%c", ch
);
/* Get list of directory prefixes. Skip relative pathnames. */
for (dirp
= dirs
; optind
< argc
&& dirp
< &dirs
[MAXDIRS
];
if (argv
[optind
][0] == '/') {
dirp
->name
= argv
[optind
];
dirp
->len
= strlen(dirp
->name
);
if (ioctl(0, FIONBIO
, &on
) < 0) {
syslog(LOG_ERR
, "ioctl(FIONBIO): %m\n");
n
= recvfrom(0, buf
, sizeof (buf
), 0,
(struct sockaddr
*)&from
, &fromlen
);
syslog(LOG_ERR
, "recvfrom: %m\n");
* 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.
for (i
= 1; i
< 20; 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.
i
= recvfrom(0, buf
, sizeof (buf
), 0,
(struct sockaddr
*)&from
, &j
);
syslog(LOG_ERR
, "fork: %m\n");
from
.sin_family
= AF_INET
;
peer
= socket(AF_INET
, SOCK_DGRAM
, 0);
syslog(LOG_ERR
, "socket: %m\n");
if (bind(peer
, (struct sockaddr
*)&sin
, sizeof (sin
)) < 0) {
syslog(LOG_ERR
, "bind: %m\n");
if (connect(peer
, (struct sockaddr
*)&from
, sizeof(from
)) < 0) {
syslog(LOG_ERR
, "connect: %m\n");
tp
= (struct tftphdr
*)buf
;
tp
->th_opcode
= ntohs(tp
->th_opcode
);
if (tp
->th_opcode
== RRQ
|| tp
->th_opcode
== WRQ
)
int sendfile(), recvfile();
{ "netascii", validate_access
, sendfile
, recvfile
, 1 },
{ "octet", validate_access
, sendfile
, recvfile
, 0 },
{ "mail", validate_user
, sendmail
, recvmail
, 1 },
* Handle initial connection protocol.
register struct formats
*pf
;
filename
= cp
= tp
->th_stuff
;
while (cp
< buf
+ size
) {
for (cp
= mode
; *cp
; cp
++)
for (pf
= formats
; pf
->f_mode
; pf
++)
if (strcmp(pf
->f_mode
, mode
) == 0)
ecode
= (*pf
->f_validate
)(&filename
, tp
->th_opcode
);
syslog(LOG_INFO
, "%s: %s request for %s: %s",
tp
->th_opcode
== WRQ
? "write" : "read",
filename
, errtomsg(ecode
));
* Avoid storms of naks to a RRQ broadcast for a relative
* bootfile pathname from a diskless Sun.
if (suppress_naks
&& *filename
!= '/' && ecode
== ENOTFOUND
)
if (tp
->th_opcode
== WRQ
)
* Validate file access. Since we
* have no uid or gid, for now require
* file to exist and be publicly
* 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.
validate_access(filep
, mode
)
static char pathname
[MAXPATHLEN
];
* Prevent tricksters from getting around the directory restrictions
if (strstr(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
for (dirp
= dirs
; dirp
->name
!= NULL
; dirp
++) {
(!strncmp(filename
, dirp
->name
, dirp
->len
) &&
filename
[dirp
->len
] == '/'))
/* If directory list is empty, allow access to any file */
if (dirp
->name
== NULL
&& dirp
!= dirs
)
if (stat(filename
, &stbuf
) < 0)
return (errno
== ENOENT
? ENOTFOUND
: EACCESS
);
if ((stbuf
.st_mode
& S_IFMT
) != S_IFREG
)
if ((stbuf
.st_mode
& S_IROTH
) == 0)
if ((stbuf
.st_mode
& S_IWOTH
) == 0)
* Relative file name: search the approved locations for it.
* Don't allow write requests or ones that avoid directory
if (mode
!= RRQ
|| !strncmp(filename
, "../", 3))
* 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.
for (dirp
= dirs
; dirp
->name
!= NULL
; dirp
++) {
sprintf(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) {
*filep
= filename
= pathname
;
fd
= open(filename
, mode
== RRQ
? 0 : 1);
file
= fdopen(fd
, (mode
== RRQ
)? "r":"w");
if (timeout
>= maxtimeout
)
* Send the requested file.
struct tftphdr
*dp
, *r_init();
register struct tftphdr
*ap
; /* ack packet */
register int block
= 1, size
, n
;
ap
= (struct tftphdr
*)ackbuf
;
size
= readit(file
, &dp
, pf
->f_convert
);
dp
->th_opcode
= htons((u_short
)DATA
);
dp
->th_block
= htons((u_short
)block
);
(void) setjmp(timeoutbuf
);
if (send(peer
, dp
, size
+ 4, 0) != size
+ 4) {
syslog(LOG_ERR
, "tftpd: write: %m\n");
read_ahead(file
, pf
->f_convert
);
alarm(rexmtval
); /* read the ack */
n
= recv(peer
, ackbuf
, sizeof (ackbuf
), 0);
syslog(LOG_ERR
, "tftpd: read: %m\n");
ap
->th_opcode
= ntohs((u_short
)ap
->th_opcode
);
ap
->th_block
= ntohs((u_short
)ap
->th_block
);
if (ap
->th_opcode
== ERROR
)
if (ap
->th_opcode
== ACK
) {
if (ap
->th_block
== block
) {
/* Re-synchronize with the other side */
if (ap
->th_block
== (block
-1)) {
} while (size
== SEGSIZE
);
struct tftphdr
*dp
, *w_init();
register struct tftphdr
*ap
; /* ack buffer */
register int block
= 0, n
, size
;
ap
= (struct tftphdr
*)ackbuf
;
ap
->th_opcode
= htons((u_short
)ACK
);
ap
->th_block
= htons((u_short
)block
);
(void) setjmp(timeoutbuf
);
if (send(peer
, ackbuf
, 4, 0) != 4) {
syslog(LOG_ERR
, "tftpd: write: %m\n");
write_behind(file
, pf
->f_convert
);
n
= recv(peer
, dp
, PKTSIZE
, 0);
if (n
< 0) { /* really? */
syslog(LOG_ERR
, "tftpd: read: %m\n");
dp
->th_opcode
= ntohs((u_short
)dp
->th_opcode
);
dp
->th_block
= ntohs((u_short
)dp
->th_block
);
if (dp
->th_opcode
== ERROR
)
if (dp
->th_opcode
== DATA
) {
if (dp
->th_block
== block
) {
/* Re-synchronize with the other side */
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);
} 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 */
n
= recv(peer
, buf
, sizeof (buf
), 0); /* normally times out and quits */
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 */
{ 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" },
register struct errmsg
*pe
;
for (pe
= errmsgs
; pe
->e_code
>= 0; pe
++)
sprintf(buf
, "error %d", error
);
* Send a nak packet (error message).
* Error code passed in is one of the
* standard TFTP codes, or a UNIX errno
register struct tftphdr
*tp
;
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
++)
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';
if (send(peer
, buf
, length
, 0) != length
)
syslog(LOG_ERR
, "nak: %m\n");
struct sockaddr_in
*fromp
;
hp
= gethostbyaddr((char *)&fromp
->sin_addr
, sizeof (fromp
->sin_addr
),
return inet_ntoa(fromp
->sin_addr
);