Make it possible to build curl for Windows CE using the CeGCC toolchain.
With both CMake and autotools, including tests and examples, also in CI.
The build configuration is the default one with Schannel enabled. No
3rd-party dependencies have been tested.
Also revive old code to make Schannel build with Windows CE, including
certificate verification.
Builds have been throughougly tested. But, I've made no functional tests
for this PR. Some parts (esp. file operations, like truncate and seek)
are stubbed out and likely broken as a result. Test servers build, but
they do not work on Windows CE. This patch substitutes `fstat()` calls
with `stat()`, which operate on filenames, not file handles. This may or
may not work and/or may not be secure.
About CeGCC: I used the latest available macOS binary build v0.59.1
r1397 from 2009, in native `mingw32ce` build mode. CeGCC is in effect
MinGW + GCC 4.4.0 + old/classic-mingw Windows headers. It targets
Windows CE v3.0 according to its `_WIN32_WCE` value. It means this PR
restores portions of old/classic-mingw support. It makes the Windows CE
codepath compatible with GCC 4.4.0. It also adds workaround for CMake,
which cannot identify and configure this toolchain out of the box.
Notes:
- CMake doesn't recognize CeGCC/mingw32ce, necessitating tricks as seen
with Amiga and MS-DOS.
- CMake doesn't set `MINGW` for mingw32ce. Set it and `MINGW32CE`
manually as a helper variable, in addition to `WINCE` which CMake sets
based on `CMAKE_SYSTEM_NAME`.
- CMake fails to create an implib for `libcurl.dll`, due to not
recognizing the platform as a Windowsy one. This patch adds the
necessary workaround to make it work.
- headers shipping with CeGCC miss some things curl needs for Schannel
support. Fixed by restoring and renovating code previously deleted
old-mingw code.
- it's sometime non-trivial to figure out if a fallout is WinCE,
mingw32ce, old-mingw, or GCC version-specific.
- WinCE is always Unicode. With exceptions: no `wmain`,
`GetProcAddress()`.
- `_fileno()` is said to convert from `FILE *` to `void *` which is
a Win32 file `HANDLE`. (This patch doesn't use this, but with further
effort it probably could be.)
https://stackoverflow.com/questions/3989545/how-do-i-get-the-file-handle-from-the-fopen-file-structure
- WinCE has no signals, current directory, stdio/CRT file handles, no
`_get_osfhandle()`, no `errno`, no `errno.h`. Some of this stuff is
standard C89, yet missing from this platform. Microsoft expects
Windows CE apps to use Win32 file API and `FILE *` exclusively.
- revived CeGCC here (not tested for this PR):
https://building.enlyze.com/posts/a-new-windows-ce-x86-compiler-in-2024/
On `UNDER_CE` vs. `_WIN32_WCE`: (This patch settled on `UNDER_CE`)
- A custom VS2008 WinCE toolchain does not set any of these.
The compiler binaries don't contain these strings, and has no compiler
option for targeting WinCE, hinting that a vanilla toolchain isn't
setting any of them either.
- `UNDER_CE` is automatically defined by the CeGCC compiler.
https://cegcc.sourceforge.net/docs/details.html
- `UNDER_CE` is similar to `_WIN32`, except it's not set automatically
by all compilers. It's not supposed to have any value, like a version.
(Though e.g. OpenSSL sets it to a version)
- `_WIN32_WCE` is the CE counterpart of the non-CE `_WIN32_WINNT` macro.
That does return the targeted Windows CE version.
- `_WIN32_WCE` is not defined by compilers, and relies on a header
setting it to a default, or the build to set it to the desired target
version. This is also how `_WIN32_WINNT` works.
- `_WIN32_WCE` default is set by `windef.h` in CeGCC.
- `_WIN32_WCE` isn't set to a default by MSVC Windows CE headers (the
ones I checked at least).
- CMake sets `_WIN32_WCE=<ver>`, `UNDER_CE`, `WINCE` for MSVC WinCE.
- `_WIN32_WCE` seems more popular in other projects, including CeGCC
itself. `zlib` is a notable exception amongst curl dependencies,
which uses `UNDER_CE`.
- Since `_WIN32_WCE` needs "certain" headers to have it defined, it's
undefined depending on headers included beforehand.
- `curl/curl.h` re-uses `_WIN32_WCE`'s as a self-guard, relying on
its not-(necessarily)-defined-by-default property:
25b445e479/include/curl/curl.h (L77)
Toolchain downloads:
- Windows:
https://downloads.sourceforge.net/cegcc/cegcc/0.59.1/cegcc_mingw32ce_cygwin1.7_r1399.tar.bz2
- macOS Intel:
https://downloads.sourceforge.net/cegcc/cegcc/0.59.1/cegcc_mingw32ce_snowleopard_r1397.tar.bz2
Closes #15975
1379 lines
35 KiB
C
1379 lines
35 KiB
C
/***************************************************************************
|
|
* _ _ ____ _
|
|
* 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) Daniel Stenberg, <daniel@haxx.se>, 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.
|
|
*
|
|
* SPDX-License-Identifier: BSD-4-Clause-UC
|
|
*/
|
|
|
|
#include "server_setup.h"
|
|
|
|
#ifdef HAVE_SYS_IOCTL_H
|
|
#include <sys/ioctl.h>
|
|
#endif
|
|
#ifndef UNDER_CE
|
|
#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_NETDB_H
|
|
#include <netdb.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_FILIO_H
|
|
/* FIONREAD on Solaris 7 */
|
|
#include <sys/filio.h>
|
|
#endif
|
|
|
|
#include <setjmp.h>
|
|
|
|
#ifdef HAVE_PWD_H
|
|
#include <pwd.h>
|
|
#endif
|
|
|
|
#include <ctype.h>
|
|
|
|
#include "curlx.h" /* from the private lib dir */
|
|
#include "getpart.h"
|
|
#include "util.h"
|
|
#include "server_sockaddr.h"
|
|
#include "tftp.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 "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 const 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 USE_IPV6
|
|
static bool use_ipv6 = FALSE;
|
|
#endif
|
|
static const char *ipv_inuse = "IPv4";
|
|
|
|
const char *serverlogfile = DEFAULT_LOGFILE;
|
|
static const char *logdir = "log";
|
|
static char loglockfile[256];
|
|
static const char *pidname = ".tftpd.pid";
|
|
static const char *portname = NULL; /* none by default */
|
|
static int serverlogslocked = 0;
|
|
static int wrotepidfile = 0;
|
|
static int wroteportfile = 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 *filename, unsigned short mode);
|
|
|
|
static void sendtftp(struct testcase *test, const struct formats *pf);
|
|
|
|
static void recvtftp(struct testcase *test, const 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);
|
|
}
|
|
|
|
#ifdef HAVE_SIGSETJMP
|
|
CURL_NORETURN
|
|
#endif
|
|
static void timer(int signum)
|
|
{
|
|
(void)signum;
|
|
|
|
logmsg("alarm!");
|
|
|
|
timeout += rexmtval;
|
|
if(timeout >= maxtimeout) {
|
|
if(wrotepidfile) {
|
|
wrotepidfile = 0;
|
|
unlink(pidname);
|
|
}
|
|
if(wroteportfile) {
|
|
wroteportfile = 0;
|
|
unlink(portname);
|
|
}
|
|
if(serverlogslocked) {
|
|
serverlogslocked = 0;
|
|
clear_advisor_read_lock(loglockfile);
|
|
}
|
|
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), "%s/upload.%ld", logdir, test->testno);
|
|
test->ofile = open(outfile, O_CREAT|O_RDWR|CURL_O_BINARY, 0777);
|
|
if(test->ofile == -1) {
|
|
logmsg("Couldn't create and/or open file %s for upload!", outfile);
|
|
return -1; /* failure! */
|
|
}
|
|
}
|
|
else if(test->ofile <= 0) {
|
|
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 */)
|
|
{
|
|
int j = 0;
|
|
char rbuf[PKTSIZE];
|
|
srvr_sockaddr_union_t fromaddr;
|
|
curl_socklen_t fromaddrlen;
|
|
|
|
for(;;) {
|
|
#if defined(HAVE_IOCTLSOCKET_CAMEL_FIONBIO)
|
|
long i;
|
|
(void) IoctlSocket(f, FIONBIO, &i);
|
|
#elif defined(HAVE_IOCTLSOCKET)
|
|
unsigned long i;
|
|
(void) ioctlsocket(f, FIONREAD, &i);
|
|
#else
|
|
int i;
|
|
(void) ioctl(f, FIONREAD, &i);
|
|
#endif
|
|
if(i) {
|
|
j++;
|
|
#ifdef USE_IPV6
|
|
if(!use_ipv6)
|
|
#endif
|
|
fromaddrlen = sizeof(fromaddr.sa4);
|
|
#ifdef USE_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;
|
|
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 USE_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)
|
|
portname = argv[arg++];
|
|
}
|
|
else if(!strcmp("--logfile", argv[arg])) {
|
|
arg++;
|
|
if(argc > arg)
|
|
serverlogfile = argv[arg++];
|
|
}
|
|
else if(!strcmp("--logdir", argv[arg])) {
|
|
arg++;
|
|
if(argc > arg)
|
|
logdir = argv[arg++];
|
|
}
|
|
else if(!strcmp("--ipv4", argv[arg])) {
|
|
#ifdef USE_IPV6
|
|
ipv_inuse = "IPv4";
|
|
use_ipv6 = FALSE;
|
|
#endif
|
|
arg++;
|
|
}
|
|
else if(!strcmp("--ipv6", argv[arg])) {
|
|
#ifdef USE_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 = util_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"
|
|
" --logdir [directory]\n"
|
|
" --pidfile [file]\n"
|
|
" --portfile [file]\n"
|
|
" --ipv4\n"
|
|
" --ipv6\n"
|
|
" --port [port]\n"
|
|
" --srcdir [path]");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
msnprintf(loglockfile, sizeof(loglockfile), "%s/%s/tftp-%s.lock",
|
|
logdir, SERVERLOGS_LOCKDIR, ipv_inuse);
|
|
|
|
#ifdef _WIN32
|
|
win32_init();
|
|
atexit(win32_cleanup);
|
|
#endif
|
|
|
|
install_signal_handlers(true);
|
|
|
|
#ifdef USE_IPV6
|
|
if(!use_ipv6)
|
|
#endif
|
|
sock = socket(AF_INET, SOCK_DGRAM, 0);
|
|
#ifdef USE_IPV6
|
|
else
|
|
sock = socket(AF_INET6, SOCK_DGRAM, 0);
|
|
#endif
|
|
|
|
if(CURL_SOCKET_BAD == sock) {
|
|
error = SOCKERRNO;
|
|
logmsg("Error creating socket: (%d) %s", error, sstrerror(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, sstrerror(error));
|
|
result = 1;
|
|
goto tftpd_cleanup;
|
|
}
|
|
|
|
#ifdef USE_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 USE_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 /* USE_IPV6 */
|
|
if(0 != rc) {
|
|
error = SOCKERRNO;
|
|
logmsg("Error binding socket on port %hu: (%d) %s", port, error,
|
|
sstrerror(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 USE_IPV6
|
|
if(!use_ipv6)
|
|
#endif
|
|
la_size = sizeof(localaddr.sa4);
|
|
#ifdef USE_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, sstrerror(error));
|
|
sclose(sock);
|
|
goto tftpd_cleanup;
|
|
}
|
|
switch(localaddr.sa.sa_family) {
|
|
case AF_INET:
|
|
port = ntohs(localaddr.sa4.sin_port);
|
|
break;
|
|
#ifdef USE_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(portname) {
|
|
wroteportfile = write_portfile(portname, port);
|
|
if(!wroteportfile) {
|
|
result = 1;
|
|
goto tftpd_cleanup;
|
|
}
|
|
}
|
|
|
|
logmsg("Running %s version on port UDP/%d", ipv_inuse, (int)port);
|
|
|
|
for(;;) {
|
|
fromlen = sizeof(from);
|
|
#ifdef USE_IPV6
|
|
if(!use_ipv6)
|
|
#endif
|
|
fromlen = sizeof(from.sa4);
|
|
#ifdef USE_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(loglockfile);
|
|
serverlogslocked = 1;
|
|
|
|
#ifdef USE_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 USE_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(loglockfile);
|
|
}
|
|
|
|
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(wroteportfile)
|
|
unlink(portname);
|
|
|
|
if(serverlogslocked) {
|
|
serverlogslocked = 0;
|
|
clear_advisor_read_lock(loglockfile);
|
|
}
|
|
|
|
restore_signal_handlers(true);
|
|
|
|
if(got_exit_signal) {
|
|
logmsg("========> %s tftpd (port: %d pid: %ld) exits with signal (%d)",
|
|
ipv_inuse, (int)port, (long)Curl_getpid(), 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;
|
|
const struct formats *pf;
|
|
char *filename, *mode = NULL;
|
|
#ifdef USE_WINSOCK
|
|
DWORD recvtimeout, recvtimeoutbak;
|
|
#endif
|
|
const char *option = "mode"; /* mode is implicit */
|
|
int toggle = 1;
|
|
FILE *server;
|
|
char dumpfile[256];
|
|
|
|
msnprintf(dumpfile, sizeof(dumpfile), "%s/%s", logdir, REQUEST_DUMP);
|
|
|
|
/* Open request dump file. */
|
|
server = fopen(dumpfile, "ab");
|
|
if(!server) {
|
|
int error = errno;
|
|
logmsg("fopen() failed with error: %d %s", error, strerror(error));
|
|
logmsg("Error opening file: %s", dumpfile);
|
|
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 || !mode) {
|
|
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, logdir);
|
|
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, unsigned short 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: %"
|
|
CURL_FORMAT_CURL_OFF_T "\r\n", our_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, logdir);
|
|
|
|
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: %ld", 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, const 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(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(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, const 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(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(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(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(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");
|
|
}
|