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
* 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);

View file

@ -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 */

View file

@ -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

View file

@ -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

View file

@ -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