From 6f96fcd1b669dbf9c0a3d96d26d370117fddcadd Mon Sep 17 00:00:00 2001 From: "H. Peter Anvin" Date: Wed, 29 May 2024 17:16:06 -0700 Subject: [PATCH] tftpd: allow a rewrite rule to probe for the existence of a file This adds an "E" flag to the rewrite rules, which exits rule processing if and only if the result is a valid, accessible filename. If combined with "r", the rewrite is cancelled if the rule is not applied. Signed-off-by: H. Peter Anvin --- tftpd/remap.c | 72 +++++++++++++++++++++++++++++++++++------------- tftpd/remap.h | 4 ++- tftpd/tftpd.8.in | 13 +++++++-- tftpd/tftpd.c | 52 +++++++++++++++++----------------- tftpd/tftpd.h | 9 ++++++ 5 files changed, 103 insertions(+), 47 deletions(-) diff --git a/tftpd/remap.c b/tftpd/remap.c index 6f5b409..c7c8f85 100644 --- a/tftpd/remap.c +++ b/tftpd/remap.c @@ -1,6 +1,6 @@ /* ----------------------------------------------------------------------- * * - * Copyright 2001-2014 H. Peter Anvin - All Rights Reserved + * Copyright 2001-2024 H. Peter Anvin - All Rights Reserved * * This program is free software available under the same license * as the "OpenBSD" operating system, distributed at @@ -34,6 +34,8 @@ #define RULE_IPV4 0x40 /* IPv4 only */ #define RULE_IPV6 0x80 /* IPv6 only */ +#define RULE_HASFILE 0x100 /* Valid if rule results in a valid filename */ + struct rule { struct rule *next; int nrule; @@ -213,6 +215,9 @@ static int parseline(char *line, struct rule *r, int lineno) case 'e': r->rule_flags |= RULE_EXIT; break; + case 'E': + r->rule_flags |= RULE_HASFILE; + break; case 's': r->rule_flags |= RULE_RESTART; break; @@ -244,15 +249,21 @@ static int parseline(char *line, struct rule *r, int lineno) } } - /* RULE_GLOBAL only applies when RULE_REWRITE specified */ - if (!(r->rule_flags & RULE_REWRITE)) - r->rule_flags &= ~RULE_GLOBAL; - - if ((r->rule_flags & (RULE_INVERSE | RULE_REWRITE)) == - (RULE_INVERSE | RULE_REWRITE)) { - syslog(LOG_ERR, "r rules cannot be inverted, line %d: %s\n", - lineno, line); - return -1; /* Error */ + if (r->rule_flags & RULE_REWRITE) { + if (r->rule_flags & RULE_INVERSE) { + syslog(LOG_ERR, "r rules cannot be inverted, line %d: %s\n", + lineno, line); + return -1; /* Error */ + } + if ((r->rule_flags & (RULE_GLOBAL|RULE_HASFILE)) + == (RULE_GLOBAL|RULE_HASFILE)) { + syslog(LOG_ERR, "E rules cannot be combined with g, line %d: %s\n", + lineno, line); + return -1; /* Error */ + } + } else { + /* RULE_GLOBAL is meaningless without RULE_REWRITE */ + r->rule_flags &= ~RULE_GLOBAL; } /* Read and compile the regex */ @@ -333,12 +344,14 @@ void freerules(struct rule *r) } /* Execute a rule set on a string; returns a malloc'd new string. */ -char *rewrite_string(const char *input, const struct rule *rules, +char *rewrite_string(const struct formats *pf, + const char *input, const struct rule *rules, char mode, int af, match_pattern_callback macrosub, const char **errmsg) { char *current = tfstrdup(input); char *newstr; + const char *accerr; const struct rule *ruleptr = rules; regmatch_t pmatch[10]; int len; @@ -410,13 +423,34 @@ char *rewrite_string(const char *input, const struct rule *rules, newstr = tfmalloc(len + 1); genmatchstring(newstr, ruleptr->pattern, current, pmatch, macrosub); - free(current); - current = newstr; - if (verbosity >= 3) { - syslog(LOG_INFO, "remap: rule %d: rewrite: %s", - ruleptr->nrule, current); - } - } + if ((ruleptr->rule_flags & RULE_HASFILE) && + pf->f_validate(newstr, mode == 'G' ? RRQ : WRQ, + pf, &accerr)) { + if (verbosity >= 3) { + syslog(LOG_INFO, "remap: rule %d: ignored rewrite (%s): %s", + ruleptr->nrule, accerr, newstr); + } + free(newstr); + was_match = 0; + break; + } + free(current); + current = newstr; + if (verbosity >= 3) { + syslog(LOG_INFO, "remap: rule %d: rewrite: %s", + ruleptr->nrule, current); + } + } else if (ruleptr->rule_flags & RULE_HASFILE) { + if (pf->f_validate(current, mode == 'G' ? RRQ : WRQ, + pf, &accerr)) { + if (verbosity >= 3) { + syslog(LOG_INFO, "remap: rule %d: not exiting (%s)\n", + ruleptr->nrule, accerr); + } + was_match = 0; + break; + } + } } else { break; /* No match, terminate unconditionally */ } @@ -426,7 +460,7 @@ char *rewrite_string(const char *input, const struct rule *rules, if (was_match) { was_match = 0; - if (ruleptr->rule_flags & RULE_EXIT) { + if (ruleptr->rule_flags & (RULE_EXIT|RULE_HASFILE)) { if (verbosity >= 3) { syslog(LOG_INFO, "remap: rule %d: exit", ruleptr->nrule); diff --git a/tftpd/remap.h b/tftpd/remap.h index 7efcf6a..2290aad 100644 --- a/tftpd/remap.h +++ b/tftpd/remap.h @@ -35,7 +35,9 @@ struct rule *parserulefile(FILE *); void freerules(struct rule *); /* Execute a rule set on a string; returns a malloc'd new string. */ -char *rewrite_string(const char *, const struct rule *, char, int, +struct formats; +char *rewrite_string(const struct formats *, const char *, + const struct rule *, char, int, match_pattern_callback, const char **); #endif /* WITH_REGEX */ diff --git a/tftpd/tftpd.8.in b/tftpd/tftpd.8.in index 71a712d..c9722cf 100644 --- a/tftpd/tftpd.8.in +++ b/tftpd/tftpd.8.in @@ -281,6 +281,15 @@ case-insensitively. By default it is case sensitive. .B e If this rule matches, end rule processing after executing the rule. .TP +.B E +If this rule matches, +\fIand the result matches a filename that can be transferred\fP, +end rule processing after executing the rule. If this is combined with +.BR r , +then if the substitution does \fInot\fP result in a valid filename, +the substitution is undone. This cannot be combined with +.BR g . +.TP .B s If this rule matches, start rule processing over from the very first rule after executing this rule. @@ -307,10 +316,10 @@ Inverse the sense of this rule, i.e. execute the only if the .I regex .I doesn't -match. Cannot used together with +match. Cannot used together with .BR r . .PP -The following escape sequences are recognized as part of the +The following escape sequences are recognized as part of a .IR "replacement pattern" : .TP \fB\\0\fP diff --git a/tftpd/tftpd.c b/tftpd/tftpd.c index 9abf009..3349894 100644 --- a/tftpd/tftpd.c +++ b/tftpd/tftpd.c @@ -106,11 +106,12 @@ int portrange = 0; unsigned int portrange_from, portrange_to; int verbosity = 0; -struct formats; #ifdef WITH_REGEX static struct rule *rewrite_rules = NULL; #endif +static FILE *file; + int tftp(struct tftphdr *, int); static void nak(int, const char *); static void timer(int); @@ -162,14 +163,14 @@ static void timer(int sig) } #ifdef WITH_REGEX -static struct rule *read_remap_rules(const char *file) +static struct rule *read_remap_rules(const char *rulefile) { FILE *f; struct rule *rulep; - f = fopen(file, "rt"); + f = fopen(rulefile, "rt"); if (!f) { - syslog(LOG_ERR, "Cannot open map file: %s: %m", file); + syslog(LOG_ERR, "Cannot open map file: %s: %m", rulefile); exit(EX_NOINPUT); } rulep = parserulefile(f); @@ -1057,19 +1058,12 @@ int main(int argc, char **argv) exit(0); } -static char *rewrite_access(char *, int, int, const char **); +static char *rewrite_access(const struct formats *, + char *, int, int, const char **); static int validate_access(char *, int, const struct formats *, const char **); static void tftp_sendfile(const struct formats *, struct tftphdr *, int); static void tftp_recvfile(const struct formats *, struct tftphdr *, int); -struct formats { - const char *f_mode; - char *(*f_rewrite) (char *, int, int, const char **); - int (*f_validate) (char *, int, const struct formats *, const char **); - void (*f_send) (const struct formats *, struct tftphdr *, int); - void (*f_recv) (const struct formats *, struct tftphdr *, int); - int f_convert; -}; static const struct formats formats[] = { { "netascii", rewrite_access, validate_access, tftp_sendfile, @@ -1126,8 +1120,9 @@ int tftp(struct tftphdr *tp, int size) nak(EBADOP, "Unknown mode"); exit(0); } + file = NULL; if (!(filename = (*pf->f_rewrite) - (origfilename, tp_opcode, from.sa.sa_family, &errmsgptr))) { + (pf, origfilename, tp_opcode, from.sa.sa_family, &errmsgptr))) { nak(EACCESS, errmsgptr); /* File denied by mapping rule */ exit(0); } @@ -1150,12 +1145,18 @@ int tftp(struct tftphdr *tp, int size) tmp_p, origfilename, filename); } - ecode = - (*pf->f_validate) (filename, tp_opcode, pf, &errmsgptr); - if (ecode) { - nak(ecode, errmsgptr); - exit(0); - } + /* + * If "file" is already set, then a file was already validated + * and opened during remap processing. + */ + if (!file) { + ecode = + (*pf->f_validate) (filename, tp_opcode, pf, &errmsgptr); + if (ecode) { + nak(ecode, errmsgptr); + exit(0); + } + } opt = ++cp; } else if (argn & 1) { val = ++cp; @@ -1411,12 +1412,12 @@ static int rewrite_macros(char macro, char *output) /* * Modify the filename, if applicable. If it returns NULL, deny the access. */ -static char *rewrite_access(char *filename, int mode, int af, - const char **msg) +static char *rewrite_access(const struct formats *pf, char *filename, + int mode, int af, const char **msg) { if (rewrite_rules) { char *newname = - rewrite_string(filename, rewrite_rules, + rewrite_string(pf, filename, rewrite_rules, mode != RRQ ? 'P' : 'G', af, rewrite_macros, msg); filename = newname; @@ -1425,8 +1426,10 @@ static char *rewrite_access(char *filename, int mode, int af, } #else -static char *rewrite_access(char *filename, int mode, int af, const char **msg) +static char *rewrite_access(const struct formats *pf, char *filename, + int mode, int af, const char **msg) { + (void)pf; (void)mode; /* Avoid warning */ (void)msg; (void)af; @@ -1434,7 +1437,6 @@ static char *rewrite_access(char *filename, int mode, int af, const char **msg) } #endif -static FILE *file; /* * Validate file access. Since we * have no uid or gid, for now require diff --git a/tftpd/tftpd.h b/tftpd/tftpd.h index e1d8bf0..277e5d2 100644 --- a/tftpd/tftpd.h +++ b/tftpd/tftpd.h @@ -23,4 +23,13 @@ char *tfstrdup(const char *); extern int verbosity; +struct formats { + const char *f_mode; + char *(*f_rewrite) (const struct formats *, char *, int, int, const char **); + int (*f_validate) (char *, int, const struct formats *, const char **); + void (*f_send) (const struct formats *, struct tftphdr *, int); + void (*f_recv) (const struct formats *, struct tftphdr *, int); + int f_convert; +}; + #endif