18a16b7a1SPedro F. Giffuni /*- 28a16b7a1SPedro F. Giffuni * SPDX-License-Identifier: BSD-3-Clause 38a16b7a1SPedro F. Giffuni * 4ea022d16SRodney W. Grimes * Copyright (c) 1983, 1993 5ea022d16SRodney W. Grimes * The Regents of the University of California. All rights reserved. 6ea022d16SRodney W. Grimes * 7ea022d16SRodney W. Grimes * Redistribution and use in source and binary forms, with or without 8ea022d16SRodney W. Grimes * modification, are permitted provided that the following conditions 9ea022d16SRodney W. Grimes * are met: 10ea022d16SRodney W. Grimes * 1. Redistributions of source code must retain the above copyright 11ea022d16SRodney W. Grimes * notice, this list of conditions and the following disclaimer. 12ea022d16SRodney W. Grimes * 2. Redistributions in binary form must reproduce the above copyright 13ea022d16SRodney W. Grimes * notice, this list of conditions and the following disclaimer in the 14ea022d16SRodney W. Grimes * documentation and/or other materials provided with the distribution. 155efaea4cSChristian Brueffer * 3. Neither the name of the University nor the names of its contributors 16ea022d16SRodney W. Grimes * may be used to endorse or promote products derived from this software 17ea022d16SRodney W. Grimes * without specific prior written permission. 18ea022d16SRodney W. Grimes * 19ea022d16SRodney W. Grimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20ea022d16SRodney W. Grimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21ea022d16SRodney W. Grimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22ea022d16SRodney W. Grimes * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23ea022d16SRodney W. Grimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24ea022d16SRodney W. Grimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25ea022d16SRodney W. Grimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26ea022d16SRodney W. Grimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27ea022d16SRodney W. Grimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28ea022d16SRodney W. Grimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29ea022d16SRodney W. Grimes * SUCH DAMAGE. 30ea022d16SRodney W. Grimes */ 31ea022d16SRodney W. Grimes 32ea022d16SRodney W. Grimes /* 33ea022d16SRodney W. Grimes * Trivial file transfer protocol server. 34ea022d16SRodney W. Grimes * 35ea022d16SRodney W. Grimes * This version includes many modifications by Jim Guyton 36ea022d16SRodney W. Grimes * <guyton@rand-unix>. 37ea022d16SRodney W. Grimes */ 38ea022d16SRodney W. Grimes 39ea022d16SRodney W. Grimes #include <sys/param.h> 40ea022d16SRodney W. Grimes #include <sys/ioctl.h> 41ea022d16SRodney W. Grimes #include <sys/socket.h> 42eb0292d9SDag-Erling Smørgrav #include <sys/stat.h> 43eb0292d9SDag-Erling Smørgrav #include <sys/time.h> 44ea022d16SRodney W. Grimes 45ea022d16SRodney W. Grimes #include <netinet/in.h> 46ea022d16SRodney W. Grimes #include <arpa/tftp.h> 47ea022d16SRodney W. Grimes 48ea022d16SRodney W. Grimes #include <ctype.h> 49ea022d16SRodney W. Grimes #include <errno.h> 50ea022d16SRodney W. Grimes #include <fcntl.h> 51ea022d16SRodney W. Grimes #include <netdb.h> 52a8faeabcSPhilippe Charnier #include <pwd.h> 53b713097aSMarius Strobl #include <stdint.h> 54ea022d16SRodney W. Grimes #include <stdio.h> 55ea022d16SRodney W. Grimes #include <stdlib.h> 56ea022d16SRodney W. Grimes #include <string.h> 57ea022d16SRodney W. Grimes #include <syslog.h> 58eb0292d9SDag-Erling Smørgrav #include <time.h> 59ea022d16SRodney W. Grimes #include <unistd.h> 60ea022d16SRodney W. Grimes 615276e639SWarner Losh #include "tftp-file.h" 625276e639SWarner Losh #include "tftp-io.h" 635276e639SWarner Losh #include "tftp-utils.h" 645276e639SWarner Losh #include "tftp-transfer.h" 655276e639SWarner Losh #include "tftp-options.h" 66ea022d16SRodney W. Grimes 674eb4663bSEnji Cooper #ifdef LIBWRAP 684eb4663bSEnji Cooper #include <tcpd.h> 694eb4663bSEnji Cooper #endif 704eb4663bSEnji Cooper 71*1ed44fccSDag-Erling Smørgrav static void tftp_wrq(int peer, char *, size_t); 72*1ed44fccSDag-Erling Smørgrav static void tftp_rrq(int peer, char *, size_t); 73ea022d16SRodney W. Grimes 74ea022d16SRodney W. Grimes /* 75ea022d16SRodney W. Grimes * Null-terminated directory prefix list for absolute pathname requests and 76ea022d16SRodney W. Grimes * search list for relative pathname requests. 77ea022d16SRodney W. Grimes * 78ea022d16SRodney W. Grimes * MAXDIRS should be at least as large as the number of arguments that 79ea022d16SRodney W. Grimes * inetd allows (currently 20). 80ea022d16SRodney W. Grimes */ 81ea022d16SRodney W. Grimes #define MAXDIRS 20 82ea022d16SRodney W. Grimes static struct dirlist { 83f49c0dc0SDavid Malone const char *name; 84*1ed44fccSDag-Erling Smørgrav size_t len; 85ea022d16SRodney W. Grimes } dirs[MAXDIRS+1]; 86ea022d16SRodney W. Grimes static int suppress_naks; 87ea022d16SRodney W. Grimes static int logging; 881ed0e5d2SBill Fumerola static int ipchroot; 89273a307dSEugene Grosbein static int check_woth = 1; 90eff77877SMatthew N. Dodd static int create_new = 0; 9104ebad38SMarius Strobl static const char *newfile_format = "%Y%m%d"; 92dba0fd30SEdwin Groothuis static int increase_name = 0; 93eff77877SMatthew N. Dodd static mode_t mask = S_IWGRP | S_IWOTH; 94ea022d16SRodney W. Grimes 955276e639SWarner Losh struct formats; 965276e639SWarner Losh static void tftp_recvfile(int peer, const char *mode); 975276e639SWarner Losh static void tftp_xmitfile(int peer, const char *mode); 985276e639SWarner Losh static int validate_access(int peer, char **, int); 995276e639SWarner Losh static char peername[NI_MAXHOST]; 100f49c0dc0SDavid Malone 101ae824d80SEd Schouten static FILE *file; 1025276e639SWarner Losh 103ae824d80SEd Schouten static struct formats { 1045276e639SWarner Losh const char *f_mode; 1055276e639SWarner Losh int f_convert; 1065276e639SWarner Losh } formats[] = { 1075276e639SWarner Losh { "netascii", 1 }, 1085276e639SWarner Losh { "octet", 0 }, 1095276e639SWarner Losh { NULL, 0 } 1105276e639SWarner Losh }; 111ea022d16SRodney W. Grimes 112ea022d16SRodney W. Grimes int 113dc4c3024SWarner Losh main(int argc, char *argv[]) 114ea022d16SRodney W. Grimes { 115dc4c3024SWarner Losh struct tftphdr *tp; 1165276e639SWarner Losh int peer; 1175276e639SWarner Losh socklen_t peerlen, len; 1185276e639SWarner Losh ssize_t n; 1195276e639SWarner Losh int ch; 1208ea31785SWarner Losh char *chroot_dir = NULL; 1218ea31785SWarner Losh struct passwd *nobody; 122f49c0dc0SDavid Malone const char *chuser = "nobody"; 1235276e639SWarner Losh char recvbuffer[MAXPKTSIZE]; 124b4736c90SDag-Erling Smørgrav int allow_ro = 1, allow_wo = 1, on = 1; 125b4736c90SDag-Erling Smørgrav pid_t pid; 126ea022d16SRodney W. Grimes 1273ec73cf1SBrian Somers tzset(); /* syslog in localtime */ 1285276e639SWarner Losh acting_as_client = 0; 1293ec73cf1SBrian Somers 1305276e639SWarner Losh tftp_openlog("tftpd", LOG_PID | LOG_NDELAY, LOG_FTP); 131615d167cSDmitry Morozovsky while ((ch = getopt(argc, argv, "cCd::F:lnoOp:s:Su:U:wW")) != -1) { 132ea022d16SRodney W. Grimes switch (ch) { 1331ed0e5d2SBill Fumerola case 'c': 1341ed0e5d2SBill Fumerola ipchroot = 1; 1351ed0e5d2SBill Fumerola break; 1361ed0e5d2SBill Fumerola case 'C': 1371ed0e5d2SBill Fumerola ipchroot = 2; 1381ed0e5d2SBill Fumerola break; 1395276e639SWarner Losh case 'd': 1409f6f6494SDag-Erling Smørgrav if (optarg == NULL) 1419f6f6494SDag-Erling Smørgrav debug++; 1429f6f6494SDag-Erling Smørgrav else if (atoi(optarg) != 0) 1435276e639SWarner Losh debug += atoi(optarg); 1445276e639SWarner Losh else 1455276e639SWarner Losh debug |= debug_finds(optarg); 1465276e639SWarner Losh break; 147dba0fd30SEdwin Groothuis case 'F': 148dba0fd30SEdwin Groothuis newfile_format = optarg; 149dba0fd30SEdwin Groothuis break; 150ea022d16SRodney W. Grimes case 'l': 151ea022d16SRodney W. Grimes logging = 1; 152ea022d16SRodney W. Grimes break; 153ea022d16SRodney W. Grimes case 'n': 154ea022d16SRodney W. Grimes suppress_naks = 1; 155ea022d16SRodney W. Grimes break; 1565276e639SWarner Losh case 'o': 1575276e639SWarner Losh options_rfc_enabled = 0; 1585276e639SWarner Losh break; 1595276e639SWarner Losh case 'O': 1605276e639SWarner Losh options_extra_enabled = 0; 1615276e639SWarner Losh break; 1625276e639SWarner Losh case 'p': 1635276e639SWarner Losh packetdroppercentage = atoi(optarg); 1645276e639SWarner Losh tftp_log(LOG_INFO, 1655276e639SWarner Losh "Randomly dropping %d out of 100 packets", 1665276e639SWarner Losh packetdroppercentage); 1675276e639SWarner Losh break; 1688ea31785SWarner Losh case 's': 1698ea31785SWarner Losh chroot_dir = optarg; 1708ea31785SWarner Losh break; 171273a307dSEugene Grosbein case 'S': 172273a307dSEugene Grosbein check_woth = -1; 173273a307dSEugene Grosbein break; 174f62eaadfSGarrett Wollman case 'u': 175f62eaadfSGarrett Wollman chuser = optarg; 176f62eaadfSGarrett Wollman break; 177eff77877SMatthew N. Dodd case 'U': 178eff77877SMatthew N. Dodd mask = strtol(optarg, NULL, 0); 179eff77877SMatthew N. Dodd break; 180eff77877SMatthew N. Dodd case 'w': 181eff77877SMatthew N. Dodd create_new = 1; 182eff77877SMatthew N. Dodd break; 183dba0fd30SEdwin Groothuis case 'W': 184dba0fd30SEdwin Groothuis create_new = 1; 185dba0fd30SEdwin Groothuis increase_name = 1; 186dba0fd30SEdwin Groothuis break; 187ea022d16SRodney W. Grimes default: 1885276e639SWarner Losh tftp_log(LOG_WARNING, 1895276e639SWarner Losh "ignoring unknown option -%c", ch); 190ea022d16SRodney W. Grimes } 191ea022d16SRodney W. Grimes } 192ea022d16SRodney W. Grimes if (optind < argc) { 193ea022d16SRodney W. Grimes struct dirlist *dirp; 194ea022d16SRodney W. Grimes 195ea022d16SRodney W. Grimes /* Get list of directory prefixes. Skip relative pathnames. */ 196ea022d16SRodney W. Grimes for (dirp = dirs; optind < argc && dirp < &dirs[MAXDIRS]; 197ea022d16SRodney W. Grimes optind++) { 198ea022d16SRodney W. Grimes if (argv[optind][0] == '/') { 199ea022d16SRodney W. Grimes dirp->name = argv[optind]; 200ea022d16SRodney W. Grimes dirp->len = strlen(dirp->name); 201ea022d16SRodney W. Grimes dirp++; 202ea022d16SRodney W. Grimes } 203ea022d16SRodney W. Grimes } 204ea022d16SRodney W. Grimes } 2058ea31785SWarner Losh else if (chroot_dir) { 2068ea31785SWarner Losh dirs->name = "/"; 2078ea31785SWarner Losh dirs->len = 1; 2088ea31785SWarner Losh } 209a273f3aeSBill Fumerola if (ipchroot > 0 && chroot_dir == NULL) { 2105276e639SWarner Losh tftp_log(LOG_ERR, "-c requires -s"); 2111ed0e5d2SBill Fumerola exit(1); 2121ed0e5d2SBill Fumerola } 213ea022d16SRodney W. Grimes 214eff77877SMatthew N. Dodd umask(mask); 215eff77877SMatthew N. Dodd 216ea022d16SRodney W. Grimes if (ioctl(0, FIONBIO, &on) < 0) { 2175276e639SWarner Losh tftp_log(LOG_ERR, "ioctl(FIONBIO): %s", strerror(errno)); 218ea022d16SRodney W. Grimes exit(1); 219ea022d16SRodney W. Grimes } 2205276e639SWarner Losh 2215276e639SWarner Losh /* Find out who we are talking to and what we are going to do */ 2225276e639SWarner Losh peerlen = sizeof(peer_sock); 2235276e639SWarner Losh n = recvfrom(0, recvbuffer, MAXPKTSIZE, 0, 2245276e639SWarner Losh (struct sockaddr *)&peer_sock, &peerlen); 225ea022d16SRodney W. Grimes if (n < 0) { 2265276e639SWarner Losh tftp_log(LOG_ERR, "recvfrom: %s", strerror(errno)); 227ea022d16SRodney W. Grimes exit(1); 228ea022d16SRodney W. Grimes } 2295276e639SWarner Losh getnameinfo((struct sockaddr *)&peer_sock, peer_sock.ss_len, 2305276e639SWarner Losh peername, sizeof(peername), NULL, 0, NI_NUMERICHOST); 2315276e639SWarner Losh 232ea022d16SRodney W. Grimes /* 233ea022d16SRodney W. Grimes * Now that we have read the message out of the UDP 234ea022d16SRodney W. Grimes * socket, we fork and exit. Thus, inetd will go back 235ea022d16SRodney W. Grimes * to listening to the tftp port, and the next request 236ea022d16SRodney W. Grimes * to come in will start up a new instance of tftpd. 237ea022d16SRodney W. Grimes * 238ea022d16SRodney W. Grimes * We do this so that inetd can run tftpd in "wait" mode. 239ea022d16SRodney W. Grimes * The problem with tftpd running in "nowait" mode is that 240ea022d16SRodney W. Grimes * inetd may get one or more successful "selects" on the 241ea022d16SRodney W. Grimes * tftp port before we do our receive, so more than one 242ea022d16SRodney W. Grimes * instance of tftpd may be started up. Worse, if tftpd 243ea022d16SRodney W. Grimes * break before doing the above "recvfrom", inetd would 244ea022d16SRodney W. Grimes * spawn endless instances, clogging the system. 245ea022d16SRodney W. Grimes */ 246ea022d16SRodney W. Grimes pid = fork(); 247ea022d16SRodney W. Grimes if (pid < 0) { 2485276e639SWarner Losh tftp_log(LOG_ERR, "fork: %s", strerror(errno)); 249ea022d16SRodney W. Grimes exit(1); 250ea022d16SRodney W. Grimes } else if (pid != 0) { 251ea022d16SRodney W. Grimes exit(0); 252ea022d16SRodney W. Grimes } 253b4736c90SDag-Erling Smørgrav /* child */ 2548ea31785SWarner Losh 2554eb4663bSEnji Cooper #ifdef LIBWRAP 2568ea31785SWarner Losh /* 2575276e639SWarner Losh * See if the client is allowed to talk to me. 2585276e639SWarner Losh * (This needs to be done before the chroot()) 2595276e639SWarner Losh */ 2605276e639SWarner Losh { 2615276e639SWarner Losh struct request_info req; 2625276e639SWarner Losh 2635276e639SWarner Losh request_init(&req, RQ_CLIENT_ADDR, peername, 0); 2645276e639SWarner Losh request_set(&req, RQ_DAEMON, "tftpd", 0); 2655276e639SWarner Losh 2665276e639SWarner Losh if (hosts_access(&req) == 0) { 2675276e639SWarner Losh if (debug & DEBUG_ACCESS) 2685276e639SWarner Losh tftp_log(LOG_WARNING, 2695276e639SWarner Losh "Access denied by 'tftpd' entry " 2705276e639SWarner Losh "in /etc/hosts.allow"); 2715276e639SWarner Losh 2725276e639SWarner Losh /* 2735276e639SWarner Losh * Full access might be disabled, but maybe the 2745276e639SWarner Losh * client is allowed to do read-only access. 2755276e639SWarner Losh */ 2765276e639SWarner Losh request_set(&req, RQ_DAEMON, "tftpd-ro", 0); 2775276e639SWarner Losh allow_ro = hosts_access(&req); 2785276e639SWarner Losh 2795276e639SWarner Losh request_set(&req, RQ_DAEMON, "tftpd-wo", 0); 2805276e639SWarner Losh allow_wo = hosts_access(&req); 2815276e639SWarner Losh 2825276e639SWarner Losh if (allow_ro == 0 && allow_wo == 0) { 2835276e639SWarner Losh tftp_log(LOG_WARNING, 2845276e639SWarner Losh "Unauthorized access from %s", peername); 2855276e639SWarner Losh exit(1); 2865276e639SWarner Losh } 2875276e639SWarner Losh 2885276e639SWarner Losh if (debug & DEBUG_ACCESS) { 2895276e639SWarner Losh if (allow_ro) 2905276e639SWarner Losh tftp_log(LOG_WARNING, 2915276e639SWarner Losh "But allowed readonly access " 2925276e639SWarner Losh "via 'tftpd-ro' entry"); 2935276e639SWarner Losh if (allow_wo) 2945276e639SWarner Losh tftp_log(LOG_WARNING, 2955276e639SWarner Losh "But allowed writeonly access " 2965276e639SWarner Losh "via 'tftpd-wo' entry"); 2975276e639SWarner Losh } 2985276e639SWarner Losh } else 2995276e639SWarner Losh if (debug & DEBUG_ACCESS) 3005276e639SWarner Losh tftp_log(LOG_WARNING, 3015276e639SWarner Losh "Full access allowed" 3025276e639SWarner Losh "in /etc/hosts.allow"); 3035276e639SWarner Losh } 3044eb4663bSEnji Cooper #endif 3055276e639SWarner Losh 3065276e639SWarner Losh /* 3078ea31785SWarner Losh * Since we exit here, we should do that only after the above 3088ea31785SWarner Losh * recvfrom to keep inetd from constantly forking should there 3098ea31785SWarner Losh * be a problem. See the above comment about system clogging. 3108ea31785SWarner Losh */ 3118ea31785SWarner Losh if (chroot_dir) { 312a273f3aeSBill Fumerola if (ipchroot > 0) { 3131ed0e5d2SBill Fumerola char *tempchroot; 3141ed0e5d2SBill Fumerola struct stat sb; 3151ed0e5d2SBill Fumerola int statret; 3164dac6235SHajimu UMEMOTO struct sockaddr_storage ss; 3174dac6235SHajimu UMEMOTO char hbuf[NI_MAXHOST]; 3181ed0e5d2SBill Fumerola 3195276e639SWarner Losh statret = -1; 3205276e639SWarner Losh memcpy(&ss, &peer_sock, peer_sock.ss_len); 3214dac6235SHajimu UMEMOTO unmappedaddr((struct sockaddr_in6 *)&ss); 3224dac6235SHajimu UMEMOTO getnameinfo((struct sockaddr *)&ss, ss.ss_len, 3234dac6235SHajimu UMEMOTO hbuf, sizeof(hbuf), NULL, 0, 3244f101318SHajimu UMEMOTO NI_NUMERICHOST); 3254dac6235SHajimu UMEMOTO asprintf(&tempchroot, "%s/%s", chroot_dir, hbuf); 326a273f3aeSBill Fumerola if (ipchroot == 2) 3271ed0e5d2SBill Fumerola statret = stat(tempchroot, &sb); 328a273f3aeSBill Fumerola if (ipchroot == 1 || 329a273f3aeSBill Fumerola (statret == 0 && (sb.st_mode & S_IFDIR))) 3301ed0e5d2SBill Fumerola chroot_dir = tempchroot; 3311ed0e5d2SBill Fumerola } 3328ea31785SWarner Losh /* Must get this before chroot because /etc might go away */ 333f62eaadfSGarrett Wollman if ((nobody = getpwnam(chuser)) == NULL) { 3345276e639SWarner Losh tftp_log(LOG_ERR, "%s: no such user", chuser); 3358ea31785SWarner Losh exit(1); 3368ea31785SWarner Losh } 3378ea31785SWarner Losh if (chroot(chroot_dir)) { 3385276e639SWarner Losh tftp_log(LOG_ERR, "chroot: %s: %s", 3395276e639SWarner Losh chroot_dir, strerror(errno)); 3408ea31785SWarner Losh exit(1); 3418ea31785SWarner Losh } 3420aabff28SMark Johnston if (chdir("/") != 0) { 3430aabff28SMark Johnston tftp_log(LOG_ERR, "chdir: %s", strerror(errno)); 3440aabff28SMark Johnston exit(1); 3450aabff28SMark Johnston } 3463c0fa265SAlan Somers if (setgroups(1, &nobody->pw_gid) != 0) { 3473c0fa265SAlan Somers tftp_log(LOG_ERR, "setgroups failed"); 3483c0fa265SAlan Somers exit(1); 3493c0fa265SAlan Somers } 35050e04779SEitan Adler if (setuid(nobody->pw_uid) != 0) { 35150e04779SEitan Adler tftp_log(LOG_ERR, "setuid failed"); 35250e04779SEitan Adler exit(1); 35350e04779SEitan Adler } 354273a307dSEugene Grosbein if (check_woth == -1) 355273a307dSEugene Grosbein check_woth = 0; 3568ea31785SWarner Losh } 357273a307dSEugene Grosbein if (check_woth == -1) 358273a307dSEugene Grosbein check_woth = 1; 3598ea31785SWarner Losh 3605276e639SWarner Losh len = sizeof(me_sock); 3615276e639SWarner Losh if (getsockname(0, (struct sockaddr *)&me_sock, &len) == 0) { 3625276e639SWarner Losh switch (me_sock.ss_family) { 3634dac6235SHajimu UMEMOTO case AF_INET: 3645276e639SWarner Losh ((struct sockaddr_in *)&me_sock)->sin_port = 0; 3654dac6235SHajimu UMEMOTO break; 3664dac6235SHajimu UMEMOTO case AF_INET6: 3675276e639SWarner Losh ((struct sockaddr_in6 *)&me_sock)->sin6_port = 0; 3684dac6235SHajimu UMEMOTO break; 3694dac6235SHajimu UMEMOTO default: 3704dac6235SHajimu UMEMOTO /* unsupported */ 3714dac6235SHajimu UMEMOTO break; 3724dac6235SHajimu UMEMOTO } 3734dac6235SHajimu UMEMOTO } else { 3745276e639SWarner Losh memset(&me_sock, 0, sizeof(me_sock)); 3755276e639SWarner Losh me_sock.ss_family = peer_sock.ss_family; 3765276e639SWarner Losh me_sock.ss_len = peer_sock.ss_len; 3774dac6235SHajimu UMEMOTO } 37877e83935SDag-Erling Smørgrav close(STDIN_FILENO); 37977e83935SDag-Erling Smørgrav close(STDOUT_FILENO); 38077e83935SDag-Erling Smørgrav close(STDERR_FILENO); 3815276e639SWarner Losh peer = socket(peer_sock.ss_family, SOCK_DGRAM, 0); 382ea022d16SRodney W. Grimes if (peer < 0) { 3835276e639SWarner Losh tftp_log(LOG_ERR, "socket: %s", strerror(errno)); 384ea022d16SRodney W. Grimes exit(1); 385ea022d16SRodney W. Grimes } 3865276e639SWarner Losh if (bind(peer, (struct sockaddr *)&me_sock, me_sock.ss_len) < 0) { 3875276e639SWarner Losh tftp_log(LOG_ERR, "bind: %s", strerror(errno)); 388ea022d16SRodney W. Grimes exit(1); 389ea022d16SRodney W. Grimes } 3905276e639SWarner Losh 3915276e639SWarner Losh tp = (struct tftphdr *)recvbuffer; 392ea022d16SRodney W. Grimes tp->th_opcode = ntohs(tp->th_opcode); 3935276e639SWarner Losh if (tp->th_opcode == RRQ) { 3945276e639SWarner Losh if (allow_ro) 395*1ed44fccSDag-Erling Smørgrav tftp_rrq(peer, tp->th_stuff, (size_t)n - 1); 3965276e639SWarner Losh else { 3975276e639SWarner Losh tftp_log(LOG_WARNING, 3985276e639SWarner Losh "%s read access denied", peername); 3995276e639SWarner Losh exit(1); 4005276e639SWarner Losh } 4016301d647SAlan Somers } else if (tp->th_opcode == WRQ) { 4025276e639SWarner Losh if (allow_wo) 403*1ed44fccSDag-Erling Smørgrav tftp_wrq(peer, tp->th_stuff, (size_t)n - 1); 4045276e639SWarner Losh else { 4055276e639SWarner Losh tftp_log(LOG_WARNING, 4065276e639SWarner Losh "%s write access denied", peername); 4075276e639SWarner Losh exit(1); 4085276e639SWarner Losh } 4096301d647SAlan Somers } else 4106301d647SAlan Somers send_error(peer, EBADOP); 411ea022d16SRodney W. Grimes exit(1); 412ea022d16SRodney W. Grimes } 413ea022d16SRodney W. Grimes 4147bc7e0c8SBrian Somers static void 4157bc7e0c8SBrian Somers reduce_path(char *fn) 4167bc7e0c8SBrian Somers { 4177bc7e0c8SBrian Somers char *slash, *ptr; 4187bc7e0c8SBrian Somers 4197bc7e0c8SBrian Somers /* Reduce all "/+./" to "/" (just in case we've got "/./../" later */ 4207bc7e0c8SBrian Somers while ((slash = strstr(fn, "/./")) != NULL) { 4217bc7e0c8SBrian Somers for (ptr = slash; ptr > fn && ptr[-1] == '/'; ptr--) 4227bc7e0c8SBrian Somers ; 4237bc7e0c8SBrian Somers slash += 2; 4247bc7e0c8SBrian Somers while (*slash) 4257bc7e0c8SBrian Somers *++ptr = *++slash; 4267bc7e0c8SBrian Somers } 4277bc7e0c8SBrian Somers 4287bc7e0c8SBrian Somers /* Now reduce all "/something/+../" to "/" */ 4297bc7e0c8SBrian Somers while ((slash = strstr(fn, "/../")) != NULL) { 4307bc7e0c8SBrian Somers if (slash == fn) 4317bc7e0c8SBrian Somers break; 4327bc7e0c8SBrian Somers for (ptr = slash; ptr > fn && ptr[-1] == '/'; ptr--) 4337bc7e0c8SBrian Somers ; 4347bc7e0c8SBrian Somers for (ptr--; ptr >= fn; ptr--) 4357bc7e0c8SBrian Somers if (*ptr == '/') 4367bc7e0c8SBrian Somers break; 4377bc7e0c8SBrian Somers if (ptr < fn) 4387bc7e0c8SBrian Somers break; 4397bc7e0c8SBrian Somers slash += 3; 4407bc7e0c8SBrian Somers while (*slash) 4417bc7e0c8SBrian Somers *++ptr = *++slash; 4427bc7e0c8SBrian Somers } 4437bc7e0c8SBrian Somers } 4447bc7e0c8SBrian Somers 4455276e639SWarner Losh static char * 446*1ed44fccSDag-Erling Smørgrav parse_header(int peer, char *recvbuffer, size_t size, 4475276e639SWarner Losh char **filename, char **mode) 448ea022d16SRodney W. Grimes { 449dc4c3024SWarner Losh char *cp; 4505276e639SWarner Losh int i; 451dc4c3024SWarner Losh struct formats *pf; 452ea022d16SRodney W. Grimes 4535276e639SWarner Losh *mode = NULL; 4545276e639SWarner Losh cp = recvbuffer; 4555276e639SWarner Losh 4565276e639SWarner Losh i = get_field(peer, recvbuffer, size); 4575276e639SWarner Losh if (i >= PATH_MAX) { 4585276e639SWarner Losh tftp_log(LOG_ERR, "Bad option - filename too long"); 4595276e639SWarner Losh send_error(peer, EBADOP); 460ea022d16SRodney W. Grimes exit(1); 461ea022d16SRodney W. Grimes } 4625276e639SWarner Losh *filename = recvbuffer; 4635276e639SWarner Losh tftp_log(LOG_INFO, "Filename: '%s'", *filename); 4645276e639SWarner Losh cp += i; 4655276e639SWarner Losh 4665276e639SWarner Losh i = get_field(peer, cp, size); 4675276e639SWarner Losh *mode = cp; 4685276e639SWarner Losh cp += i; 4695276e639SWarner Losh 4705276e639SWarner Losh /* Find the file transfer mode */ 4715276e639SWarner Losh for (cp = *mode; *cp; cp++) 472ea022d16SRodney W. Grimes if (isupper(*cp)) 473ea022d16SRodney W. Grimes *cp = tolower(*cp); 474ea022d16SRodney W. Grimes for (pf = formats; pf->f_mode; pf++) 4755276e639SWarner Losh if (strcmp(pf->f_mode, *mode) == 0) 476ea022d16SRodney W. Grimes break; 4775276e639SWarner Losh if (pf->f_mode == NULL) { 4785276e639SWarner Losh tftp_log(LOG_ERR, 4795276e639SWarner Losh "Bad option - Unknown transfer mode (%s)", *mode); 4805276e639SWarner Losh send_error(peer, EBADOP); 481ea022d16SRodney W. Grimes exit(1); 482ea022d16SRodney W. Grimes } 4835276e639SWarner Losh tftp_log(LOG_INFO, "Mode: '%s'", *mode); 4845276e639SWarner Losh 4855276e639SWarner Losh return (cp + 1); 4865276e639SWarner Losh } 4875276e639SWarner Losh 48814f0ab1cSBenno Rice /* 4895276e639SWarner Losh * WRQ - receive a file from the client 49014f0ab1cSBenno Rice */ 4915276e639SWarner Losh void 492*1ed44fccSDag-Erling Smørgrav tftp_wrq(int peer, char *recvbuffer, size_t size) 4935276e639SWarner Losh { 4945276e639SWarner Losh char *cp; 4955276e639SWarner Losh int has_options = 0, ecode; 4965276e639SWarner Losh char *filename, *mode; 4975276e639SWarner Losh char fnbuf[PATH_MAX]; 498c9374115SDavid E. O'Brien 4995276e639SWarner Losh cp = parse_header(peer, recvbuffer, size, &filename, &mode); 5005276e639SWarner Losh size -= (cp - recvbuffer) + 1; 5015276e639SWarner Losh 5023c0fa265SAlan Somers strlcpy(fnbuf, filename, sizeof(fnbuf)); 5035276e639SWarner Losh reduce_path(fnbuf); 5045276e639SWarner Losh filename = fnbuf; 5055276e639SWarner Losh 5065276e639SWarner Losh if (size > 0) { 5075276e639SWarner Losh if (options_rfc_enabled) 5085276e639SWarner Losh has_options = !parse_options(peer, cp, size); 509c9374115SDavid E. O'Brien else 5105276e639SWarner Losh tftp_log(LOG_INFO, "Options found but not enabled"); 511c9374115SDavid E. O'Brien } 512c9374115SDavid E. O'Brien 5135276e639SWarner Losh ecode = validate_access(peer, &filename, WRQ); 5145276e639SWarner Losh if (ecode == 0) { 5155276e639SWarner Losh if (has_options) 5165276e639SWarner Losh send_oack(peer); 5175276e639SWarner Losh else 5185276e639SWarner Losh send_ack(peer, 0); 5195276e639SWarner Losh } 520ea022d16SRodney W. Grimes if (logging) { 5215276e639SWarner Losh tftp_log(LOG_INFO, "%s: write request for %s: %s", peername, 522ea022d16SRodney W. Grimes filename, errtomsg(ecode)); 523ea022d16SRodney W. Grimes } 5245276e639SWarner Losh 525b7da179eSAlan Somers if (ecode) { 526b7da179eSAlan Somers send_error(peer, ecode); 527b7da179eSAlan Somers exit(1); 528b7da179eSAlan Somers } 5295276e639SWarner Losh tftp_recvfile(peer, mode); 5305276e639SWarner Losh exit(0); 5315276e639SWarner Losh } 5325276e639SWarner Losh 5335276e639SWarner Losh /* 5345276e639SWarner Losh * RRQ - send a file to the client 5355276e639SWarner Losh */ 5365276e639SWarner Losh void 537*1ed44fccSDag-Erling Smørgrav tftp_rrq(int peer, char *recvbuffer, size_t size) 5385276e639SWarner Losh { 5395276e639SWarner Losh char *cp; 5405276e639SWarner Losh int has_options = 0, ecode; 5415276e639SWarner Losh char *filename, *mode; 5425276e639SWarner Losh char fnbuf[PATH_MAX]; 5435276e639SWarner Losh 5445276e639SWarner Losh cp = parse_header(peer, recvbuffer, size, &filename, &mode); 5455276e639SWarner Losh size -= (cp - recvbuffer) + 1; 5465276e639SWarner Losh 5473c0fa265SAlan Somers strlcpy(fnbuf, filename, sizeof(fnbuf)); 5485276e639SWarner Losh reduce_path(fnbuf); 5495276e639SWarner Losh filename = fnbuf; 5505276e639SWarner Losh 5515276e639SWarner Losh if (size > 0) { 5525276e639SWarner Losh if (options_rfc_enabled) 5535276e639SWarner Losh has_options = !parse_options(peer, cp, size); 5545276e639SWarner Losh else 5555276e639SWarner Losh tftp_log(LOG_INFO, "Options found but not enabled"); 5565276e639SWarner Losh } 5575276e639SWarner Losh 5585276e639SWarner Losh ecode = validate_access(peer, &filename, RRQ); 5595276e639SWarner Losh if (ecode == 0) { 5605276e639SWarner Losh if (has_options) { 5615276e639SWarner Losh int n; 5625276e639SWarner Losh char lrecvbuffer[MAXPKTSIZE]; 5635276e639SWarner Losh struct tftphdr *rp = (struct tftphdr *)lrecvbuffer; 5645276e639SWarner Losh 5655276e639SWarner Losh send_oack(peer); 5665276e639SWarner Losh n = receive_packet(peer, lrecvbuffer, MAXPKTSIZE, 5675276e639SWarner Losh NULL, timeoutpacket); 5685276e639SWarner Losh if (n < 0) { 5695276e639SWarner Losh if (debug & DEBUG_SIMPLE) 5705276e639SWarner Losh tftp_log(LOG_DEBUG, "Aborting: %s", 5715276e639SWarner Losh rp_strerror(n)); 5725276e639SWarner Losh return; 5735276e639SWarner Losh } 5745276e639SWarner Losh if (rp->th_opcode != ACK) { 5755276e639SWarner Losh if (debug & DEBUG_SIMPLE) 5765276e639SWarner Losh tftp_log(LOG_DEBUG, 5775276e639SWarner Losh "Expected ACK, got %s on OACK", 5785276e639SWarner Losh packettype(rp->th_opcode)); 5795276e639SWarner Losh return; 5805276e639SWarner Losh } 5815276e639SWarner Losh } 5825276e639SWarner Losh } 5835276e639SWarner Losh 5845276e639SWarner Losh if (logging) 5855276e639SWarner Losh tftp_log(LOG_INFO, "%s: read request for %s: %s", peername, 5865276e639SWarner Losh filename, errtomsg(ecode)); 5875276e639SWarner Losh 588ea022d16SRodney W. Grimes if (ecode) { 589ea022d16SRodney W. Grimes /* 590ea022d16SRodney W. Grimes * Avoid storms of naks to a RRQ broadcast for a relative 591ea022d16SRodney W. Grimes * bootfile pathname from a diskless Sun. 592ea022d16SRodney W. Grimes */ 593ea022d16SRodney W. Grimes if (suppress_naks && *filename != '/' && ecode == ENOTFOUND) 594ea022d16SRodney W. Grimes exit(0); 5955276e639SWarner Losh send_error(peer, ecode); 596ea022d16SRodney W. Grimes exit(1); 597ea022d16SRodney W. Grimes } 5985276e639SWarner Losh tftp_xmitfile(peer, mode); 599ea022d16SRodney W. Grimes } 600ea022d16SRodney W. Grimes 601ea022d16SRodney W. Grimes /* 602dba0fd30SEdwin Groothuis * Find the next value for YYYYMMDD.nn when the file to be written should 603dba0fd30SEdwin Groothuis * be unique. Due to the limitations of nn, we will fail if nn reaches 100. 604dba0fd30SEdwin Groothuis * Besides, that is four updates per hour on a file, which is kind of 605dba0fd30SEdwin Groothuis * execessive anyway. 606dba0fd30SEdwin Groothuis */ 607dba0fd30SEdwin Groothuis static int 608dba0fd30SEdwin Groothuis find_next_name(char *filename, int *fd) 609dba0fd30SEdwin Groothuis { 610dba0fd30SEdwin Groothuis int i; 611dba0fd30SEdwin Groothuis time_t tval; 612dba0fd30SEdwin Groothuis size_t len; 613dba0fd30SEdwin Groothuis struct tm lt; 614dba0fd30SEdwin Groothuis char yyyymmdd[MAXPATHLEN]; 615dba0fd30SEdwin Groothuis char newname[MAXPATHLEN]; 616dba0fd30SEdwin Groothuis 617dba0fd30SEdwin Groothuis /* Create the YYYYMMDD part of the filename */ 618dba0fd30SEdwin Groothuis time(&tval); 619dba0fd30SEdwin Groothuis lt = *localtime(&tval); 620dba0fd30SEdwin Groothuis len = strftime(yyyymmdd, sizeof(yyyymmdd), newfile_format, <); 621dba0fd30SEdwin Groothuis if (len == 0) { 622dba0fd30SEdwin Groothuis syslog(LOG_WARNING, 623dba0fd30SEdwin Groothuis "Filename suffix too long (%d characters maximum)", 624dba0fd30SEdwin Groothuis MAXPATHLEN); 625dba0fd30SEdwin Groothuis return (EACCESS); 626dba0fd30SEdwin Groothuis } 627dba0fd30SEdwin Groothuis 628dba0fd30SEdwin Groothuis /* Make sure the new filename is not too long */ 629dba0fd30SEdwin Groothuis if (strlen(filename) > MAXPATHLEN - len - 5) { 630dba0fd30SEdwin Groothuis syslog(LOG_WARNING, 6315276e639SWarner Losh "Filename too long (%zd characters, %zd maximum)", 632dba0fd30SEdwin Groothuis strlen(filename), MAXPATHLEN - len - 5); 633dba0fd30SEdwin Groothuis return (EACCESS); 634dba0fd30SEdwin Groothuis } 635dba0fd30SEdwin Groothuis 636dba0fd30SEdwin Groothuis /* Find the first file which doesn't exist */ 637dba0fd30SEdwin Groothuis for (i = 0; i < 100; i++) { 638dba0fd30SEdwin Groothuis sprintf(newname, "%s.%s.%02d", filename, yyyymmdd, i); 639dba0fd30SEdwin Groothuis *fd = open(newname, 640dba0fd30SEdwin Groothuis O_WRONLY | O_CREAT | O_EXCL, 641dba0fd30SEdwin Groothuis S_IRUSR | S_IWUSR | S_IRGRP | 642dba0fd30SEdwin Groothuis S_IWGRP | S_IROTH | S_IWOTH); 643dba0fd30SEdwin Groothuis if (*fd > 0) 644dba0fd30SEdwin Groothuis return 0; 645dba0fd30SEdwin Groothuis } 646dba0fd30SEdwin Groothuis 647dba0fd30SEdwin Groothuis return (EEXIST); 648dba0fd30SEdwin Groothuis } 649dba0fd30SEdwin Groothuis 650dba0fd30SEdwin Groothuis /* 651ea022d16SRodney W. Grimes * Validate file access. Since we 652ea022d16SRodney W. Grimes * have no uid or gid, for now require 653ea022d16SRodney W. Grimes * file to exist and be publicly 654ea022d16SRodney W. Grimes * readable/writable. 655ea022d16SRodney W. Grimes * If we were invoked with arguments 656ea022d16SRodney W. Grimes * from inetd then the file must also be 657ea022d16SRodney W. Grimes * in one of the given directory prefixes. 658ea022d16SRodney W. Grimes * Note also, full path name must be 659ea022d16SRodney W. Grimes * given as we have no login directory. 660ea022d16SRodney W. Grimes */ 661ea022d16SRodney W. Grimes int 6625276e639SWarner Losh validate_access(int peer, char **filep, int mode) 663ea022d16SRodney W. Grimes { 664ea022d16SRodney W. Grimes struct stat stbuf; 665ea022d16SRodney W. Grimes int fd; 666dba0fd30SEdwin Groothuis int error; 667ea022d16SRodney W. Grimes struct dirlist *dirp; 668ea022d16SRodney W. Grimes static char pathname[MAXPATHLEN]; 669ea022d16SRodney W. Grimes char *filename = *filep; 670ea022d16SRodney W. Grimes 671ea022d16SRodney W. Grimes /* 672ea022d16SRodney W. Grimes * Prevent tricksters from getting around the directory restrictions 673ea022d16SRodney W. Grimes */ 674ea022d16SRodney W. Grimes if (strstr(filename, "/../")) 675ea022d16SRodney W. Grimes return (EACCESS); 676ea022d16SRodney W. Grimes 677ea022d16SRodney W. Grimes if (*filename == '/') { 678ea022d16SRodney W. Grimes /* 679ea022d16SRodney W. Grimes * Allow the request if it's in one of the approved locations. 680ea022d16SRodney W. Grimes * Special case: check the null prefix ("/") by looking 681ea022d16SRodney W. Grimes * for length = 1 and relying on the arg. processing that 682ea022d16SRodney W. Grimes * it's a /. 683ea022d16SRodney W. Grimes */ 684ea022d16SRodney W. Grimes for (dirp = dirs; dirp->name != NULL; dirp++) { 685ea022d16SRodney W. Grimes if (dirp->len == 1 || 686ea022d16SRodney W. Grimes (!strncmp(filename, dirp->name, dirp->len) && 687ea022d16SRodney W. Grimes filename[dirp->len] == '/')) 688ea022d16SRodney W. Grimes break; 689ea022d16SRodney W. Grimes } 690ea022d16SRodney W. Grimes /* If directory list is empty, allow access to any file */ 691ea022d16SRodney W. Grimes if (dirp->name == NULL && dirp != dirs) 692ea022d16SRodney W. Grimes return (EACCESS); 693ea022d16SRodney W. Grimes if (stat(filename, &stbuf) < 0) 694ea022d16SRodney W. Grimes return (errno == ENOENT ? ENOTFOUND : EACCESS); 695ea022d16SRodney W. Grimes if ((stbuf.st_mode & S_IFMT) != S_IFREG) 696ea022d16SRodney W. Grimes return (ENOTFOUND); 697ea022d16SRodney W. Grimes if (mode == RRQ) { 698ea022d16SRodney W. Grimes if ((stbuf.st_mode & S_IROTH) == 0) 699ea022d16SRodney W. Grimes return (EACCESS); 700ea022d16SRodney W. Grimes } else { 701273a307dSEugene Grosbein if (check_woth && ((stbuf.st_mode & S_IWOTH) == 0)) 702ea022d16SRodney W. Grimes return (EACCESS); 703ea022d16SRodney W. Grimes } 704ea022d16SRodney W. Grimes } else { 705ea022d16SRodney W. Grimes int err; 706ea022d16SRodney W. Grimes 707ea022d16SRodney W. Grimes /* 708ea022d16SRodney W. Grimes * Relative file name: search the approved locations for it. 70923adc6b8SJordan K. Hubbard * Don't allow write requests that avoid directory 710ea022d16SRodney W. Grimes * restrictions. 711ea022d16SRodney W. Grimes */ 712ea022d16SRodney W. Grimes 71323adc6b8SJordan K. Hubbard if (!strncmp(filename, "../", 3)) 714ea022d16SRodney W. Grimes return (EACCESS); 715ea022d16SRodney W. Grimes 716ea022d16SRodney W. Grimes /* 717ea022d16SRodney W. Grimes * If the file exists in one of the directories and isn't 718ea022d16SRodney W. Grimes * readable, continue looking. However, change the error code 719ea022d16SRodney W. Grimes * to give an indication that the file exists. 720ea022d16SRodney W. Grimes */ 721ea022d16SRodney W. Grimes err = ENOTFOUND; 722ea022d16SRodney W. Grimes for (dirp = dirs; dirp->name != NULL; dirp++) { 723fca08b7cSWarner Losh snprintf(pathname, sizeof(pathname), "%s/%s", 724fca08b7cSWarner Losh dirp->name, filename); 725ea022d16SRodney W. Grimes if (stat(pathname, &stbuf) == 0 && 726ea022d16SRodney W. Grimes (stbuf.st_mode & S_IFMT) == S_IFREG) { 727d89aca76SAlan Somers if (mode == RRQ) { 728d89aca76SAlan Somers if ((stbuf.st_mode & S_IROTH) != 0) 729d89aca76SAlan Somers break; 730d89aca76SAlan Somers } else { 731273a307dSEugene Grosbein if (!check_woth || ((stbuf.st_mode & S_IWOTH) != 0)) 732ea022d16SRodney W. Grimes break; 733ea022d16SRodney W. Grimes } 734ea022d16SRodney W. Grimes err = EACCESS; 735ea022d16SRodney W. Grimes } 736ea022d16SRodney W. Grimes } 737eff77877SMatthew N. Dodd if (dirp->name != NULL) 738ea022d16SRodney W. Grimes *filep = filename = pathname; 739eff77877SMatthew N. Dodd else if (mode == RRQ) 740eff77877SMatthew N. Dodd return (err); 741d89aca76SAlan Somers else if (err != ENOTFOUND || !create_new) 742d89aca76SAlan Somers return (err); 743ea022d16SRodney W. Grimes } 7445276e639SWarner Losh 7455276e639SWarner Losh /* 7465276e639SWarner Losh * This option is handled here because it (might) require(s) the 7475276e639SWarner Losh * size of the file. 7485276e639SWarner Losh */ 7495276e639SWarner Losh option_tsize(peer, NULL, mode, &stbuf); 7505276e639SWarner Losh 751eff77877SMatthew N. Dodd if (mode == RRQ) 752eff77877SMatthew N. Dodd fd = open(filename, O_RDONLY); 753eff77877SMatthew N. Dodd else { 754dba0fd30SEdwin Groothuis if (create_new) { 755dba0fd30SEdwin Groothuis if (increase_name) { 756dba0fd30SEdwin Groothuis error = find_next_name(filename, &fd); 757dba0fd30SEdwin Groothuis if (error > 0) 758dba0fd30SEdwin Groothuis return (error + 100); 759dba0fd30SEdwin Groothuis } else 760dba0fd30SEdwin Groothuis fd = open(filename, 761dba0fd30SEdwin Groothuis O_WRONLY | O_TRUNC | O_CREAT, 762dba0fd30SEdwin Groothuis S_IRUSR | S_IWUSR | S_IRGRP | 763dba0fd30SEdwin Groothuis S_IWGRP | S_IROTH | S_IWOTH ); 764dba0fd30SEdwin Groothuis } else 765eff77877SMatthew N. Dodd fd = open(filename, O_WRONLY | O_TRUNC); 766eff77877SMatthew N. Dodd } 767ea022d16SRodney W. Grimes if (fd < 0) 768ea022d16SRodney W. Grimes return (errno + 100); 769ea022d16SRodney W. Grimes file = fdopen(fd, (mode == RRQ)? "r":"w"); 770ea022d16SRodney W. Grimes if (file == NULL) { 771e99c7b0dSMatthew N. Dodd close(fd); 772e99c7b0dSMatthew N. Dodd return (errno + 100); 773ea022d16SRodney W. Grimes } 774ea022d16SRodney W. Grimes return (0); 775ea022d16SRodney W. Grimes } 776ea022d16SRodney W. Grimes 7775276e639SWarner Losh static void 7785276e639SWarner Losh tftp_xmitfile(int peer, const char *mode) 779ea022d16SRodney W. Grimes { 7805276e639SWarner Losh uint16_t block; 7815276e639SWarner Losh time_t now; 7825276e639SWarner Losh struct tftp_stats ts; 783ea022d16SRodney W. Grimes 7843c0fa265SAlan Somers memset(&ts, 0, sizeof(ts)); 7855276e639SWarner Losh now = time(NULL); 7865276e639SWarner Losh if (debug & DEBUG_SIMPLE) 7875276e639SWarner Losh tftp_log(LOG_DEBUG, "Transmitting file"); 788ea022d16SRodney W. Grimes 7895276e639SWarner Losh read_init(0, file, mode); 790ea022d16SRodney W. Grimes block = 1; 7915276e639SWarner Losh tftp_send(peer, &block, &ts); 7925276e639SWarner Losh read_close(); 7935276e639SWarner Losh if (debug & DEBUG_SIMPLE) 794b713097aSMarius Strobl tftp_log(LOG_INFO, "Sent %jd bytes in %jd seconds", 795b713097aSMarius Strobl (intmax_t)ts.amount, (intmax_t)time(NULL) - now); 796ea022d16SRodney W. Grimes } 797ea022d16SRodney W. Grimes 7985276e639SWarner Losh static void 7995276e639SWarner Losh tftp_recvfile(int peer, const char *mode) 800ff93f08cSDoug Ambrisko { 8015276e639SWarner Losh uint16_t block; 8025276e639SWarner Losh struct timeval now1, now2; 8035276e639SWarner Losh struct tftp_stats ts; 804ea022d16SRodney W. Grimes 8055276e639SWarner Losh gettimeofday(&now1, NULL); 8065276e639SWarner Losh if (debug & DEBUG_SIMPLE) 8075276e639SWarner Losh tftp_log(LOG_DEBUG, "Receiving file"); 808ea022d16SRodney W. Grimes 8095276e639SWarner Losh write_init(0, file, mode); 810ea022d16SRodney W. Grimes 811ea022d16SRodney W. Grimes block = 0; 8125276e639SWarner Losh tftp_receive(peer, &block, &ts, NULL, 0); 813ea022d16SRodney W. Grimes 81404ebad38SMarius Strobl gettimeofday(&now2, NULL); 815ea022d16SRodney W. Grimes 8165276e639SWarner Losh if (debug & DEBUG_SIMPLE) { 8175276e639SWarner Losh double f; 8185276e639SWarner Losh if (now1.tv_usec > now2.tv_usec) { 8195276e639SWarner Losh now2.tv_usec += 1000000; 8205276e639SWarner Losh now2.tv_sec--; 821ea022d16SRodney W. Grimes } 8225276e639SWarner Losh 8235276e639SWarner Losh f = now2.tv_sec - now1.tv_sec + 8245276e639SWarner Losh (now2.tv_usec - now1.tv_usec) / 100000.0; 8255276e639SWarner Losh tftp_log(LOG_INFO, 826b713097aSMarius Strobl "Download of %jd bytes in %d blocks completed after %0.1f seconds\n", 827b713097aSMarius Strobl (intmax_t)ts.amount, block, f); 8285276e639SWarner Losh } 8295276e639SWarner Losh 830ea022d16SRodney W. Grimes return; 831ea022d16SRodney W. Grimes } 832