On Mon, 2019-03-04 at 08:07 +0800, Paul Wise wrote: > On Sun, 2019-03-03 at 23:07 +0100, Reiner Herrmann wrote: > > > I just noticed that you suggested a patch for atftp, > > but it is no longer available. > > There is a HTML-only copy of the patch on archive.org, it would require > some pre-processing but it could potentially be applied. > > https://web.archive.org/web/20090502165734/http://www.kaarsemaker.net/downloads/code/atftp.patch > > Looks like there may be a git repo containing the patch, but the web > server is returning an internal server error for it. > > https://git.kaarsemaker.net/ > https://git.kaarsemaker.net/atftp/ > > The author of the patch seems to be on IRC and also lists their email > address so they should be easily contactable, I've CCed them. > > https://www.kaarsemaker.net/contact/ > > Dennis, we are discussing your old dynamic content patch for atftpd, > which I proposed to add to the Debian package in this bug report: > > https://bugs.debian.org/523979 > > > Do you by chance still have the patch and could attach it > > to the bug? Otherwise I would suggest to close it. > > I don't have a copy of the patch file. > > BTW, atftpd upstream looks like they need help, you might want to > consider working on atftpd upstream. There is also a newer upstream > release and git commits than what is in Debian.
Here are some freshly format-patch'ed patches. One is the patch under discussion, another adds large file support to atftpd. Both were needed at the time (and probably still are, but I changed jobs and no longer work on this) and I don't know whether either of them has been merged upstream.
From e60ad90917c727b607975c4077b5b4e1bc85b316 Mon Sep 17 00:00:00 2001 From: Dennis Kaarsemaker <[email protected]> Date: Fri, 30 Nov 2012 15:33:41 +0100 Subject: [PATCH 2/2] Support tftp wraparound when sending files (tftpd only) gpxe hardcodes a block size of 1408, presumably to avoid fragmentation. This limits the size of a file it can receive to 88MB when adhering strictly to the rfc's. However, there is a non-standard extension, supported by gpxe and other tftp(d) implementations: block-id wraparound. This lets block id's wrap around, effectively making the file size unlimited. Unfortunately I now need this as HP's scripting toolkit now has a 120MB initrd. --- atftpd.8 | 7 +++++++ tftp_def.c | 1 + tftp_def.h | 3 ++- tftpd.c | 5 +++++ tftpd_file.c | 34 +++++++++++++++++++++------------- 5 files changed, 36 insertions(+), 14 deletions(-) diff --git a/atftpd.8 b/atftpd.8 index 2c3293c..1977aea 100644 --- a/atftpd.8 +++ b/atftpd.8 @@ -73,6 +73,13 @@ acknowledging the 'tsize' option requested by the client. disable 'blksize' from RFC2348. This will prevent the server from acknowledging the 'blksize' request by the client. +.TP +.B \-\-allow\-wraparound +Allow block ID wraparound when sending files. This allows you to send +files larger than 2^16 * blksize if the client supports wraparound too +pxelinux.0 needs this to use an initrd larger than 88MB as it hardcodes +a blksize of 1492 + .TP .B \-\-no\-multicast disable 'multicast' from RFC2090. This will prevent the server from diff --git a/tftp_def.c b/tftp_def.c index 96abdc5..f4132fa 100644 --- a/tftp_def.c +++ b/tftp_def.c @@ -40,6 +40,7 @@ struct tftp_opt tftp_default_options[OPT_NUMBER] = { { "blksize", "512", 0, 1 }, /* This is the default option */ { "multicast", "", 0, 1 }, /* structure */ { "password", "", 0, 1}, /* password */ + { "wraparound", "", 0, 0 },/* Allow block id wraparound in send (default: no) */ { "", "", 0, 0} }; diff --git a/tftp_def.h b/tftp_def.h index e4b338d..865948d 100644 --- a/tftp_def.h +++ b/tftp_def.h @@ -41,7 +41,8 @@ #define OPT_BLKSIZE 4 #define OPT_MULTICAST 5 #define OPT_PASSWORD 6 -#define OPT_NUMBER 7 /* number of OPT_xx options */ +#define OPT_WRAPAROUND 7 +#define OPT_NUMBER 8 /* number of OPT_xx options */ #define OPT_SIZE 12 #define VAL_SIZE MAXLEN diff --git a/tftpd.c b/tftpd.c index 7306f5a..7ee137b 100644 --- a/tftpd.c +++ b/tftpd.c @@ -890,6 +890,7 @@ int tftpd_cmd_line_options(int argc, char **argv) { "no-timeout", 0, NULL, 'T' }, { "no-tsize", 0, NULL, 'S' }, { "no-blksize", 0, NULL, 'B' }, + { "allow-wraparound", 0, NULL, 'W' }, { "no-multicast", 0, NULL, 'M' }, { "logfile", 1, NULL, 'L' }, { "pidfile", 1, NULL, 'I'}, @@ -966,6 +967,9 @@ int tftpd_cmd_line_options(int argc, char **argv) case 'B': tftp_default_options[OPT_BLKSIZE].enabled = 0; break; + case 'W': + tftp_default_options[OPT_WRAPAROUND].enabled = 1; + break; case 'M': tftp_default_options[OPT_MULTICAST].enabled = 0; break; @@ -1219,6 +1223,7 @@ void tftpd_usage(void) " --no-timeout : disable 'timeout' from RFC2349\n" " --no-tsize : disable 'tsize' from RFC2349\n" " --no-blksize : disable 'blksize' from RFC2348\n" + " --allow-wraparound : allow block ID's to wrap around when sending\n" " --no-multicast : disable 'multicast' from RFC2090\n" " --logfile <file> : logfile to log logs to ;-) (use - for stdout)\n" " --pidfile <file> : write PID to this file\n" diff --git a/tftpd_file.c b/tftpd_file.c index b8ff1fc..a29a343 100644 --- a/tftpd_file.c +++ b/tftpd_file.c @@ -411,6 +411,8 @@ int tftpd_send_file(struct thread_data *data) int timeout_state = state; int result; int block_number = 0; + unsigned short recv_number = 0; + int wraparound_offset = 0; int last_block = -1; int data_size; struct sockaddr_storage *sa = &data->client_info->client; @@ -618,16 +620,18 @@ int tftpd_send_file(struct thread_data *data) logger(LOG_INFO, "blksize option -> %d", result); } - /* Verify that the file can be sent in 2^16 block of BLKSIZE octets */ - if ((file_stat.st_size / (data->data_buffer_size - 4)) > 65535) - { - tftp_send_error(sockfd, sa, EUNDEF, data->data_buffer, data->data_buffer_size); - logger(LOG_NOTICE, "Requested file to big, increase BLKSIZE"); - if (data->trace) - logger(LOG_DEBUG, "sent ERROR <code: %d, msg: %s>", EUNDEF, - tftp_errmsg[EUNDEF]); - fclose(fp); - return ERR; + if(!data->tftp_options[OPT_WRAPAROUND].enabled) { + /* Verify that the file can be sent in 2^16 block of BLKSIZE octets */ + if ((file_stat.st_size / (data->data_buffer_size - 4)) > 65535) + { + tftp_send_error(sockfd, sa, EUNDEF, data->data_buffer, data->data_buffer_size); + logger(LOG_NOTICE, "Requested file to big, increase BLKSIZE"); + if (data->trace) + logger(LOG_DEBUG, "sent ERROR <code: %d, msg: %s>", EUNDEF, + tftp_errmsg[EUNDEF]); + fclose(fp); + return ERR; + } } /* multicast option */ @@ -933,10 +937,14 @@ int tftpd_send_file(struct thread_data *data) } /* The ACK is from the current client */ number_of_timeout = 0; - block_number = ntohs(tftphdr->th_block); + recv_number = ntohs(tftphdr->th_block); + if(recv_number == 0 && block_number) { + wraparound_offset += 0x10000; + } + block_number = wraparound_offset + recv_number; if (data->trace) - logger(LOG_DEBUG, "received ACK <block: %d>", - block_number); + logger(LOG_DEBUG, "received ACK <block: %d/%d>", + recv_number, block_number); if ((last_block != -1) && (block_number > last_block)) { state = S_END; -- 2.17.1
From 34aba54b0afb66a42456bb347739973433f4fce0 Mon Sep 17 00:00:00 2001 From: Dennis Kaarsemaker <[email protected]> Date: Fri, 30 Nov 2012 15:20:52 +0100 Subject: [PATCH 1/2] Add an option to generate sent files dynamically Similar to what an httpd can do with cgi scripts, atftpd can now call separate programs to generate file contents it should send back. e.g. atftpd --content-generator /usr/local/bin/my_generator This generator can for instance write dynamically generated pxelinux.cfg files, or really anythong you want. It's only called if the requested file does not exist. --- README | 1 + README.GENERATED | 61 ++++++++++++++++++++++++++++++++++++++++++++++++ atftpd.8 | 9 +++++++ tftpd.c | 13 +++++++++++ tftpd_file.c | 53 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 137 insertions(+) create mode 100644 README.GENERATED diff --git a/README b/README index 2f3b962..01a13b8 100644 --- a/README +++ b/README @@ -47,3 +47,4 @@ Thayne Harbaugh <[email protected]> Thomas Anders <[email protected]> MichaĆ Rzechonek <[email protected]> Florian Fainelli <[email protected]> +Dennis Kaarsemaker <[email protected]> diff --git a/README.GENERATED b/README.GENERATED new file mode 100644 index 0000000..455edd8 --- /dev/null +++ b/README.GENERATED @@ -0,0 +1,61 @@ +Dynamically generating content with atftpd +------------------------------------------ + +When installing many machines via PXEboot kickstart/d-i installs, it is often +useful to generate pxelinux.cfg files on demand with contents grabbed from a +database. Or when you have an Asus Wl500g router, you have only tftp available +to download data, being able to generate this data could be useful (eg. lists +of macaddresses or iptables rules). + +These were my two use cases for implementing support for dynamic content in +atftpd. Here is how to use it: + +* Add the "--content-generator /path/to/executable" argument to the atftpd + commandline (eg. in xinetd.conf or /etc/default/atftpd, depending on your + setup). +* Whenever a requested file cannot be found, atftpd will open a temporary + file with tmpfile(3) and will execute your application, with the + requested name as arguments (example: + /usr/local/bin/tftp-generator --file /var/lib/tftpboot/some_file) + The application must ignore unknown arguments, more arguments might be added + later. +* Your application is now responsible for writing the content that atftpd + should send to the given fd (6 in the example). If the content needs to + be stored, your application also must do so itself. +* If your application exists with a non-zero exitstatus or writes zero bytes, + atftpd will treat that as "file not found". If data is written and your + application exits with code 0, the written data is sent to the client. + +-- +Dennis Kaarsemaker +<[email protected]> + +------------------------------------------------------------------------------- +Example application for use case #2: Grabbing a list of mac addresses from a +database. The database is maintained using the django framework. + +#!/usr/bin/python + +import os +import sys +from optparse import OptionParser + +parser = OptionParser() +parser.add_option("--file", dest="filename", help="Requested file", metavar="FILE") +options, args = parser.parse_args() + +if os.path.basename(options.filename) != 'maclist': + sys.exit(1) +fd = os.fdopen(3, 'w') + +sys.path.insert(0,'/srv/www/intranet') +os.environ['DJANGO_SETTINGS_MODULE'] = 'infra.settings' +from infra.machine.models import Interface + +fd.write("""#!/bin/sh +# Shellscript generated from infra database +# Do not edit by hand + +wl mac none +wl mac %s 00:00:00:00:00:00 +""" % ' '.join([x.mac for x in Interface.objects.filter(wireless=True)])) diff --git a/atftpd.8 b/atftpd.8 index c4d7a8f..2c3293c 100644 --- a/atftpd.8 +++ b/atftpd.8 @@ -161,6 +161,15 @@ Test a pattern/replacement file. When using this option, the server will not start as usual but just read file name from stdin and printout the substitution. +.TP +.B \-\-content\-generator <path> +Execute <path> to generate content for nonexisting files. If a +requested file does not exist, the program will be called as <path> +--file <requested file>. The generated contents must be written to +file descriptor 3, which will be opened by atftpd using tmpfile(). +If the contents need to be stored as well, the program itself is +responsible for doing so, atftpd will not store the contents. + .TP .B \-\-mtftp <file> This will start a mtftp server thread for each valid entry in the diff --git a/tftpd.c b/tftpd.c index b9822c4..7306f5a 100644 --- a/tftpd.c +++ b/tftpd.c @@ -115,6 +115,8 @@ tftpd_pcre_self_t *pcre_top = NULL; char *pcre_file; #endif +char *content_generator = NULL; + #ifdef HAVE_MTFTP /* mtftp options */ struct mtftp_data *mtftp_data = NULL; @@ -908,6 +910,7 @@ int tftpd_cmd_line_options(int argc, char **argv) { "pcre", 1, NULL, OPT_PCRE }, { "pcre-test", 1, NULL, OPT_PCRE_TEST }, #endif + { "content-generator", 1, NULL, 'g' }, #ifdef HAVE_MTFTP { "mtftp", 1, NULL, OPT_MTFTP }, { "mtftp-port", 1, NULL, OPT_MTFTP_PORT }, @@ -1043,6 +1046,13 @@ int tftpd_cmd_line_options(int argc, char **argv) printf("Substitution: \"%s\" -> \"%s\"\n", string, out); } #endif + case 'g': + content_generator = strdup(optarg); + if(access(content_generator, X_OK)) { + fprintf(stderr, "Cannot use %s as content generator: %s\n", content_generator, strerror(errno)); + content_generator = NULL; + } + break; case OPT_PORT_CHECK: source_port_checking = 0; break; @@ -1135,6 +1145,8 @@ void tftpd_log_options(void) if (pcre_top) logger(LOG_INFO, " PCRE: using file: %s", pcre_file); #endif + if(content_generator) + logger(LOG_INFO, " content generator: %s", content_generator); #ifdef HAVE_MTFTP if (strcmp(mtftp_file, "") != 0) { @@ -1225,6 +1237,7 @@ void tftpd_usage(void) " --pcre <file> : use this file for pattern replacement\n" " --pcre-test <file> : just test pattern file, not starting server\n" #endif + " --content-generator <path> : use <path> to generate content\n" #ifdef HAVE_MTFTP " --mtftp <file> : mtftp configuration file\n" " --mtftp-port <port> : port mtftp will listen\n" diff --git a/tftpd_file.c b/tftpd_file.c index da1d6c9..b8ff1fc 100644 --- a/tftpd_file.c +++ b/tftpd_file.c @@ -36,6 +36,8 @@ #ifdef HAVE_PCRE #include "tftpd_pcre.h" #endif +#include <sys/types.h> +#include <sys/wait.h> #define S_BEGIN 0 #define S_SEND_REQ 1 @@ -60,6 +62,7 @@ extern int tftpd_cancel; extern tftpd_pcre_self_t *pcre_top; #endif +extern char* content_generator; /* * Rules for filenames. This is common to both tftpd_recieve_file @@ -434,6 +437,8 @@ int tftpd_send_file(struct thread_data *data) int prev_block_number = 0; /* needed to support netascii convertion */ int prev_file_pos = 0; int temp = 0; + pid_t generator_pid = 0; + int generator_status; /* look for mode option */ if (strcasecmp(data->tftp_options[OPT_MODE].value, "netascii") == 0) @@ -491,6 +496,54 @@ int tftpd_send_file(struct thread_data *data) } } #endif + if (fp == NULL) + { + if (content_generator) + { + logger(LOG_DEBUG, "Trying to generate contents for %s", filename); + fp = tmpfile(); + generator_pid = fork(); + switch(generator_pid) { + case 0: + /* In the child */ + close(3); + dup2(fileno(fp), 3); + for(temp=4; temp < 1024; temp++) + close(temp); + execl(content_generator, content_generator, "--file", filename, (char *)NULL); + /* Exec failed, make sure the parent notices */ + exit(66); + ;; + case -1: + logger(LOG_WARNING, "fork() failed: %s", strerror(errno)); + fclose(fp); + fp = NULL; + default: + /* In the parent */ + while(1) { + errno = 0; + temp = waitpid(generator_pid, &generator_status, 0); + if(temp == -1 && errno == EINTR) + continue; + break; + } + if(!WIFEXITED(generator_status) || (WEXITSTATUS(generator_status) != 0)) { + logger(LOG_WARNING, "generating content failed: %d %d", generator_status, WEXITSTATUS(generator_status)); + fclose(fp); + fp = NULL; + } + else { + fstat(fileno(fp), &file_stat); + if(file_stat.st_size == 0) { + logger(LOG_WARNING, "content generator wrote 0 bytes"); + fclose(fp); + fp = NULL; + } + } + break; + } + } + } if (fp == NULL) { tftp_send_error(sockfd, sa, ENOTFOUND, data->data_buffer, data->data_buffer_size); -- 2.17.1

