int main(int argc, char *argv[]) { struct tftphdr *tp; int peer; socklen_t peerlen, len; ssize_t n; int ch; char *chroot_dir = NULL; struct passwd *nobody; const char *chuser = "******"; char recvbuffer[MAXPKTSIZE]; int allow_ro = 1, allow_wo = 1; tzset(); /* syslog in localtime */ acting_as_client = 0; tftp_openlog("tftpd", LOG_PID | LOG_NDELAY, LOG_FTP); while ((ch = getopt(argc, argv, "cCd:F:lnoOp:s:u:U:wW")) != -1) { switch (ch) { case 'c': ipchroot = 1; break; case 'C': ipchroot = 2; break; case 'd': if (atoi(optarg) != 0) debug += atoi(optarg); else debug |= debug_finds(optarg); break; case 'F': newfile_format = optarg; break; case 'l': logging = 1; break; case 'n': suppress_naks = 1; break; case 'o': options_rfc_enabled = 0; break; case 'O': options_extra_enabled = 0; break; case 'p': packetdroppercentage = atoi(optarg); tftp_log(LOG_INFO, "Randomly dropping %d out of 100 packets", packetdroppercentage); break; case 's': chroot_dir = optarg; break; case 'u': chuser = optarg; break; case 'U': mask = strtol(optarg, NULL, 0); break; case 'w': create_new = 1; break; case 'W': create_new = 1; increase_name = 1; break; default: tftp_log(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 > 0 && chroot_dir == NULL) { tftp_log(LOG_ERR, "-c requires -s"); exit(1); } umask(mask); { int on = 1; if (ioctl(0, FIONBIO, &on) < 0) { tftp_log(LOG_ERR, "ioctl(FIONBIO): %s", strerror(errno)); exit(1); } } /* Find out who we are talking to and what we are going to do */ peerlen = sizeof(peer_sock); n = recvfrom(0, recvbuffer, MAXPKTSIZE, 0, (struct sockaddr *)&peer_sock, &peerlen); if (n < 0) { tftp_log(LOG_ERR, "recvfrom: %s", strerror(errno)); exit(1); } getnameinfo((struct sockaddr *)&peer_sock, peer_sock.ss_len, peername, sizeof(peername), NULL, 0, NI_NUMERICHOST); /* * 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 i, pid; 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. */ peerlen = sizeof peer_sock; i = recvfrom(0, recvbuffer, MAXPKTSIZE, 0, (struct sockaddr *)&peer_sock, &peerlen); if (i > 0) { n = i; } } else { break; } } if (pid < 0) { tftp_log(LOG_ERR, "fork: %s", strerror(errno)); exit(1); } else if (pid != 0) { exit(0); } } /* * See if the client is allowed to talk to me. * (This needs to be done before the chroot()) */ { struct request_info req; request_init(&req, RQ_CLIENT_ADDR, peername, 0); request_set(&req, RQ_DAEMON, "tftpd", 0); if (hosts_access(&req) == 0) { if (debug&DEBUG_ACCESS) tftp_log(LOG_WARNING, "Access denied by 'tftpd' entry " "in /etc/hosts.allow"); /* * Full access might be disabled, but maybe the * client is allowed to do read-only access. */ request_set(&req, RQ_DAEMON, "tftpd-ro", 0); allow_ro = hosts_access(&req); request_set(&req, RQ_DAEMON, "tftpd-wo", 0); allow_wo = hosts_access(&req); if (allow_ro == 0 && allow_wo == 0) { tftp_log(LOG_WARNING, "Unauthorized access from %s", peername); exit(1); } if (debug&DEBUG_ACCESS) { if (allow_ro) tftp_log(LOG_WARNING, "But allowed readonly access " "via 'tftpd-ro' entry"); if (allow_wo) tftp_log(LOG_WARNING, "But allowed writeonly access " "via 'tftpd-wo' entry"); } } else if (debug&DEBUG_ACCESS) tftp_log(LOG_WARNING, "Full access allowed" "in /etc/hosts.allow"); } /* * 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 > 0) { char *tempchroot; struct stat sb; int statret; struct sockaddr_storage ss; char hbuf[NI_MAXHOST]; statret = -1; memcpy(&ss, &peer_sock, peer_sock.ss_len); unmappedaddr((struct sockaddr_in6 *)&ss); getnameinfo((struct sockaddr *)&ss, ss.ss_len, hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST); asprintf(&tempchroot, "%s/%s", chroot_dir, hbuf); if (ipchroot == 2) statret = stat(tempchroot, &sb); if (ipchroot == 1 || (statret == 0 && (sb.st_mode & S_IFDIR))) chroot_dir = tempchroot; } /* Must get this before chroot because /etc might go away */ if ((nobody = getpwnam(chuser)) == NULL) { tftp_log(LOG_ERR, "%s: no such user", chuser); exit(1); } if (chroot(chroot_dir)) { tftp_log(LOG_ERR, "chroot: %s: %s", chroot_dir, strerror(errno)); exit(1); } chdir("/"); setgroups(1, &nobody->pw_gid); if (setuid(nobody->pw_uid) != 0) { tftp_log(LOG_ERR, "setuid failed"); exit(1); } } len = sizeof(me_sock); if (getsockname(0, (struct sockaddr *)&me_sock, &len) == 0) { switch (me_sock.ss_family) { case AF_INET: ((struct sockaddr_in *)&me_sock)->sin_port = 0; break; case AF_INET6: ((struct sockaddr_in6 *)&me_sock)->sin6_port = 0; break; default: /* unsupported */ break; } } else { memset(&me_sock, 0, sizeof(me_sock)); me_sock.ss_family = peer_sock.ss_family; me_sock.ss_len = peer_sock.ss_len; } close(0); close(1); peer = socket(peer_sock.ss_family, SOCK_DGRAM, 0); if (peer < 0) { tftp_log(LOG_ERR, "socket: %s", strerror(errno)); exit(1); } if (bind(peer, (struct sockaddr *)&me_sock, me_sock.ss_len) < 0) { tftp_log(LOG_ERR, "bind: %s", strerror(errno)); exit(1); } tp = (struct tftphdr *)recvbuffer; tp->th_opcode = ntohs(tp->th_opcode); if (tp->th_opcode == RRQ) { if (allow_ro) tftp_rrq(peer, tp->th_stuff, n - 1); else { tftp_log(LOG_WARNING, "%s read access denied", peername); exit(1); } } if (tp->th_opcode == WRQ) { if (allow_wo) tftp_wrq(peer, tp->th_stuff, n - 1); else { tftp_log(LOG_WARNING, "%s write access denied", peername); exit(1); } } exit(1); }
/* * Transfer file */ static fetchIO * ftp_transfer(conn_t *conn, const char *oper, const char *file, const char *op_arg, int mode, off_t offset, const char *flags) { union anonymous { struct sockaddr_storage ss; struct sockaddr sa; struct sockaddr_in6 sin6; struct sockaddr_in sin4; } u; const char *bindaddr; const char *filename; int filenamelen, type; int pasv, verbose; int e, sd = -1; socklen_t l; char *s; fetchIO *df; /* check flags */ pasv = !CHECK_FLAG('a'); verbose = CHECK_FLAG('v'); /* passive mode */ if (!pasv) pasv = ((s = getenv("FTP_PASSIVE_MODE")) != NULL && strncasecmp(s, "no", 2) != 0); /* isolate filename */ filename = ftp_filename(file, &filenamelen, &type, op_arg != NULL); /* set transfer mode and data type */ if ((e = ftp_mode_type(conn, 0, type)) != FTP_OK) goto ouch; /* find our own address, bind, and listen */ l = sizeof(u.ss); if (getsockname(conn->sd, &u.sa, &l) == -1) goto sysouch; if (u.ss.ss_family == AF_INET6) unmappedaddr(&u.sin6, &l); retry_mode: /* open data socket */ if ((sd = socket(u.ss.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) { fetch_syserr(); return (NULL); } if (pasv) { unsigned char addr[64]; char *ln, *p; unsigned int i; int port; /* send PASV command */ if (verbose) fetch_info("setting passive mode"); switch (u.ss.ss_family) { case AF_INET: if ((e = ftp_cmd(conn, "PASV\r\n")) != FTP_PASSIVE_MODE) goto ouch; break; case AF_INET6: if ((e = ftp_cmd(conn, "EPSV\r\n")) != FTP_EPASSIVE_MODE) { if (e == -1) goto ouch; if ((e = ftp_cmd(conn, "LPSV\r\n")) != FTP_LPASSIVE_MODE) goto ouch; } break; default: e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ goto ouch; } /* * Find address and port number. The reply to the PASV command * is IMHO the one and only weak point in the FTP protocol. */ ln = conn->buf; switch (e) { case FTP_PASSIVE_MODE: case FTP_LPASSIVE_MODE: for (p = ln + 3; *p && !isdigit((unsigned char)*p); p++) /* nothing */ ; if (!*p) { e = FTP_PROTOCOL_ERROR; goto ouch; } l = (e == FTP_PASSIVE_MODE ? 6 : 21); for (i = 0; *p && i < l; i++, p++) addr[i] = strtol(p, &p, 10); if (i < l) { e = FTP_PROTOCOL_ERROR; goto ouch; } break; case FTP_EPASSIVE_MODE: for (p = ln + 3; *p && *p != '('; p++) /* nothing */ ; if (!*p) { e = FTP_PROTOCOL_ERROR; goto ouch; } ++p; if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2], &port, &addr[3]) != 5 || addr[0] != addr[1] || addr[0] != addr[2] || addr[0] != addr[3]) { e = FTP_PROTOCOL_ERROR; goto ouch; } break; case FTP_SYNTAX_ERROR: if (verbose) fetch_info("passive mode failed"); /* Close socket and retry with passive mode. */ pasv = 0; close(sd); sd = -1; goto retry_mode; } /* seek to required offset */ if (offset) if (ftp_cmd(conn, "REST %lu\r\n", (unsigned long)offset) != FTP_FILE_OK) goto sysouch; /* construct sockaddr for data socket */ l = sizeof(u.ss); if (getpeername(conn->sd, &u.sa, &l) == -1) goto sysouch; if (u.ss.ss_family == AF_INET6) unmappedaddr(&u.sin6, &l); switch (u.ss.ss_family) { case AF_INET6: if (e == FTP_EPASSIVE_MODE) u.sin6.sin6_port = htons(port); else { memcpy(&u.sin6.sin6_addr, addr + 2, 16); memcpy(&u.sin6.sin6_port, addr + 19, 2); } break; case AF_INET: if (e == FTP_EPASSIVE_MODE) u.sin4.sin_port = htons(port); else { memcpy(&u.sin4.sin_addr, addr, 4); memcpy(&u.sin4.sin_port, addr + 4, 2); } break; default: e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ break; } /* connect to data port */ if (verbose) fetch_info("opening data connection"); bindaddr = getenv("FETCH_BIND_ADDRESS"); if (bindaddr != NULL && *bindaddr != '\0' && fetch_bind(sd, u.ss.ss_family, bindaddr) != 0) goto sysouch; if (connect(sd, &u.sa, l) == -1) goto sysouch; /* make the server initiate the transfer */ if (verbose) fetch_info("initiating transfer"); if (op_arg) e = ftp_cmd(conn, "%s%s%s\r\n", oper, *op_arg ? " " : "", op_arg); else e = ftp_cmd(conn, "%s %.*s\r\n", oper, (int)filenamelen, filename); if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION) goto ouch; } else { uint32_t a; uint16_t p; #if defined(IPV6_PORTRANGE) || defined(IP_PORTRANGE) int arg; int low = CHECK_FLAG('l'); #endif int d; char hname[INET6_ADDRSTRLEN]; switch (u.ss.ss_family) { case AF_INET6: u.sin6.sin6_port = 0; #ifdef IPV6_PORTRANGE arg = low ? IPV6_PORTRANGE_DEFAULT : IPV6_PORTRANGE_HIGH; if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE, (char *)&arg, sizeof(arg)) == -1) goto sysouch; #endif break; case AF_INET: u.sin4.sin_port = 0; #ifdef IP_PORTRANGE arg = low ? IP_PORTRANGE_DEFAULT : IP_PORTRANGE_HIGH; if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE, (char *)&arg, sizeof(arg)) == -1) goto sysouch; #endif break; } if (verbose) fetch_info("binding data socket"); if (bind(sd, &u.sa, l) == -1) goto sysouch; if (listen(sd, 1) == -1) goto sysouch; /* find what port we're on and tell the server */ if (getsockname(sd, &u.sa, &l) == -1) goto sysouch; switch (u.ss.ss_family) { case AF_INET: a = ntohl(u.sin4.sin_addr.s_addr); p = ntohs(u.sin4.sin_port); e = ftp_cmd(conn, "PORT %d,%d,%d,%d,%d,%d\r\n", (a >> 24) & 0xff, (a >> 16) & 0xff, (a >> 8) & 0xff, a & 0xff, (p >> 8) & 0xff, p & 0xff); break; case AF_INET6: e = -1; u.sin6.sin6_scope_id = 0; if (getnameinfo(&u.sa, l, hname, sizeof(hname), NULL, 0, NI_NUMERICHOST) == 0) { e = ftp_cmd(conn, "EPRT |%d|%s|%d|\r\n", 2, hname, htons(u.sin6.sin6_port)); if (e == -1) goto ouch; } if (e != FTP_OK) { unsigned char *ap = (void *)&u.sin6.sin6_addr.s6_addr; uint16_t port = ntohs(u.sin6.sin6_port); e = ftp_cmd(conn, "LPRT %d,%d,%u,%u,%u,%u,%u,%u,%u,%u," "%u,%u,%u,%u,%u,%u,%u,%u,%d,%d,%d\r\n", 6, 16, (unsigned)ap[0], (unsigned)ap[1], (unsigned)ap[2], (unsigned)ap[3], (unsigned)ap[4], (unsigned)ap[5], (unsigned)ap[6], (unsigned)ap[7], (unsigned)ap[8], (unsigned)ap[9], (unsigned)ap[10], (unsigned)ap[11], (unsigned)ap[12], (unsigned)ap[13], (unsigned)ap[14], (unsigned)ap[15], 2, port >> 8, port & 0xff); } break; default: e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ goto ouch; } if (e != FTP_OK) goto ouch; /* seek to required offset */ if (offset) if (ftp_cmd(conn, "REST %llu\r\n", (unsigned long long)offset) != FTP_FILE_OK) goto sysouch; /* make the server initiate the transfer */ if (verbose) fetch_info("initiating transfer"); if (op_arg) e = ftp_cmd(conn, "%s%s%s\r\n", oper, *op_arg ? " " : "", op_arg); else e = ftp_cmd(conn, "%s %.*s\r\n", oper, (int)filenamelen, filename); if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION) goto ouch; /* accept the incoming connection and go to town */ if ((d = accept(sd, NULL, NULL)) == -1) goto sysouch; close(sd); sd = d; }
int main(int argc, char *argv[]) { struct tftphdr *tp; int n; int ch, on; struct sockaddr_storage me; int len; char *chroot_dir = NULL; struct passwd *nobody; const char *chuser = "******"; 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; struct sockaddr_storage ss; char hbuf[NI_MAXHOST]; memcpy(&ss, &from, from.ss_len); unmappedaddr((struct sockaddr_in6 *)&ss); getnameinfo((struct sockaddr *)&ss, ss.ss_len, hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST | NI_WITHSCOPEID); asprintf(&tempchroot, "%s/%s", chroot_dir, hbuf); 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); setgroups(1, &nobody->pw_gid); } len = sizeof(me); if (getsockname(0, (struct sockaddr *)&me, &len) == 0) { switch (me.ss_family) { case AF_INET: ((struct sockaddr_in *)&me)->sin_port = 0; break; case AF_INET6: ((struct sockaddr_in6 *)&me)->sin6_port = 0; break; default: /* unsupported */ break; } } else { memset(&me, 0, sizeof(me)); me.ss_family = from.ss_family; me.ss_len = from.ss_len; } alarm(0); close(0); close(1); peer = socket(from.ss_family, SOCK_DGRAM, 0); if (peer < 0) { syslog(LOG_ERR, "socket: %m"); exit(1); } if (bind(peer, (struct sockaddr *)&me, me.ss_len) < 0) { syslog(LOG_ERR, "bind: %m"); exit(1); } if (connect(peer, (struct sockaddr *)&from, from.ss_len) < 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); }