📄 tftpd.c
字号:
} /* Daemonize this process */ { pid_t f = fork(); int nfd; if ( f > 0 ) exit(0); if ( f < 0 ) { syslog(LOG_ERR, "cannot fork: %m"); exit(EX_OSERR); } nfd = open("/dev/null", O_RDWR); if ( nfd >= 3 ) {#ifdef HAVE_DUP2 dup2(nfd, 0); dup2(nfd, 1); dup2(nfd, 2);#else close(0); dup(nfd); close(1); dup(nfd); close(2); dup(nfd);#endif close(nfd); } else if ( nfd < 0 ) { close(0); close(1); close(2); }#ifdef HAVE_SETSID setsid();#endif } } else { /* 0 is our socket descriptor */ close(1); close(2); } /* This means we don't want to wait() for children */#ifdef SA_NOCLDWAIT set_signal(SIGCHLD, SIG_IGN, SA_NOCLDSTOP|SA_NOCLDWAIT);#else set_signal(SIGCHLD, SIG_IGN, SA_NOCLDSTOP);#endif /* Take SIGHUP and use it to set a variable. This is polled synchronously to make sure we don't lose packets as a result. */ set_signal(SIGHUP, handle_sighup, 0); while ( 1 ) { fd_set readset; struct timeval tv_waittime; int rv; if ( caught_sighup ) { caught_sighup = 0; if ( standalone ) {#ifdef HAVE_REGEX if ( rewrite_file ) { freerules(rewrite_rules); rewrite_rules = read_remap_rules(rewrite_file); }#endif } else { /* Return to inetd for respawn */ exit(0); } } FD_ZERO(&readset); FD_SET(fd, &readset); tv_waittime.tv_sec = waittime; tv_waittime.tv_usec = 0; #ifdef __CYGWIN__ /* On Cygwin, select() on a nonblocking socket returns immediately, with a rv of 0! */ set_socket_nonblock(fd, 0);#endif /* Never time out if we're in standalone mode */ rv = select(fd+1, &readset, NULL, NULL, standalone ? NULL : &tv_waittime); if ( rv == -1 && errno == EINTR ) continue; /* Signal caught, reloop */ if ( rv == -1 ) { syslog(LOG_ERR, "select loop: %m"); exit(EX_IOERR); } else if ( rv == 0 ) { exit(0); /* Timeout, return to inetd */ }#ifdef __CYGWIN__ set_socket_nonblock(fd, 1);#endif fromlen = sizeof (from); n = myrecvfrom(fd, buf, sizeof (buf), 0, (struct sockaddr *)&from, &fromlen, &myaddr); if ( n < 0 ) { if ( E_WOULD_BLOCK(errno) || errno == EINTR ) { continue; /* Again, from the top */ } else { syslog(LOG_ERR, "recvfrom: %m"); exit(EX_IOERR); } } if ( from.sin_family != AF_INET ) { syslog(LOG_ERR, "received address was not AF_INET, please check your inetd config"); exit(EX_PROTOCOL); } if ( standalone && myaddr.sin_addr.s_addr == INADDR_ANY ) { /* myrecvfrom() didn't capture the source address; but we might have bound to a specific address, if so we should use it */ memcpy(&myaddr.sin_addr, &bindaddr.sin_addr, sizeof bindaddr.sin_addr); } /* * Now that we have read the request packet from the UDP * socket, we fork and go back to listening to the socket. */ pid = fork(); if (pid < 0) { syslog(LOG_ERR, "fork: %m"); exit(EX_OSERR); /* Return to inetd, just in case */ } else if ( pid == 0 ) break; /* Child exit, parent loop */ } /* Child process: handle the actual request here */ /* Ignore SIGHUP */ set_signal(SIGHUP, SIG_IGN, 0); #ifdef HAVE_TCPWRAPPERS /* Verify if this was a legal request for us. This has to be done before the chroot, while /etc is still accessible. */ request_init(&wrap_request, RQ_DAEMON, __progname, RQ_FILE, fd, RQ_CLIENT_SIN, &from, RQ_SERVER_SIN, &myaddr, 0); sock_methods(&wrap_request); if ( hosts_access(&wrap_request) == 0 ) { if ( deny_severity != -1 ) syslog(deny_severity, "connection refused from %s", inet_ntoa(from.sin_addr)); exit(EX_NOPERM); /* Access denied */ } else if ( allow_severity != -1 ) { syslog(allow_severity, "connect from %s", inet_ntoa(from.sin_addr)); }#endif /* Close file descriptors we don't need */ close(fd); /* Get a socket. This has to be done before the chroot(), since some systems require access to /dev to create a socket. */ peer = socket(AF_INET, SOCK_DGRAM, 0); if (peer < 0) { syslog(LOG_ERR, "socket: %m"); exit(EX_IOERR); } /* Set up the supplementary group access list if possible */ /* /etc/group still need to be accessible at this point */#ifdef HAVE_INITGROUPS setrv = initgroups(user, pw->pw_gid); if ( setrv ) { syslog(LOG_ERR, "cannot set groups for user %s", user); exit(EX_OSERR); }#else#ifdef HAVE_SETGROUPS if ( setgroups(0, NULL) ) { syslog(LOG_ERR, "cannot clear group list"); }#endif#endif /* Chroot and drop privileges */ if (secure) { if (chroot(".")) { syslog(LOG_ERR, "chroot: %m"); exit(EX_OSERR); }#ifdef __CYGWIN__ chdir("/"); /* Cygwin chroot() bug workaround */#endif }#ifdef HAVE_SETREGID setrv = setregid(pw->pw_gid, pw->pw_gid);#else setrv = setegid(pw->pw_gid) || setgid(pw->pw_gid);#endif #ifdef HAVE_SETREUID setrv = setrv || setreuid(pw->pw_uid, pw->pw_uid);#else /* Important: setuid() must come first */ setrv = setrv || setuid(pw->pw_uid) || (geteuid() != pw->pw_uid && seteuid(pw->pw_uid));#endif if ( setrv ) { syslog(LOG_ERR, "cannot drop privileges: %m"); exit(EX_OSERR); } /* Other basic setup */ from.sin_family = AF_INET; /* Process the request... */ myaddr.sin_port = htons(0); /* We want a new local port */ if (bind(peer, (struct sockaddr *)&myaddr, sizeof myaddr) < 0) { syslog(LOG_ERR, "bind: %m"); exit(EX_IOERR); } if (connect(peer, (struct sockaddr *)&from, sizeof from) < 0) { syslog(LOG_ERR, "connect: %m"); exit(EX_IOERR); } tp = (struct tftphdr *)buf; tp_opcode = ntohs(tp->th_opcode); if (tp_opcode == RRQ || tp_opcode == WRQ) tftp(tp, n); exit(0);}char *rewrite_access(char *, int, const char **);int validate_access(char *, int, struct formats *, const char **);void tftp_sendfile(struct formats *, struct tftphdr *, int);void tftp_recvfile(struct formats *, struct tftphdr *, int);struct formats { const char *f_mode; char *(*f_rewrite)(char *, int, const char **); int (*f_validate)(char *, int, struct formats *, const char **); void (*f_send)(struct formats *, struct tftphdr *, int); void (*f_recv)(struct formats *, struct tftphdr *, int); int f_convert;} formats[] = { { "netascii", rewrite_access, validate_access, tftp_sendfile, tftp_recvfile, 1 }, { "octet", rewrite_access, validate_access, tftp_sendfile, tftp_recvfile, 0 }, { NULL, NULL, NULL, NULL, NULL, 0 }};/* * Handle initial connection protocol. */inttftp(struct tftphdr *tp, int size){ char *cp, *end; int argn, ecode; struct formats *pf = NULL; char *origfilename; char *filename, *mode = NULL; const char *errmsgptr; u_short tp_opcode = ntohs(tp->th_opcode); char *val = NULL, *opt = NULL; char *ap = ackbuf + 2; ((struct tftphdr *)ackbuf)->th_opcode = htons(OACK); origfilename = cp = (char *) &(tp->th_stuff); argn = 0; end = (char *)tp + size; while ( cp < end && *cp ) { do { cp++; } while (cp < end && *cp); if ( *cp ) { nak(EBADOP, "Request not null-terminated"); exit(0); } argn++; if (argn == 1) { mode = ++cp; } else if (argn == 2) { for (cp = mode; *cp; cp++) *cp = tolower(*cp); for (pf = formats; pf->f_mode; pf++) { if (!strcmp(pf->f_mode, mode)) break; } if (!pf->f_mode) { nak(EBADOP, "Unknown mode"); exit(0); } if ( !(filename = (*pf->f_rewrite)(origfilename, tp_opcode, &errmsgptr)) ) { nak(EACCESS, errmsgptr); /* File denied by mapping rule */ exit(0); } if ( verbosity >= 1 ) { if ( filename == origfilename || !strcmp(filename, origfilename) ) syslog(LOG_NOTICE, "%s from %s filename %s\n", tp_opcode == WRQ ? "WRQ" : "RRQ", inet_ntoa(from.sin_addr), filename); else syslog(LOG_NOTICE, "%s from %s filename %s remapped to %s\n", tp_opcode == WRQ ? "WRQ" : "RRQ", inet_ntoa(from.sin_addr), origfilename, filename); } ecode = (*pf->f_validate)(filename, tp_opcode, pf, &errmsgptr); if (ecode) { nak(ecode, errmsgptr); exit(0); } opt = ++cp; } else if ( argn & 1 ) { val = ++cp; } else { do_opt(opt, val, &ap); opt = ++cp; } } if (!pf) { nak(EBADOP, "Missing mode"); exit(0); } if ( ap != (ackbuf+2) ) { if ( tp_opcode == WRQ ) (*pf->f_recv)(pf, (struct tftphdr *)ackbuf, ap-ackbuf); else (*pf->f_send)(pf, (struct tftphdr *)ackbuf, ap-ackbuf); } else { if (tp_opcode == WRQ) (*pf->f_recv)(pf, NULL, 0); else (*pf->f_send)(pf, NULL, 0); } exit(0); /* Request completed */}static int blksize_set;/* * Set a non-standard block size (c.f. RFC2348) */intset_blksize(char *val, char **ret){ static char b_ret[6]; unsigned int sz; char *vend; sz = (unsigned int)strtoul(val, &vend, 10); if ( blksize_set || *vend ) return 0; if (sz < 8) return(0); else if (sz > max_blksize) sz = max_blksize; segsize = sz; sprintf(*ret = b_ret, "%u", sz); blksize_set = 1; return(1);}/* * Set a power-of-two block size (nonstandard) */intset_blksize2(char *val, char **ret){ static char b_ret[6]; unsigned int sz; char *vend; sz = (unsigned int)strtoul(val, &vend, 10); if ( blksize_set || *vend ) return 0; if (sz < 8) return(0); else if (sz > max_blksize) sz = max_blksize; /* Convert to a power of two */ if ( sz & (sz-1) ) { unsigned int sz1 = 1; /* Not a power of two - need to convert */ while ( sz >>= 1 ) sz1 <<= 1; sz = sz1; } segsize = sz; sprintf(*ret = b_ret, "%u", sz); blksize_set = 1; return(1);}/* * Return a file size (c.f. RFC2349) * For netascii mode, we don't know the size ahead of time; * so reject the option. */intset_tsize(char *val, char **ret){ static char b_ret[sizeof(uintmax_t)*CHAR_BIT/3+2]; uintmax_t sz; char *vend; sz = strtoumax(val, &vend, 10); if ( !tsize_ok || *vend ) return 0; if (sz == 0) sz = (uintmax_t)tsize; sprintf(*ret = b_ret, "%"PRIuMAX, sz); return(1);}/* * Set the timeout (c.f. RFC2349). This is supposed * to be the (default) retransmission timeout, but being an * integer in seconds it seems a bit limited. */intset_timeout(char *val, char **ret){ static char b_ret[4]; unsigned long to; char *vend; to = strtoul(val, &vend, 10); if ( to < 1 || to > 255 || *vend ) return 0; rexmtval = timeout = to*1000000UL; maxtimeout = rexmtval*TIMEOUT_LIMIT; sprintf(*ret = b_ret, "%lu", to); return(1);}/* Similar, but in microseconds. We allow down to 10 ms. */intset_utimeout(char *val, char **ret){ static char b_ret[4]; unsigned long to; char *vend; to = strtoul(val, &vend, 10);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -