| /*************************************************************************** |
| * _ _ ____ _ |
| * Project ___| | | | _ \| | |
| * / __| | | | |_) | | |
| * | (__| |_| | _ <| |___ |
| * \___|\___/|_| \_\_____| |
| * |
| * |
| * Trivial file transfer protocol server. |
| * |
| * This code includes many modifications by Jim Guyton <guyton@rand-unix> |
| * |
| * This source file was started based on netkit-tftpd 0.17 |
| * Heavily modified for curl's test suite |
| */ |
| |
| /* |
| * Copyright (C) 2005 - 2020, Daniel Stenberg, <[email protected]>, et al. |
| * Copyright (c) 1983, Regents of the University of California. |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. All advertising materials mentioning features or use of this software |
| * must display the following acknowledgement: |
| * This product includes software developed by the University of |
| * California, Berkeley and its contributors. |
| * 4. Neither the name of the University nor the names of its contributors |
| * may be used to endorse or promote products derived from this software |
| * without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| */ |
| |
| #include "server_setup.h" |
| |
| #ifdef HAVE_SYS_IOCTL_H |
| #include <sys/ioctl.h> |
| #endif |
| #ifdef HAVE_SIGNAL_H |
| #include <signal.h> |
| #endif |
| #ifdef HAVE_FCNTL_H |
| #include <fcntl.h> |
| #endif |
| #ifdef HAVE_NETINET_IN_H |
| #include <netinet/in.h> |
| #endif |
| #ifdef HAVE_ARPA_INET_H |
| #include <arpa/inet.h> |
| #endif |
| #ifdef HAVE_ARPA_TFTP_H |
| #include <arpa/tftp.h> |
| #else |
| #include "tftp.h" |
| #endif |
| #ifdef HAVE_NETDB_H |
| #include <netdb.h> |
| #endif |
| #ifdef HAVE_SYS_FILIO_H |
| /* FIONREAD on Solaris 7 */ |
| #include <sys/filio.h> |
| #endif |
| |
| #ifdef HAVE_SETJMP_H |
| #include <setjmp.h> |
| #endif |
| |
| #ifdef HAVE_PWD_H |
| #include <pwd.h> |
| #endif |
| |
| #define ENABLE_CURLX_PRINTF |
| /* make the curlx header define all printf() functions to use the curlx_* |
| versions instead */ |
| #include "curlx.h" /* from the private lib dir */ |
| #include "getpart.h" |
| #include "util.h" |
| #include "server_sockaddr.h" |
| |
| /* include memdebug.h last */ |
| #include "memdebug.h" |
| |
| /***************************************************************************** |
| * STRUCT DECLARATIONS AND DEFINES * |
| *****************************************************************************/ |
| |
| #ifndef PKTSIZE |
| #define PKTSIZE (SEGSIZE + 4) /* SEGSIZE defined in arpa/tftp.h */ |
| #endif |
| |
| struct testcase { |
| char *buffer; /* holds the file data to send to the client */ |
| size_t bufsize; /* size of the data in buffer */ |
| char *rptr; /* read pointer into the buffer */ |
| size_t rcount; /* amount of data left to read of the file */ |
| long testno; /* test case number */ |
| int ofile; /* file descriptor for output file when uploading to us */ |
| |
| int writedelay; /* number of seconds between each packet */ |
| }; |
| |
| struct formats { |
| const char *f_mode; |
| int f_convert; |
| }; |
| |
| struct errmsg { |
| int e_code; |
| const char *e_msg; |
| }; |
| |
| typedef union { |
| struct tftphdr hdr; |
| char storage[PKTSIZE]; |
| } tftphdr_storage_t; |
| |
| /* |
| * bf.counter values in range [-1 .. SEGSIZE] represents size of data in the |
| * bf.buf buffer. Additionally it can also hold flags BF_ALLOC or BF_FREE. |
| */ |
| |
| struct bf { |
| int counter; /* size of data in buffer, or flag */ |
| tftphdr_storage_t buf; /* room for data packet */ |
| }; |
| |
| #define BF_ALLOC -3 /* alloc'd but not yet filled */ |
| #define BF_FREE -2 /* free */ |
| |
| #define opcode_RRQ 1 |
| #define opcode_WRQ 2 |
| #define opcode_DATA 3 |
| #define opcode_ACK 4 |
| #define opcode_ERROR 5 |
| |
| #define TIMEOUT 5 |
| |
| #undef MIN |
| #define MIN(x,y) ((x)<(y)?(x):(y)) |
| |
| #ifndef DEFAULT_LOGFILE |
| #define DEFAULT_LOGFILE "log/tftpd.log" |
| #endif |
| |
| #define REQUEST_DUMP "log/server.input" |
| |
| #define DEFAULT_PORT 8999 /* UDP */ |
| |
| /***************************************************************************** |
| * GLOBAL VARIABLES * |
| *****************************************************************************/ |
| |
| static struct errmsg 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 } |
| }; |
| |
| static struct formats formata[] = { |
| { "netascii", 1 }, |
| { "octet", 0 }, |
| { NULL, 0 } |
| }; |
| |
| static struct bf bfs[2]; |
| |
| static int nextone; /* index of next buffer to use */ |
| static int current; /* index of buffer in use */ |
| |
| /* control flags for crlf conversions */ |
| static int newline = 0; /* fillbuf: in middle of newline expansion */ |
| static int prevchar = -1; /* putbuf: previous char (cr check) */ |
| |
| static tftphdr_storage_t buf; |
| static tftphdr_storage_t ackbuf; |
| |
| static srvr_sockaddr_union_t from; |
| static curl_socklen_t fromlen; |
| |
| static curl_socket_t peer = CURL_SOCKET_BAD; |
| |
| static unsigned int timeout; |
| static unsigned int maxtimeout = 5 * TIMEOUT; |
| |
| #ifdef ENABLE_IPV6 |
| static bool use_ipv6 = FALSE; |
| #endif |
| static const char *ipv_inuse = "IPv4"; |
| |
| const char *serverlogfile = DEFAULT_LOGFILE; |
| static const char *pidname = ".tftpd.pid"; |
| static const char *portfile = NULL; |
| static int serverlogslocked = 0; |
| static int wrotepidfile = 0; |
| |
| #ifdef HAVE_SIGSETJMP |
| static sigjmp_buf timeoutbuf; |
| #endif |
| |
| #if defined(HAVE_ALARM) && defined(SIGALRM) |
| static const unsigned int rexmtval = TIMEOUT; |
| #endif |
| |
| /***************************************************************************** |
| * FUNCTION PROTOTYPES * |
| *****************************************************************************/ |
| |
| static struct tftphdr *rw_init(int); |
| |
| static struct tftphdr *w_init(void); |
| |
| static struct tftphdr *r_init(void); |
| |
| static void read_ahead(struct testcase *test, int convert); |
| |
| static ssize_t write_behind(struct testcase *test, int convert); |
| |
| static int synchnet(curl_socket_t); |
| |
| static int do_tftp(struct testcase *test, struct tftphdr *tp, ssize_t size); |
| |
| static int validate_access(struct testcase *test, const char *fname, int mode); |
| |
| static void sendtftp(struct testcase *test, struct formats *pf); |
| |
| static void recvtftp(struct testcase *test, struct formats *pf); |
| |
| static void nak(int error); |
| |
| #if defined(HAVE_ALARM) && defined(SIGALRM) |
| |
| static void mysignal(int sig, void (*handler)(int)); |
| |
| static void timer(int signum); |
| |
| static void justtimeout(int signum); |
| |
| #endif /* HAVE_ALARM && SIGALRM */ |
| |
| /***************************************************************************** |
| * FUNCTION IMPLEMENTATIONS * |
| *****************************************************************************/ |
| |
| #if defined(HAVE_ALARM) && defined(SIGALRM) |
| |
| /* |
| * Like signal(), but with well-defined semantics. |
| */ |
| static void mysignal(int sig, void (*handler)(int)) |
| { |
| struct sigaction sa; |
| memset(&sa, 0, sizeof(sa)); |
| sa.sa_handler = handler; |
| sigaction(sig, &sa, NULL); |
| } |
| |
| static void timer(int signum) |
| { |
| (void)signum; |
| |
| logmsg("alarm!"); |
| |
| timeout += rexmtval; |
| if(timeout >= maxtimeout) { |
| if(wrotepidfile) { |
| wrotepidfile = 0; |
| unlink(pidname); |
| } |
| if(serverlogslocked) { |
| serverlogslocked = 0; |
| clear_advisor_read_lock(SERVERLOGS_LOCK); |
| } |
| exit(1); |
| } |
| #ifdef HAVE_SIGSETJMP |
| siglongjmp(timeoutbuf, 1); |
| #endif |
| } |
| |
| static void justtimeout(int signum) |
| { |
| (void)signum; |
| } |
| |
| #endif /* HAVE_ALARM && SIGALRM */ |
| |
| /* |
| * init for either read-ahead or write-behind. |
| * zero for write-behind, one for read-head. |
| */ |
| static struct tftphdr *rw_init(int x) |
| { |
| newline = 0; /* init crlf flag */ |
| prevchar = -1; |
| bfs[0].counter = BF_ALLOC; /* pass out the first buffer */ |
| current = 0; |
| bfs[1].counter = BF_FREE; |
| nextone = x; /* ahead or behind? */ |
| return &bfs[0].buf.hdr; |
| } |
| |
| static struct tftphdr *w_init(void) |
| { |
| return rw_init(0); /* write-behind */ |
| } |
| |
| static struct tftphdr *r_init(void) |
| { |
| return rw_init(1); /* read-ahead */ |
| } |
| |
| /* Have emptied current buffer by sending to net and getting ack. |
| Free it and return next buffer filled with data. |
| */ |
| static int readit(struct testcase *test, struct tftphdr **dpp, |
| int convert /* if true, convert to ascii */) |
| { |
| struct bf *b; |
| |
| bfs[current].counter = BF_FREE; /* free old one */ |
| current = !current; /* "incr" current */ |
| |
| b = &bfs[current]; /* look at new buffer */ |
| if(b->counter == BF_FREE) /* if it's empty */ |
| read_ahead(test, convert); /* fill it */ |
| |
| *dpp = &b->buf.hdr; /* set caller's ptr */ |
| return b->counter; |
| } |
| |
| /* |
| * fill the input buffer, doing ascii conversions if requested |
| * conversions are lf -> cr, lf and cr -> cr, nul |
| */ |
| static void read_ahead(struct testcase *test, |
| int convert /* if true, convert to ascii */) |
| { |
| int i; |
| char *p; |
| int c; |
| struct bf *b; |
| struct tftphdr *dp; |
| |
| b = &bfs[nextone]; /* look at "next" buffer */ |
| if(b->counter != BF_FREE) /* nop if not free */ |
| return; |
| nextone = !nextone; /* "incr" next buffer ptr */ |
| |
| dp = &b->buf.hdr; |
| |
| if(convert == 0) { |
| /* The former file reading code did this: |
| b->counter = read(fileno(file), dp->th_data, SEGSIZE); */ |
| size_t copy_n = MIN(SEGSIZE, test->rcount); |
| memcpy(dp->th_data, test->rptr, copy_n); |
| |
| /* decrease amount, advance pointer */ |
| test->rcount -= copy_n; |
| test->rptr += copy_n; |
| b->counter = (int)copy_n; |
| return; |
| } |
| |
| p = dp->th_data; |
| for(i = 0 ; i < SEGSIZE; i++) { |
| if(newline) { |
| if(prevchar == '\n') |
| c = '\n'; /* lf to cr,lf */ |
| else |
| c = '\0'; /* cr to cr,nul */ |
| newline = 0; |
| } |
| else { |
| if(test->rcount) { |
| c = test->rptr[0]; |
| test->rptr++; |
| test->rcount--; |
| } |
| else |
| break; |
| if(c == '\n' || c == '\r') { |
| prevchar = c; |
| c = '\r'; |
| newline = 1; |
| } |
| } |
| *p++ = (char)c; |
| } |
| b->counter = (int)(p - dp->th_data); |
| } |
| |
| /* Update count associated with the buffer, get new buffer from the queue. |
| Calls write_behind only if next buffer not available. |
| */ |
| static int writeit(struct testcase *test, struct tftphdr * volatile *dpp, |
| int ct, int convert) |
| { |
| bfs[current].counter = ct; /* set size of data to write */ |
| current = !current; /* switch to other buffer */ |
| if(bfs[current].counter != BF_FREE) /* if not free */ |
| write_behind(test, convert); /* flush it */ |
| bfs[current].counter = BF_ALLOC; /* mark as alloc'd */ |
| *dpp = &bfs[current].buf.hdr; |
| return ct; /* this is a lie of course */ |
| } |
| |
| /* |
| * Output a buffer to a file, converting from netascii if requested. |
| * CR, NUL -> CR and CR, LF => LF. |
| * Note spec is undefined if we get CR as last byte of file or a |
| * CR followed by anything else. In this case we leave it alone. |
| */ |
| static ssize_t write_behind(struct testcase *test, int convert) |
| { |
| char *writebuf; |
| int count; |
| int ct; |
| char *p; |
| int c; /* current character */ |
| struct bf *b; |
| struct tftphdr *dp; |
| |
| b = &bfs[nextone]; |
| if(b->counter < -1) /* anything to flush? */ |
| return 0; /* just nop if nothing to do */ |
| |
| if(!test->ofile) { |
| char outfile[256]; |
| msnprintf(outfile, sizeof(outfile), "log/upload.%ld", test->testno); |
| #ifdef WIN32 |
| test->ofile = open(outfile, O_CREAT|O_RDWR|O_BINARY, 0777); |
| #else |
| test->ofile = open(outfile, O_CREAT|O_RDWR, 0777); |
| #endif |
| if(test->ofile == -1) { |
| logmsg("Couldn't create and/or open file %s for upload!", outfile); |
| return -1; /* failure! */ |
| } |
| } |
| |
| count = b->counter; /* remember byte count */ |
| b->counter = BF_FREE; /* reset flag */ |
| dp = &b->buf.hdr; |
| nextone = !nextone; /* incr for next time */ |
| writebuf = dp->th_data; |
| |
| if(count <= 0) |
| return -1; /* nak logic? */ |
| |
| if(convert == 0) |
| return write(test->ofile, writebuf, count); |
| |
| p = writebuf; |
| ct = count; |
| while(ct--) { /* loop over the buffer */ |
| c = *p++; /* pick up a character */ |
| if(prevchar == '\r') { /* if prev char was cr */ |
| if(c == '\n') /* if have cr,lf then just */ |
| lseek(test->ofile, -1, SEEK_CUR); /* smash lf on top of the cr */ |
| else |
| if(c == '\0') /* if have cr,nul then */ |
| goto skipit; /* just skip over the putc */ |
| /* else just fall through and allow it */ |
| } |
| /* formerly |
| putc(c, file); */ |
| if(1 != write(test->ofile, &c, 1)) |
| break; |
| skipit: |
| prevchar = c; |
| } |
| return count; |
| } |
| |
| /* When an error has occurred, it is possible that the two sides are out of |
| * synch. Ie: that what I think is the other side's response to packet N is |
| * really their response to packet N-1. |
| * |
| * So, to try to prevent that, we flush all the input queued up for us on the |
| * network connection on our host. |
| * |
| * We return the number of packets we flushed (mostly for reporting when trace |
| * is active). |
| */ |
| |
| static int synchnet(curl_socket_t f /* socket to flush */) |
| { |
| |
| #if defined(HAVE_IOCTLSOCKET) |
| unsigned long i; |
| #else |
| int i; |
| #endif |
| int j = 0; |
| char rbuf[PKTSIZE]; |
| srvr_sockaddr_union_t fromaddr; |
| curl_socklen_t fromaddrlen; |
| |
| for(;;) { |
| #if defined(HAVE_IOCTLSOCKET) |
| (void) ioctlsocket(f, FIONREAD, &i); |
| #else |
| (void) ioctl(f, FIONREAD, &i); |
| #endif |
| if(i) { |
| j++; |
| #ifdef ENABLE_IPV6 |
| if(!use_ipv6) |
| #endif |
| fromaddrlen = sizeof(fromaddr.sa4); |
| #ifdef ENABLE_IPV6 |
| else |
| fromaddrlen = sizeof(fromaddr.sa6); |
| #endif |
| (void) recvfrom(f, rbuf, sizeof(rbuf), 0, |
| &fromaddr.sa, &fromaddrlen); |
| } |
| else |
| break; |
| } |
| return j; |
| } |
| |
| int main(int argc, char **argv) |
| { |
| srvr_sockaddr_union_t me; |
| struct tftphdr *tp; |
| ssize_t n = 0; |
| int arg = 1; |
| unsigned short port = DEFAULT_PORT; |
| curl_socket_t sock = CURL_SOCKET_BAD; |
| int flag; |
| int rc; |
| int error; |
| long pid; |
| struct testcase test; |
| int result = 0; |
| |
| memset(&test, 0, sizeof(test)); |
| |
| while(argc>arg) { |
| if(!strcmp("--version", argv[arg])) { |
| printf("tftpd IPv4%s\n", |
| #ifdef ENABLE_IPV6 |
| "/IPv6" |
| #else |
| "" |
| #endif |
| ); |
| return 0; |
| } |
| else if(!strcmp("--pidfile", argv[arg])) { |
| arg++; |
| if(argc>arg) |
| pidname = argv[arg++]; |
| } |
| else if(!strcmp("--portfile", argv[arg])) { |
| arg++; |
| if(argc>arg) |
| portfile = argv[arg++]; |
| } |
| else if(!strcmp("--logfile", argv[arg])) { |
| arg++; |
| if(argc>arg) |
| serverlogfile = argv[arg++]; |
| } |
| else if(!strcmp("--ipv4", argv[arg])) { |
| #ifdef ENABLE_IPV6 |
| ipv_inuse = "IPv4"; |
| use_ipv6 = FALSE; |
| #endif |
| arg++; |
| } |
| else if(!strcmp("--ipv6", argv[arg])) { |
| #ifdef ENABLE_IPV6 |
| ipv_inuse = "IPv6"; |
| use_ipv6 = TRUE; |
| #endif |
| arg++; |
| } |
| else if(!strcmp("--port", argv[arg])) { |
| arg++; |
| if(argc>arg) { |
| char *endptr; |
| unsigned long ulnum = strtoul(argv[arg], &endptr, 10); |
| port = curlx_ultous(ulnum); |
| arg++; |
| } |
| } |
| else if(!strcmp("--srcdir", argv[arg])) { |
| arg++; |
| if(argc>arg) { |
| path = argv[arg]; |
| arg++; |
| } |
| } |
| else { |
| puts("Usage: tftpd [option]\n" |
| " --version\n" |
| " --logfile [file]\n" |
| " --pidfile [file]\n" |
| " --ipv4\n" |
| " --ipv6\n" |
| " --port [port]\n" |
| " --srcdir [path]"); |
| return 0; |
| } |
| } |
| |
| #ifdef WIN32 |
| win32_init(); |
| atexit(win32_cleanup); |
| #endif |
| |
| install_signal_handlers(true); |
| |
| pid = (long)getpid(); |
| |
| #ifdef ENABLE_IPV6 |
| if(!use_ipv6) |
| #endif |
| sock = socket(AF_INET, SOCK_DGRAM, 0); |
| #ifdef ENABLE_IPV6 |
| else |
| sock = socket(AF_INET6, SOCK_DGRAM, 0); |
| #endif |
| |
| if(CURL_SOCKET_BAD == sock) { |
| error = SOCKERRNO; |
| logmsg("Error creating socket: (%d) %s", |
| error, strerror(error)); |
| result = 1; |
| goto tftpd_cleanup; |
| } |
| |
| flag = 1; |
| if(0 != setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, |
| (void *)&flag, sizeof(flag))) { |
| error = SOCKERRNO; |
| logmsg("setsockopt(SO_REUSEADDR) failed with error: (%d) %s", |
| error, strerror(error)); |
| result = 1; |
| goto tftpd_cleanup; |
| } |
| |
| #ifdef ENABLE_IPV6 |
| if(!use_ipv6) { |
| #endif |
| memset(&me.sa4, 0, sizeof(me.sa4)); |
| me.sa4.sin_family = AF_INET; |
| me.sa4.sin_addr.s_addr = INADDR_ANY; |
| me.sa4.sin_port = htons(port); |
| rc = bind(sock, &me.sa, sizeof(me.sa4)); |
| #ifdef ENABLE_IPV6 |
| } |
| else { |
| memset(&me.sa6, 0, sizeof(me.sa6)); |
| me.sa6.sin6_family = AF_INET6; |
| me.sa6.sin6_addr = in6addr_any; |
| me.sa6.sin6_port = htons(port); |
| rc = bind(sock, &me.sa, sizeof(me.sa6)); |
| } |
| #endif /* ENABLE_IPV6 */ |
| if(0 != rc) { |
| error = SOCKERRNO; |
| logmsg("Error binding socket on port %hu: (%d) %s", |
| port, error, strerror(error)); |
| result = 1; |
| goto tftpd_cleanup; |
| } |
| |
| if(!port) { |
| /* The system was supposed to choose a port number, figure out which |
| port we actually got and update the listener port value with it. */ |
| curl_socklen_t la_size; |
| srvr_sockaddr_union_t localaddr; |
| #ifdef ENABLE_IPV6 |
| if(!use_ipv6) |
| #endif |
| la_size = sizeof(localaddr.sa4); |
| #ifdef ENABLE_IPV6 |
| else |
| la_size = sizeof(localaddr.sa6); |
| #endif |
| memset(&localaddr.sa, 0, (size_t)la_size); |
| if(getsockname(sock, &localaddr.sa, &la_size) < 0) { |
| error = SOCKERRNO; |
| logmsg("getsockname() failed with error: (%d) %s", |
| error, strerror(error)); |
| sclose(sock); |
| goto tftpd_cleanup; |
| } |
| switch(localaddr.sa.sa_family) { |
| case AF_INET: |
| port = ntohs(localaddr.sa4.sin_port); |
| break; |
| #ifdef ENABLE_IPV6 |
| case AF_INET6: |
| port = ntohs(localaddr.sa6.sin6_port); |
| break; |
| #endif |
| default: |
| break; |
| } |
| if(!port) { |
| /* Real failure, listener port shall not be zero beyond this point. */ |
| logmsg("Apparently getsockname() succeeded, with listener port zero."); |
| logmsg("A valid reason for this failure is a binary built without"); |
| logmsg("proper network library linkage. This might not be the only"); |
| logmsg("reason, but double check it before anything else."); |
| result = 2; |
| goto tftpd_cleanup; |
| } |
| } |
| |
| wrotepidfile = write_pidfile(pidname); |
| if(!wrotepidfile) { |
| result = 1; |
| goto tftpd_cleanup; |
| } |
| |
| if(portfile) { |
| wrotepidfile = write_portfile(portfile, port); |
| if(!wrotepidfile) { |
| result = 1; |
| goto tftpd_cleanup; |
| } |
| } |
| |
| logmsg("Running %s version on port UDP/%d", ipv_inuse, (int)port); |
| |
| for(;;) { |
| fromlen = sizeof(from); |
| #ifdef ENABLE_IPV6 |
| if(!use_ipv6) |
| #endif |
| fromlen = sizeof(from.sa4); |
| #ifdef ENABLE_IPV6 |
| else |
| fromlen = sizeof(from.sa6); |
| #endif |
| n = (ssize_t)recvfrom(sock, &buf.storage[0], sizeof(buf.storage), 0, |
| &from.sa, &fromlen); |
| if(got_exit_signal) |
| break; |
| if(n < 0) { |
| logmsg("recvfrom"); |
| result = 3; |
| break; |
| } |
| |
| set_advisor_read_lock(SERVERLOGS_LOCK); |
| serverlogslocked = 1; |
| |
| #ifdef ENABLE_IPV6 |
| if(!use_ipv6) { |
| #endif |
| from.sa4.sin_family = AF_INET; |
| peer = socket(AF_INET, SOCK_DGRAM, 0); |
| if(CURL_SOCKET_BAD == peer) { |
| logmsg("socket"); |
| result = 2; |
| break; |
| } |
| if(connect(peer, &from.sa, sizeof(from.sa4)) < 0) { |
| logmsg("connect: fail"); |
| result = 1; |
| break; |
| } |
| #ifdef ENABLE_IPV6 |
| } |
| else { |
| from.sa6.sin6_family = AF_INET6; |
| peer = socket(AF_INET6, SOCK_DGRAM, 0); |
| if(CURL_SOCKET_BAD == peer) { |
| logmsg("socket"); |
| result = 2; |
| break; |
| } |
| if(connect(peer, &from.sa, sizeof(from.sa6)) < 0) { |
| logmsg("connect: fail"); |
| result = 1; |
| break; |
| } |
| } |
| #endif |
| |
| maxtimeout = 5*TIMEOUT; |
| |
| tp = &buf.hdr; |
| tp->th_opcode = ntohs(tp->th_opcode); |
| if(tp->th_opcode == opcode_RRQ || tp->th_opcode == opcode_WRQ) { |
| memset(&test, 0, sizeof(test)); |
| if(do_tftp(&test, tp, n) < 0) |
| break; |
| free(test.buffer); |
| } |
| sclose(peer); |
| peer = CURL_SOCKET_BAD; |
| |
| if(got_exit_signal) |
| break; |
| |
| if(serverlogslocked) { |
| serverlogslocked = 0; |
| clear_advisor_read_lock(SERVERLOGS_LOCK); |
| } |
| |
| logmsg("end of one transfer"); |
| |
| } |
| |
| tftpd_cleanup: |
| |
| if(test.ofile > 0) |
| close(test.ofile); |
| |
| if((peer != sock) && (peer != CURL_SOCKET_BAD)) |
| sclose(peer); |
| |
| if(sock != CURL_SOCKET_BAD) |
| sclose(sock); |
| |
| if(got_exit_signal) |
| logmsg("signalled to die"); |
| |
| if(wrotepidfile) |
| unlink(pidname); |
| if(portfile) |
| unlink(portfile); |
| |
| if(serverlogslocked) { |
| serverlogslocked = 0; |
| clear_advisor_read_lock(SERVERLOGS_LOCK); |
| } |
| |
| restore_signal_handlers(true); |
| |
| if(got_exit_signal) { |
| logmsg("========> %s tftpd (port: %d pid: %ld) exits with signal (%d)", |
| ipv_inuse, (int)port, pid, exit_signal); |
| /* |
| * To properly set the return status of the process we |
| * must raise the same signal SIGINT or SIGTERM that we |
| * caught and let the old handler take care of it. |
| */ |
| raise(exit_signal); |
| } |
| |
| logmsg("========> tftpd quits"); |
| return result; |
| } |
| |
| /* |
| * Handle initial connection protocol. |
| */ |
| static int do_tftp(struct testcase *test, struct tftphdr *tp, ssize_t size) |
| { |
| char *cp; |
| int first = 1, ecode; |
| struct formats *pf; |
| char *filename, *mode = NULL; |
| #ifdef USE_WINSOCK |
| DWORD recvtimeout, recvtimeoutbak; |
| #endif |
| const char *option = "mode"; /* mode is implicit */ |
| int toggle = 1; |
| |
| /* Open request dump file. */ |
| FILE *server = fopen(REQUEST_DUMP, "ab"); |
| if(!server) { |
| int error = errno; |
| logmsg("fopen() failed with error: %d %s", error, strerror(error)); |
| logmsg("Error opening file: %s", REQUEST_DUMP); |
| return -1; |
| } |
| |
| /* store input protocol */ |
| fprintf(server, "opcode: %x\n", tp->th_opcode); |
| |
| cp = (char *)&tp->th_stuff; |
| filename = cp; |
| do { |
| bool endofit = true; |
| while(cp < &buf.storage[size]) { |
| if(*cp == '\0') { |
| endofit = false; |
| break; |
| } |
| cp++; |
| } |
| if(endofit) |
| /* no more options */ |
| break; |
| |
| /* before increasing pointer, make sure it is still within the legal |
| space */ |
| if((cp + 1) < &buf.storage[size]) { |
| ++cp; |
| if(first) { |
| /* store the mode since we need it later */ |
| mode = cp; |
| first = 0; |
| } |
| if(toggle) |
| /* name/value pair: */ |
| fprintf(server, "%s: %s\n", option, cp); |
| else { |
| /* store the name pointer */ |
| option = cp; |
| } |
| toggle ^= 1; |
| } |
| else |
| /* No more options */ |
| break; |
| } while(1); |
| |
| if(*cp) { |
| nak(EBADOP); |
| fclose(server); |
| return 3; |
| } |
| |
| /* store input protocol */ |
| fprintf(server, "filename: %s\n", filename); |
| |
| for(cp = mode; cp && *cp; cp++) |
| if(ISUPPER(*cp)) |
| *cp = (char)tolower((int)*cp); |
| |
| /* store input protocol */ |
| fclose(server); |
| |
| for(pf = formata; pf->f_mode; pf++) |
| if(strcmp(pf->f_mode, mode) == 0) |
| break; |
| if(!pf->f_mode) { |
| nak(EBADOP); |
| return 2; |
| } |
| ecode = validate_access(test, filename, tp->th_opcode); |
| if(ecode) { |
| nak(ecode); |
| return 1; |
| } |
| |
| #ifdef USE_WINSOCK |
| recvtimeout = sizeof(recvtimeoutbak); |
| getsockopt(peer, SOL_SOCKET, SO_RCVTIMEO, |
| (char *)&recvtimeoutbak, (int *)&recvtimeout); |
| recvtimeout = TIMEOUT*1000; |
| setsockopt(peer, SOL_SOCKET, SO_RCVTIMEO, |
| (const char *)&recvtimeout, sizeof(recvtimeout)); |
| #endif |
| |
| if(tp->th_opcode == opcode_WRQ) |
| recvtftp(test, pf); |
| else |
| sendtftp(test, pf); |
| |
| #ifdef USE_WINSOCK |
| recvtimeout = recvtimeoutbak; |
| setsockopt(peer, SOL_SOCKET, SO_RCVTIMEO, |
| (const char *)&recvtimeout, sizeof(recvtimeout)); |
| #endif |
| |
| return 0; |
| } |
| |
| /* Based on the testno, parse the correct server commands. */ |
| static int parse_servercmd(struct testcase *req) |
| { |
| FILE *stream; |
| int error; |
| |
| stream = test2fopen(req->testno); |
| if(!stream) { |
| error = errno; |
| logmsg("fopen() failed with error: %d %s", error, strerror(error)); |
| logmsg(" Couldn't open test file %ld", req->testno); |
| return 1; /* done */ |
| } |
| else { |
| char *orgcmd = NULL; |
| char *cmd = NULL; |
| size_t cmdsize = 0; |
| int num = 0; |
| |
| /* get the custom server control "commands" */ |
| error = getpart(&orgcmd, &cmdsize, "reply", "servercmd", stream); |
| fclose(stream); |
| if(error) { |
| logmsg("getpart() failed with error: %d", error); |
| return 1; /* done */ |
| } |
| |
| cmd = orgcmd; |
| while(cmd && cmdsize) { |
| char *check; |
| if(1 == sscanf(cmd, "writedelay: %d", &num)) { |
| logmsg("instructed to delay %d secs between packets", num); |
| req->writedelay = num; |
| } |
| else { |
| logmsg("Unknown <servercmd> instruction found: %s", cmd); |
| } |
| /* try to deal with CRLF or just LF */ |
| check = strchr(cmd, '\r'); |
| if(!check) |
| check = strchr(cmd, '\n'); |
| |
| if(check) { |
| /* get to the letter following the newline */ |
| while((*check == '\r') || (*check == '\n')) |
| check++; |
| |
| if(!*check) |
| /* if we reached a zero, get out */ |
| break; |
| cmd = check; |
| } |
| else |
| break; |
| } |
| free(orgcmd); |
| } |
| |
| return 0; /* OK! */ |
| } |
| |
| |
| /* |
| * Validate file access. |
| */ |
| static int validate_access(struct testcase *test, |
| const char *filename, int mode) |
| { |
| char *ptr; |
| |
| logmsg("trying to get file: %s mode %x", filename, mode); |
| |
| if(!strncmp("verifiedserver", filename, 14)) { |
| char weare[128]; |
| size_t count = msnprintf(weare, sizeof(weare), |
| "WE ROOLZ: %ld\r\n", (long)getpid()); |
| |
| logmsg("Are-we-friendly question received"); |
| test->buffer = strdup(weare); |
| test->rptr = test->buffer; /* set read pointer */ |
| test->bufsize = count; /* set total count */ |
| test->rcount = count; /* set data left to read */ |
| return 0; /* fine */ |
| } |
| |
| /* find the last slash */ |
| ptr = strrchr(filename, '/'); |
| |
| if(ptr) { |
| char partbuf[80]="data"; |
| long partno; |
| long testno; |
| FILE *stream; |
| |
| ptr++; /* skip the slash */ |
| |
| /* skip all non-numericals following the slash */ |
| while(*ptr && !ISDIGIT(*ptr)) |
| ptr++; |
| |
| /* get the number */ |
| testno = strtol(ptr, &ptr, 10); |
| |
| if(testno > 10000) { |
| partno = testno % 10000; |
| testno /= 10000; |
| } |
| else |
| partno = 0; |
| |
| |
| logmsg("requested test number %ld part %ld", testno, partno); |
| |
| test->testno = testno; |
| |
| (void)parse_servercmd(test); |
| |
| stream = test2fopen(testno); |
| |
| if(0 != partno) |
| msnprintf(partbuf, sizeof(partbuf), "data%ld", partno); |
| |
| if(!stream) { |
| int error = errno; |
| logmsg("fopen() failed with error: %d %s", error, strerror(error)); |
| logmsg("Couldn't open test file for test : %d", testno); |
| return EACCESS; |
| } |
| else { |
| size_t count; |
| int error = getpart(&test->buffer, &count, "reply", partbuf, stream); |
| fclose(stream); |
| if(error) { |
| logmsg("getpart() failed with error: %d", error); |
| return EACCESS; |
| } |
| if(test->buffer) { |
| test->rptr = test->buffer; /* set read pointer */ |
| test->bufsize = count; /* set total count */ |
| test->rcount = count; /* set data left to read */ |
| } |
| else |
| return EACCESS; |
| } |
| } |
| else { |
| logmsg("no slash found in path"); |
| return EACCESS; /* failure */ |
| } |
| |
| logmsg("file opened and all is good"); |
| return 0; |
| } |
| |
| /* |
| * Send the requested file. |
| */ |
| static void sendtftp(struct testcase *test, struct formats *pf) |
| { |
| int size; |
| ssize_t n; |
| /* These are volatile to live through a siglongjmp */ |
| volatile unsigned short sendblock; /* block count */ |
| struct tftphdr * volatile sdp = r_init(); /* data buffer */ |
| struct tftphdr * const sap = &ackbuf.hdr; /* ack buffer */ |
| |
| sendblock = 1; |
| #if defined(HAVE_ALARM) && defined(SIGALRM) |
| mysignal(SIGALRM, timer); |
| #endif |
| do { |
| size = readit(test, (struct tftphdr **)&sdp, pf->f_convert); |
| if(size < 0) { |
| nak(errno + 100); |
| return; |
| } |
| sdp->th_opcode = htons((unsigned short)opcode_DATA); |
| sdp->th_block = htons(sendblock); |
| timeout = 0; |
| #ifdef HAVE_SIGSETJMP |
| (void) sigsetjmp(timeoutbuf, 1); |
| #endif |
| if(test->writedelay) { |
| logmsg("Pausing %d seconds before %d bytes", test->writedelay, |
| size); |
| wait_ms(1000*test->writedelay); |
| } |
| |
| send_data: |
| logmsg("write"); |
| if(swrite(peer, sdp, size + 4) != size + 4) { |
| logmsg("write: fail"); |
| return; |
| } |
| read_ahead(test, pf->f_convert); |
| for(;;) { |
| #ifdef HAVE_ALARM |
| alarm(rexmtval); /* read the ack */ |
| #endif |
| logmsg("read"); |
| n = sread(peer, &ackbuf.storage[0], sizeof(ackbuf.storage)); |
| logmsg("read: %zd", n); |
| #ifdef HAVE_ALARM |
| alarm(0); |
| #endif |
| if(got_exit_signal) |
| return; |
| if(n < 0) { |
| logmsg("read: fail"); |
| return; |
| } |
| sap->th_opcode = ntohs((unsigned short)sap->th_opcode); |
| sap->th_block = ntohs(sap->th_block); |
| |
| if(sap->th_opcode == opcode_ERROR) { |
| logmsg("got ERROR"); |
| return; |
| } |
| |
| if(sap->th_opcode == opcode_ACK) { |
| if(sap->th_block == sendblock) { |
| break; |
| } |
| /* Re-synchronize with the other side */ |
| (void) synchnet(peer); |
| if(sap->th_block == (sendblock-1)) { |
| goto send_data; |
| } |
| } |
| |
| } |
| sendblock++; |
| } while(size == SEGSIZE); |
| } |
| |
| /* |
| * Receive a file. |
| */ |
| static void recvtftp(struct testcase *test, struct formats *pf) |
| { |
| ssize_t n, size; |
| /* These are volatile to live through a siglongjmp */ |
| volatile unsigned short recvblock; /* block count */ |
| struct tftphdr * volatile rdp; /* data buffer */ |
| struct tftphdr *rap; /* ack buffer */ |
| |
| recvblock = 0; |
| rdp = w_init(); |
| #if defined(HAVE_ALARM) && defined(SIGALRM) |
| mysignal(SIGALRM, timer); |
| #endif |
| rap = &ackbuf.hdr; |
| do { |
| timeout = 0; |
| rap->th_opcode = htons((unsigned short)opcode_ACK); |
| rap->th_block = htons(recvblock); |
| recvblock++; |
| #ifdef HAVE_SIGSETJMP |
| (void) sigsetjmp(timeoutbuf, 1); |
| #endif |
| send_ack: |
| logmsg("write"); |
| if(swrite(peer, &ackbuf.storage[0], 4) != 4) { |
| logmsg("write: fail"); |
| goto abort; |
| } |
| write_behind(test, pf->f_convert); |
| for(;;) { |
| #ifdef HAVE_ALARM |
| alarm(rexmtval); |
| #endif |
| logmsg("read"); |
| n = sread(peer, rdp, PKTSIZE); |
| logmsg("read: %zd", n); |
| #ifdef HAVE_ALARM |
| alarm(0); |
| #endif |
| if(got_exit_signal) |
| goto abort; |
| if(n < 0) { /* really? */ |
| logmsg("read: fail"); |
| goto abort; |
| } |
| rdp->th_opcode = ntohs((unsigned short)rdp->th_opcode); |
| rdp->th_block = ntohs(rdp->th_block); |
| if(rdp->th_opcode == opcode_ERROR) |
| goto abort; |
| if(rdp->th_opcode == opcode_DATA) { |
| if(rdp->th_block == recvblock) { |
| break; /* normal */ |
| } |
| /* Re-synchronize with the other side */ |
| (void) synchnet(peer); |
| if(rdp->th_block == (recvblock-1)) |
| goto send_ack; /* rexmit */ |
| } |
| } |
| |
| size = writeit(test, &rdp, (int)(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(test, pf->f_convert); |
| /* close the output file as early as possible after upload completion */ |
| if(test->ofile > 0) { |
| close(test->ofile); |
| test->ofile = 0; |
| } |
| |
| rap->th_opcode = htons((unsigned short)opcode_ACK); /* send the "final" |
| ack */ |
| rap->th_block = htons(recvblock); |
| (void) swrite(peer, &ackbuf.storage[0], 4); |
| #if defined(HAVE_ALARM) && defined(SIGALRM) |
| mysignal(SIGALRM, justtimeout); /* just abort read on timeout */ |
| alarm(rexmtval); |
| #endif |
| /* normally times out and quits */ |
| n = sread(peer, &buf.storage[0], sizeof(buf.storage)); |
| #ifdef HAVE_ALARM |
| alarm(0); |
| #endif |
| if(got_exit_signal) |
| goto abort; |
| if(n >= 4 && /* if read some data */ |
| rdp->th_opcode == opcode_DATA && /* and got a data block */ |
| recvblock == rdp->th_block) { /* then my last ack was lost */ |
| (void) swrite(peer, &ackbuf.storage[0], 4); /* resend final ack */ |
| } |
| abort: |
| /* make sure the output file is closed in case of abort */ |
| if(test->ofile > 0) { |
| close(test->ofile); |
| test->ofile = 0; |
| } |
| return; |
| } |
| |
| /* |
| * Send a nak packet (error message). Error code passed in is one of the |
| * standard TFTP codes, or a Unix errno offset by 100. |
| */ |
| static void nak(int error) |
| { |
| struct tftphdr *tp; |
| int length; |
| struct errmsg *pe; |
| |
| tp = &buf.hdr; |
| tp->th_opcode = htons((unsigned short)opcode_ERROR); |
| tp->th_code = htons((unsigned short)error); |
| for(pe = errmsgs; pe->e_code >= 0; pe++) |
| if(pe->e_code == error) |
| break; |
| if(pe->e_code < 0) { |
| pe->e_msg = strerror(error - 100); |
| tp->th_code = EUNDEF; /* set 'undef' errorcode */ |
| } |
| length = (int)strlen(pe->e_msg); |
| |
| /* we use memcpy() instead of strcpy() in order to avoid buffer overflow |
| * report from glibc with FORTIFY_SOURCE */ |
| memcpy(tp->th_msg, pe->e_msg, length + 1); |
| length += 5; |
| if(swrite(peer, &buf.storage[0], length) != length) |
| logmsg("nak: fail\n"); |
| } |