diff --git a/libexec/tftpd/Makefile b/libexec/tftpd/Makefile --- a/libexec/tftpd/Makefile +++ b/libexec/tftpd/Makefile @@ -10,9 +10,17 @@ LIBADD= wrap .endif +.if ${MK_CASPER} != "no" +LIBADD+= casper +LIBADD+= cap_dns +LIBADD+= cap_net +LIBADD+= cap_fileargs +CFLAGS+=-DWITH_CASPER +.endif + CWARNFLAGS.gcc+= -Wno-format-nonliteral HAS_TESTS= SUBDIR.${MK_TESTS}+= tests - +LIBADD+= ipsec .include diff --git a/libexec/tftpd/tftp-io.c b/libexec/tftpd/tftp-io.c --- a/libexec/tftpd/tftp-io.c +++ b/libexec/tftpd/tftp-io.c @@ -106,8 +106,7 @@ for (i = 0; i < 12 ; i++) { DROPPACKETn("send_packet", 0); - if (sendto(peer, pkt, size, 0, (struct sockaddr *)&peer_sock, - peer_sock.ss_len) == size) { + if (send(peer, pkt, size, 0) == size) { if (i) tftp_log(LOG_ERR, "%s block %d, attempt %d successful", @@ -162,8 +161,7 @@ if (debug & DEBUG_PACKETS) tftp_log(LOG_DEBUG, "Sending ERROR %d: %s", error, tp->th_msg); - if (sendto(peer, buf, length, 0, - (struct sockaddr *)&peer_sock, peer_sock.ss_len) != length) + if (send(peer, buf, length, 0) != length) tftp_log(LOG_ERR, "send_error: %s", strerror(errno)); } @@ -206,8 +204,7 @@ if (options_rfc_enabled) size += make_options(peer, bp, sizeof(buf) - size); - n = sendto(peer, buf, size, 0, - (struct sockaddr *)&peer_sock, peer_sock.ss_len); + n = send(peer, buf, size, 0); if (n != size) { tftp_log(LOG_ERR, "send_wrq: %s", strerror(errno)); return (1); @@ -256,8 +253,7 @@ size += make_options(peer, bp, sizeof(buf) - size); } - n = sendto(peer, buf, size, 0, - (struct sockaddr *)&peer_sock, peer_sock.ss_len); + n = send(peer, buf, size, 0); if (n != size) { tftp_log(LOG_ERR, "send_rrq: %d %s", n, strerror(errno)); return (1); @@ -303,8 +299,7 @@ } size = bp - buf; - if (sendto(peer, buf, size, 0, - (struct sockaddr *)&peer_sock, peer_sock.ss_len) != size) { + if (send(peer, buf, size, 0) != size) { tftp_log(LOG_INFO, "send_oack: %s", strerror(errno)); return (1); } @@ -332,8 +327,7 @@ tp->th_block = htons((u_short)block); size = 4; - if (sendto(fp, buf, size, 0, - (struct sockaddr *)&peer_sock, peer_sock.ss_len) != size) { + if (send(fp, buf, size, 0) != size) { tftp_log(LOG_INFO, "send_ack: %s", strerror(errno)); return (1); } @@ -381,7 +375,6 @@ struct tftphdr *pkt; struct sockaddr_storage from_local; struct sockaddr_storage *pfrom; - socklen_t fromlen; int n; if (debug & DEBUG_PACKETS) @@ -399,8 +392,7 @@ } pfrom = (from == NULL) ? &from_local : from; - fromlen = sizeof(*pfrom); - n = recvfrom(peer, data, size, 0, (struct sockaddr *)pfrom, &fromlen); + n = recv(peer, data, size, 0); DROPPACKETn("receive_packet", RP_TIMEOUT); diff --git a/libexec/tftpd/tftpd.c b/libexec/tftpd/tftpd.c --- a/libexec/tftpd/tftpd.c +++ b/libexec/tftpd/tftpd.c @@ -36,8 +36,10 @@ * . */ +#include #include #include +#include #include #include #include @@ -45,8 +47,15 @@ #include #include +#include +#include +#include +#include +#include + #include #include +#include #include #include #include @@ -64,10 +73,9 @@ #include "tftp-transfer.h" #include "tftp-options.h" -#ifdef LIBWRAP +#ifdef LIBWRAP #include #endif - static void tftp_wrq(int peer, char *, size_t); static void tftp_rrq(int peer, char *, size_t); @@ -123,11 +131,20 @@ char recvbuffer[MAXPKTSIZE]; int allow_ro = 1, allow_wo = 1, on = 1; pid_t pid; - + cap_channel_t *casper; + cap_channel_t *casnet; + + tftp_openlog("tftpd", LOG_PID | LOG_NDELAY, LOG_FTP); tzset(); /* syslog in localtime */ acting_as_client = 0; + casper = cap_init(); + casnet = cap_service_open(casper, "system.net"); + if (casper == NULL) + tftp_log(LOG_ERR, "Unable to contact Casper"); + cap_close(casper); + caph_cache_catpages(); + caph_cache_tzdata(); - tftp_openlog("tftpd", LOG_PID | LOG_NDELAY, LOG_FTP); while ((ch = getopt(argc, argv, "cCd::F:lnoOp:s:Su:U:wW")) != -1) { switch (ch) { case 'c': @@ -196,6 +213,7 @@ for (dirp = dirs; optind < argc && dirp < &dirs[MAXDIRS]; optind++) { if (argv[optind][0] == '/') { + tftp_log(LOG_ERR,"%s",argv[optind]); dirp->name = argv[optind]; dirp->len = strlen(dirp->name); dirp++; @@ -221,19 +239,18 @@ /* 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); + (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, + cap_getnameinfo(casnet, (struct sockaddr *)&peer_sock, peer_sock.ss_len, peername, sizeof(peername), NULL, 0, NI_NUMERICHOST); if ((size_t)n < 4 /* tftphdr */) { tftp_log(LOG_ERR, "Rejecting %zd-byte request from %s", n, peername); exit(1); } - /* * Now that we have read the message out of the UDP * socket, we fork and exit. Thus, inetd will go back @@ -313,6 +330,7 @@ * 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; @@ -324,7 +342,7 @@ statret = -1; memcpy(&ss, &peer_sock, peer_sock.ss_len); unmappedaddr((struct sockaddr_in6 *)&ss); - getnameinfo((struct sockaddr *)&ss, ss.ss_len, + cap_getnameinfo(casnet, (struct sockaddr *)&ss, ss.ss_len, hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST); asprintf(&tempchroot, "%s/%s", chroot_dir, hbuf); @@ -361,7 +379,6 @@ } if (check_woth == -1) check_woth = 1; - len = sizeof(me_sock); if (getsockname(0, (struct sockaddr *)&me_sock, &len) == 0) { switch (me_sock.ss_family) { @@ -388,10 +405,15 @@ tftp_log(LOG_ERR, "socket: %s", strerror(errno)); exit(1); } - if (bind(peer, (struct sockaddr *)&me_sock, me_sock.ss_len) < 0) { + + if (cap_bind(casnet, peer, (struct sockaddr *)&me_sock, me_sock.ss_len) < 0) { tftp_log(LOG_ERR, "bind: %s", strerror(errno)); exit(1); } + if (cap_connect(casnet, peer, (struct sockaddr *)&peer_sock, peer_sock.ss_len) < 0) { + tftp_log(LOG_ERR, "connect:%s",strerror(errno)); + exit(1); + } tp = (struct tftphdr *)recvbuffer; tp->th_opcode = ntohs(tp->th_opcode); @@ -403,6 +425,7 @@ "%s read access denied", peername); exit(1); } + tftp_log(LOG_ERR,"out"); } else if (tp->th_opcode == WRQ) { if (allow_wo) tftp_wrq(peer, tp->th_stuff, (size_t)n - 1); @@ -521,6 +544,7 @@ else send_ack(peer, 0); } + if (logging) { tftp_log(LOG_INFO, "%s: write request for %s: %s", peername, filename, errtomsg(ecode)); @@ -551,8 +575,8 @@ strlcpy(fnbuf, filename, sizeof(fnbuf)); reduce_path(fnbuf); filename = fnbuf; - - if (size > 0) { + + if(size > 0) { if (options_rfc_enabled) has_options = !parse_options(peer, cp, size); else @@ -609,7 +633,7 @@ * execessive anyway. */ static int -find_next_name(char *filename, int *fd) +find_next_name(char *filename, int *fd, fileargs_t *fa) { /* * GCC "knows" that we might write all of yyyymmdd plus the static @@ -658,7 +682,7 @@ */ if (ret < 0 || (size_t)ret >= sizeof(newname)) __unreachable(); - *fd = open(newname, O_WRONLY | O_CREAT | O_EXCL, 0666); + *fd = fileargs_open(fa, newname); if (*fd > 0) return 0; } @@ -680,27 +704,58 @@ int validate_access(int peer, char **filep, int mode) { - static char pathname[MAXPATHLEN]; - struct stat sb; + struct stat stbuf; + int fd; + int error; struct dirlist *dirp; + static char pathname[MAXPATHLEN]; char *filename = *filep; - int err, fd; + char *fnlist[1]; + fileargs_t *fa; + cap_rights_t rights; /* * Prevent tricksters from getting around the directory restrictions */ - if (strncmp(filename, "../", 3) == 0 || - strstr(filename, "/../") != NULL) - return (EACCESS); + if(strncmp(filename,"../",3) == 0 || + strstr(filename,"/../") != NULL) + return (EACCESS); + + if(*filename != '/') { + for (dirp = dirs; dirp->name != NULL; dirp++) { + snprintf(pathname, sizeof(pathname), "%s/%s", + dirp->name, filename); + } + fnlist[0] = pathname; + } else + fnlist[0] = filename; + if (mode == RRQ) { + fa = fileargs_init(1, fnlist, O_RDWR, 0, + cap_rights_init(&rights, CAP_FCNTL, CAP_READ + , CAP_SEEK, CAP_FSTAT, CAP_CONNECT, CAP_WRITE), FA_OPEN | FA_LSTAT); + if (fa == NULL) + tftp_log(LOG_DEBUG, "unable to open rrq system.fileargs service"); + } else { + fa = fileargs_init(1, fnlist, O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | + S_IWGRP | S_IROTH | S_IWOTH, + cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_CREATE, + CAP_FCNTL ,CAP_SEEK, CAP_FTRUNCATE, CAP_FSTAT, CAP_CONNECT), FA_OPEN); + if (fa == NULL) + tftp_log(LOG_DEBUG, "unable to open wrq system.fileargs service"); + } + + if (caph_enter_casper() < 0) + tftp_log(LOG_ERR, "Unable to enter capability mode"); if (*filename == '/') { /* - * Absolute file name: allow the request if it's in one of the - * approved locations. + * 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) - /* Only "/" can have len 1 */ break; if (strncmp(filename, dirp->name, dirp->len) == 0 && filename[dirp->len] == '/') @@ -709,42 +764,78 @@ /* If directory list is empty, allow access to any file */ if (dirp->name == NULL && dirp != dirs) return (EACCESS); - if (stat(filename, &sb) != 0) + + if (mode == RRQ) { + fd = fileargs_open(fa, filename); + } else if (create_new) { + if (increase_name) { + error = find_next_name(pathname, &fd, fa); + if (error > 0) + return (error + 100); + } else { + fd = fileargs_open(fa, filename); + } + } else { + fd = fileargs_open(fa, filename); + } + + if(dirp->name == NULL && dirp != dirs) + return (EACCESS); + if (fstat(fd, &stbuf) < 0) return (errno == ENOENT ? ENOTFOUND : EACCESS); - if (!S_ISREG(sb.st_mode)) + if ((stbuf.st_mode & S_IFMT) != S_IFREG) return (ENOTFOUND); if (mode == RRQ) { - if ((sb.st_mode & S_IROTH) == 0) + if ((stbuf.st_mode & S_IROTH) == 0) return (EACCESS); } else { - if (check_woth && (sb.st_mode & S_IWOTH) == 0) + if (check_woth && ((stbuf.st_mode & S_IWOTH) == 0)) return (EACCESS); } } else { + int err; + /* * Relative file name: search the approved locations for it. * 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, &sb) != 0) + dirp->name, filename); + if (mode == RRQ) { + fd = fileargs_open(fa, pathname); + } else if (create_new) { + if (increase_name) { + err = find_next_name(pathname, &fd, fa); + if (err > 0) + return (err + 100); + } else { + fd = fileargs_open(fa, pathname); + } + } else { + fd = fileargs_open(fa, pathname); + } + if (fstat(fd, &stbuf) != 0) { + tftp_log(LOG_ERR, "fstat failed:%d",errno); continue; - if (!S_ISREG(sb.st_mode)) + } + if (!S_ISREG(stbuf.st_mode)) continue; err = EACCESS; if (mode == RRQ) { - if ((sb.st_mode & S_IROTH) == 0) + if ((stbuf.st_mode & S_IROTH) == 0) continue; } else { - if (check_woth && (sb.st_mode & S_IWOTH) == 0) + if (check_woth && (stbuf.st_mode & S_IWOTH) == 0) continue; } break; } + if (dirp->name != NULL) *filep = filename = pathname; else if (mode == RRQ) @@ -757,31 +848,18 @@ * This option is handled here because it (might) require(s) the * size of the file. */ - option_tsize(peer, NULL, mode, &sb); - - if (mode == RRQ) { - fd = open(filename, O_RDONLY); - } else if (create_new) { - if (increase_name) { - err = find_next_name(filename, &fd); - if (err > 0) - return (err + 100); - } else { - fd = open(filename, - O_WRONLY | O_TRUNC | O_CREAT, - S_IRUSR | S_IWUSR | S_IRGRP | - S_IWGRP | S_IROTH | S_IWOTH ); - } - } else { - fd = open(filename, O_WRONLY | O_TRUNC); - } - if (fd < 0) + option_tsize(peer, NULL, mode, &stbuf); + if (fd < 0){ + tftp_log(LOG_ERR,"fd"); return (errno + 100); - file = fdopen(fd, mode == RRQ ? "r" : "w"); + } + file = fdopen(fd, (mode == RRQ)? "r":"w"); if (file == NULL) { + tftp_log(LOG_ERR,"null file"); close(fd); return (errno + 100); } + fileargs_free(fa); return (0); }