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 <hpa@zytor.com>
This commit is contained in:
H. Peter Anvin 2024-05-29 17:16:06 -07:00
parent 1dc6d55811
commit 6f96fcd1b6
5 changed files with 103 additions and 47 deletions

View file

@ -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 * This program is free software available under the same license
* as the "OpenBSD" operating system, distributed at * as the "OpenBSD" operating system, distributed at
@ -34,6 +34,8 @@
#define RULE_IPV4 0x40 /* IPv4 only */ #define RULE_IPV4 0x40 /* IPv4 only */
#define RULE_IPV6 0x80 /* IPv6 only */ #define RULE_IPV6 0x80 /* IPv6 only */
#define RULE_HASFILE 0x100 /* Valid if rule results in a valid filename */
struct rule { struct rule {
struct rule *next; struct rule *next;
int nrule; int nrule;
@ -213,6 +215,9 @@ static int parseline(char *line, struct rule *r, int lineno)
case 'e': case 'e':
r->rule_flags |= RULE_EXIT; r->rule_flags |= RULE_EXIT;
break; break;
case 'E':
r->rule_flags |= RULE_HASFILE;
break;
case 's': case 's':
r->rule_flags |= RULE_RESTART; r->rule_flags |= RULE_RESTART;
break; break;
@ -244,16 +249,22 @@ static int parseline(char *line, struct rule *r, int lineno)
} }
} }
/* RULE_GLOBAL only applies when RULE_REWRITE specified */ if (r->rule_flags & RULE_REWRITE) {
if (!(r->rule_flags & RULE_REWRITE)) if (r->rule_flags & RULE_INVERSE) {
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", syslog(LOG_ERR, "r rules cannot be inverted, line %d: %s\n",
lineno, line); lineno, line);
return -1; /* Error */ 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 */ /* Read and compile the regex */
if (!readescstring(buffer, &line)) { if (!readescstring(buffer, &line)) {
@ -333,12 +344,14 @@ void freerules(struct rule *r)
} }
/* Execute a rule set on a string; returns a malloc'd new string. */ /* 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, char mode, int af, match_pattern_callback macrosub,
const char **errmsg) const char **errmsg)
{ {
char *current = tfstrdup(input); char *current = tfstrdup(input);
char *newstr; char *newstr;
const char *accerr;
const struct rule *ruleptr = rules; const struct rule *ruleptr = rules;
regmatch_t pmatch[10]; regmatch_t pmatch[10];
int len; int len;
@ -410,12 +423,33 @@ char *rewrite_string(const char *input, const struct rule *rules,
newstr = tfmalloc(len + 1); newstr = tfmalloc(len + 1);
genmatchstring(newstr, ruleptr->pattern, current, genmatchstring(newstr, ruleptr->pattern, current,
pmatch, macrosub); pmatch, macrosub);
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); free(current);
current = newstr; current = newstr;
if (verbosity >= 3) { if (verbosity >= 3) {
syslog(LOG_INFO, "remap: rule %d: rewrite: %s", syslog(LOG_INFO, "remap: rule %d: rewrite: %s",
ruleptr->nrule, current); 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 { } else {
break; /* No match, terminate unconditionally */ break; /* No match, terminate unconditionally */
@ -426,7 +460,7 @@ char *rewrite_string(const char *input, const struct rule *rules,
if (was_match) { if (was_match) {
was_match = 0; was_match = 0;
if (ruleptr->rule_flags & RULE_EXIT) { if (ruleptr->rule_flags & (RULE_EXIT|RULE_HASFILE)) {
if (verbosity >= 3) { if (verbosity >= 3) {
syslog(LOG_INFO, "remap: rule %d: exit", syslog(LOG_INFO, "remap: rule %d: exit",
ruleptr->nrule); ruleptr->nrule);

View file

@ -35,7 +35,9 @@ struct rule *parserulefile(FILE *);
void freerules(struct rule *); void freerules(struct rule *);
/* Execute a rule set on a string; returns a malloc'd new string. */ /* 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 **); match_pattern_callback, const char **);
#endif /* WITH_REGEX */ #endif /* WITH_REGEX */

View file

@ -281,6 +281,15 @@ case-insensitively. By default it is case sensitive.
.B e .B e
If this rule matches, end rule processing after executing the rule. If this rule matches, end rule processing after executing the rule.
.TP .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 .B s
If this rule matches, start rule processing over from the very first If this rule matches, start rule processing over from the very first
rule after executing this rule. rule after executing this rule.
@ -310,7 +319,7 @@ only if the
match. Cannot used together with match. Cannot used together with
.BR r . .BR r .
.PP .PP
The following escape sequences are recognized as part of the The following escape sequences are recognized as part of a
.IR "replacement pattern" : .IR "replacement pattern" :
.TP .TP
\fB\\0\fP \fB\\0\fP

View file

@ -106,11 +106,12 @@ int portrange = 0;
unsigned int portrange_from, portrange_to; unsigned int portrange_from, portrange_to;
int verbosity = 0; int verbosity = 0;
struct formats;
#ifdef WITH_REGEX #ifdef WITH_REGEX
static struct rule *rewrite_rules = NULL; static struct rule *rewrite_rules = NULL;
#endif #endif
static FILE *file;
int tftp(struct tftphdr *, int); int tftp(struct tftphdr *, int);
static void nak(int, const char *); static void nak(int, const char *);
static void timer(int); static void timer(int);
@ -162,14 +163,14 @@ static void timer(int sig)
} }
#ifdef WITH_REGEX #ifdef WITH_REGEX
static struct rule *read_remap_rules(const char *file) static struct rule *read_remap_rules(const char *rulefile)
{ {
FILE *f; FILE *f;
struct rule *rulep; struct rule *rulep;
f = fopen(file, "rt"); f = fopen(rulefile, "rt");
if (!f) { 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); exit(EX_NOINPUT);
} }
rulep = parserulefile(f); rulep = parserulefile(f);
@ -1057,19 +1058,12 @@ int main(int argc, char **argv)
exit(0); 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 int validate_access(char *, int, const struct formats *, const char **);
static void tftp_sendfile(const struct formats *, struct tftphdr *, int); static void tftp_sendfile(const struct formats *, struct tftphdr *, int);
static void tftp_recvfile(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[] = { static const struct formats formats[] = {
{ {
"netascii", rewrite_access, validate_access, tftp_sendfile, "netascii", rewrite_access, validate_access, tftp_sendfile,
@ -1126,8 +1120,9 @@ int tftp(struct tftphdr *tp, int size)
nak(EBADOP, "Unknown mode"); nak(EBADOP, "Unknown mode");
exit(0); exit(0);
} }
file = NULL;
if (!(filename = (*pf->f_rewrite) 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 */ nak(EACCESS, errmsgptr); /* File denied by mapping rule */
exit(0); exit(0);
} }
@ -1150,12 +1145,18 @@ int tftp(struct tftphdr *tp, int size)
tmp_p, origfilename, tmp_p, origfilename,
filename); filename);
} }
/*
* If "file" is already set, then a file was already validated
* and opened during remap processing.
*/
if (!file) {
ecode = ecode =
(*pf->f_validate) (filename, tp_opcode, pf, &errmsgptr); (*pf->f_validate) (filename, tp_opcode, pf, &errmsgptr);
if (ecode) { if (ecode) {
nak(ecode, errmsgptr); nak(ecode, errmsgptr);
exit(0); exit(0);
} }
}
opt = ++cp; opt = ++cp;
} else if (argn & 1) { } else if (argn & 1) {
val = ++cp; 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. * Modify the filename, if applicable. If it returns NULL, deny the access.
*/ */
static char *rewrite_access(char *filename, int mode, int af, static char *rewrite_access(const struct formats *pf, char *filename,
const char **msg) int mode, int af, const char **msg)
{ {
if (rewrite_rules) { if (rewrite_rules) {
char *newname = char *newname =
rewrite_string(filename, rewrite_rules, rewrite_string(pf, filename, rewrite_rules,
mode != RRQ ? 'P' : 'G', af, mode != RRQ ? 'P' : 'G', af,
rewrite_macros, msg); rewrite_macros, msg);
filename = newname; filename = newname;
@ -1425,8 +1426,10 @@ static char *rewrite_access(char *filename, int mode, int af,
} }
#else #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)mode; /* Avoid warning */
(void)msg; (void)msg;
(void)af; (void)af;
@ -1434,7 +1437,6 @@ static char *rewrite_access(char *filename, int mode, int af, const char **msg)
} }
#endif #endif
static FILE *file;
/* /*
* Validate file access. Since we * Validate file access. Since we
* have no uid or gid, for now require * have no uid or gid, for now require

View file

@ -23,4 +23,13 @@ char *tfstrdup(const char *);
extern int verbosity; 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 #endif