tftpd.c
来自「<B>Digital的Unix操作系统VAX 4.2源码</B>」· C语言 代码 · 共 662 行
C
662 行
#ifndef lintstatic char *sccsid = "@(#)tftpd.c 4.1 ULTRIX 7/2/90";#endif not lint/************************************************************************ * * * Copyright (c) 1988, 1989 by * * Digital Equipment Corporation, Maynard, MA * * All rights reserved. * * * * This software is furnished under a license and may be used and * * copied only in accordance with the terms of such license and * * with the inclusion of the above copyright notice. This * * software or any other copies thereof may not be provided or * * otherwise made available to any other person. No title to and * * ownership of the software is hereby transferred. * * * * This software is derived from software received from the * * University of California, Berkeley, and from Bell * * Laboratories. Use, duplication, or disclosure is subject to * * restrictions under license agreements with University of * * California and with AT&T. * * * * The information in this software is subject to change without * * notice and should not be construed as a commitment by Digital * * Equipment Corporation. * * * * Digital assumes no responsibility for the use or reliability * * of its software on equipment which is not supplied by Digital. * * * ************************************************************************//* * Copyright (c) 1983 Regents of the University of California. * All rights reserved. The Berkeley software License Agreement * specifies the terms and conditions for redistribution. */#ifndef lintchar copyright[] ="Copyright (c) 1983 Regents of the University of California.\n\ All rights reserved.\n";#endif not lint#define SUN_BOOT /* JSD *//* * Trivial file transfer protocol server. * * This version includes many modifications by Jim Guyton <guyton@rand-unix> * merged with Sun features for tftp booting. */#include <sys/types.h>#include <sys/socket.h>#include <sys/ioctl.h>#include <sys/wait.h>#include <sys/stat.h>#include <sys/time.h>#include <netinet/in.h>#include <arpa/tftp.h>#include <sys/dir.h>#include <signal.h>#include <stdio.h>#include <errno.h>#include <ctype.h>#include <netdb.h>#include <setjmp.h>#include <syslog.h>#define TIMEOUT 5extern int errno;struct sockaddr_in sin = { AF_INET };int peer;int rexmtval = TIMEOUT;int maxtimeout = 5*TIMEOUT;#define PKTSIZE SEGSIZE+4char buf[PKTSIZE];char ackbuf[PKTSIZE];struct sockaddr_in from;int fromlen;int debug = 0;FILE *file = NULL;char *newroot = (char *)0;#ifdef SUN_BOOT/* * compatibility suffixes for diskless workstation booting */char *compatexten [] = { ".SUN2", ".SUN3", ".SUN4", ""};/* * Default directory for unqualified names * Used by TFTP boot procedures */char *tftphomedir = "/tftpboot";#endif SUN_BOOTmain(argc, argv)int argc;char **argv;{ register struct tftphdr *tp; register int n; int on = 1;#ifdef LOG_DAEMON openlog("tftpd", LOG_PID, LOG_DAEMON);#else openlog("tftpd", LOG_PID);#endif LOG_DAEMON while ((argc > 1) && (argv[1][0] == '-')) { switch (argv[1][1]) { case 'r': /* -r newroot */ if (argc < 2) { syslog(LOG_WARNING, "-r is missing homedir arg, using default %s\n", tftphomedir); newroot = tftphomedir; } else { newroot = argv[2]; } argv++; argc--; break; case 'd': debug++; break; default: usage(); break; } argv++; argc--; }#ifdef SUN_BOOT /* * warn about any other ignored arguments. */ while (argc > 1) { syslog(LOG_ERR, "ignored argument: %s", argv[1]); argc--; argv++; } if (debug) syslog(LOG_DEBUG, ">>> start: homedir %s", newroot);#endif SUN_BOOT if (ioctl(0, FIONBIO, &on) < 0) { syslog(LOG_ERR, "ioctl(FIONBIO): %m\n"); exit(1); } fromlen = sizeof (from); n = recvfrom(0, buf, sizeof (buf), 0, (caddr_t)&from, &fromlen); if (n < 0) { syslog(LOG_ERR, "recvfrom: %m\n"); 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, (caddr_t)&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); } } from.sin_family = AF_INET; alarm(0); close(0); close(1); if (newroot) { if (chroot(newroot) < 0) { syslog(LOG_ERR, "chroot: %m"); exit(1); } (void)chdir("/"); if (setuid(getuid()) < 0) { syslog(LOG_ERR, "setuid: %m"); exit(1); } } peer = socket(AF_INET, SOCK_DGRAM, 0); if (peer < 0) { syslog(LOG_ERR, "socket: %m"); exit(1); } if (bind(peer, (caddr_t)&sin, sizeof (sin)) < 0) { syslog(LOG_ERR, "bind: %m"); exit(1); } if (connect(peer, (caddr_t)&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);}int validate_access();int sendfile(), recvfile();struct formats { char *f_mode; int (*f_validate)(); int (*f_send)(); int (*f_recv)(); int f_convert;} formats[] = { { "netascii", validate_access, sendfile, recvfile, 1 }, { "octet", validate_access, sendfile, recvfile, 0 },#ifdef notdef { "mail", validate_user, sendmail, recvmail, 1 },#endif { 0 }};/* * Handle initial connection protocol. */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 (ecode) { nak(ecode); exit(1); } if (tp->th_opcode == WRQ) (*pf->f_recv)(pf); else (*pf->f_send)(pf); exit(0);}/* * Validate file access. Since we * have no uid or gid, for now require * file to exist and be publicly * readable/writable. * Note also, full path name must be * given as we have no login directory (unless we have done a chroot) */validate_access(filename, mode) char *filename; int mode;{ struct stat stbuf; int fd, i; int stated = 0; char newfilename [MAXNAMLEN + 1]; if (debug) syslog(LOG_DEBUG, "validate %s access for \"%s\"", mode == RRQ? "read" : "write", filename);#ifdef SUN_BOOT /* * Need to perform access check as someone who will only * be allowed "public" access to the file. There is no * such uid/gid reserved so we set it to -2/-2 ("nobody"). * (Can't use -1/-1 'cause that means "don't change".) */ if (setgid(-2) < 0) { syslog(LOG_ERR, "setgid: %m"); exit(1); } if (setuid(-2) < 0) { syslog(LOG_ERR, "setuid: %m"); exit(1); }#endif SUN_BOOT if (*filename != '/') if (newroot == (char *)0) return (EACCESS);#ifdef SUN_BOOT if (stat(filename, &stbuf) >= 0) stated = 1; if (!stated) { /* check that requested filename is in boot file format */ if (filename [0] == '/') return (errno == ENOENT ? ENOTFOUND : EACCESS); for (i = 0; i < 7; i++) if (!(filename [i] >= '0' && filename [i] <= '9') && !(filename [i] >= 'A' && filename [i] <= 'F')) return (ENOTFOUND); /* * For compatibility with the old format of boot filenames, * check a variety of extentions. */ if (strlen (filename) <= 8) { /* try adding extentions to filename */ for (i = 0; compatexten [i] [0] != '\0'; i++) { strcpy (newfilename, filename); strcat (newfilename, compatexten [i]); if (stat (newfilename, &stbuf) >= 0) { filename = &newfilename [0]; stated = 1; break; } } } else { /* take off the extention */ strncpy (newfilename, filename, 8); newfilename [8] = '\0'; if (stat (newfilename, &stbuf) >= 0) { filename = &newfilename [0]; stated = 1; } } } if (!stated) return (ENOTFOUND);#endif SUN_BOOT if (mode == RRQ) { if ((stbuf.st_mode&(S_IREAD >> 6)) == 0) return (EACCESS); } else { if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0) return (EACCESS); }#ifdef SUN_BOOT if ((stbuf.st_mode & S_IFMT) != S_IFREG) return (EACCESS);#endif SUN_BOOT fd = open(filename, mode == RRQ ? 0 : 1); if (fd < 0) return (errno + 100); file = fdopen(fd, (mode == RRQ)? "r":"w"); if (file == NULL) { (void) close(fd); return errno+100; } if (debug) syslog(LOG_DEBUG, "\"%s\" opened ok", filename); return (0);}int timeout;jmp_buf timeoutbuf;timer(){ timeout += rexmtval; if (timeout >= maxtimeout) exit(1); longjmp(timeoutbuf, 1);}/* * Send the requested file. */sendfile(pf) struct formats *pf;{ struct tftphdr *dp, *r_init(); register struct tftphdr *ap; /* ack packet */ register int block = 1, size, n; signal(SIGALRM, timer); dp = r_init(); ap = (struct tftphdr *)ackbuf; 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) { if ((errno == ENETUNREACH) || (errno == EHOSTUNREACH) || (errno == ECONNREFUSED)) syslog(LOG_WARNING, "send (data): %m"); else syslog(LOG_ERR, "send (data): %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) { if (errno == EINTR) continue; if ((errno == ENETUNREACH) || (errno == EHOSTUNREACH) || (errno == ECONNREFUSED)) syslog(LOG_WARNING, "recv (ack): %m"); else syslog(LOG_ERR, "recv (ack): %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);}justquit(){ exit(0);}/* * Receive a file. */recvfile(pf) struct formats *pf;{ struct tftphdr *dp, *w_init(); register struct tftphdr *ap; /* ack buffer */ register int block = 0, n, size; signal(SIGALRM, timer); dp = w_init(); ap = (struct tftphdr *)ackbuf; 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, "send (ack): %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, "recv (data): %m\n"); 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: (void) fclose(file); /* close data file */ 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 }};/* * Send a nak packet (error message). * Error code passed in is one of the * standard TFTP codes, or a UNIX errno * offset by 100. */nak(error) int error;{ register struct tftphdr *tp; int length; register struct errmsg *pe; extern char *sys_errlist[]; 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 = sys_errlist[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) { if ((errno == ENETUNREACH) || (errno == EHOSTUNREACH) || (errno == ECONNREFUSED)) syslog(LOG_WARNING, "send (nak): %m"); else syslog(LOG_ERR, "send (nak): %m"); }}usage(){ (void) fprintf (stderr, "usage: tftpd [-d] [-r] [ home-directory ]\n"); exit (1);}
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?