remap: fix timeouts for "g", add a "gg" flag to match sed s///g

Make sure that when using the global option, we still bump the deadman
timer.

The "g" option really should only have applied to the right-hand
unmatched part of the string, like in sed. Add a "gg" option which
does that.

Signed-off-by: H. Peter Anvin <hpa@zytor.com>
This commit is contained in:
H. Peter Anvin 2024-06-10 20:19:55 -07:00
parent 74c5d8a020
commit ac7f98e4d8
2 changed files with 189 additions and 119 deletions

View file

@ -22,7 +22,7 @@
#include "tftpd.h"
#include "remap.h"
#define DEADMAN_MAX_STEPS 1024 /* Timeout after this many steps */
#define DEADMAN_MAX_STEPS 4096 /* Timeout after this many steps */
#define MAXLINE 16384 /* Truncate a line at this many bytes */
#define RULE_REWRITE 0x01 /* This is a rewrite rule */
@ -37,6 +37,7 @@
#define RULE_HASFILE 0x100 /* Valid if rule results in a valid filename */
#define RULE_RRQ 0x200 /* Get (read) only */
#define RULE_WRQ 0x400 /* Put (write) only */
#define RULE_SEDG 0x800 /* sed-style global */
struct rule {
struct rule *next;
@ -61,22 +62,30 @@ static int xform_tolower(int c)
return tolower(c);
}
/* Do \-substitution. Call with string == NULL to get length only. */
static int genmatchstring(char *string, const char *pattern,
const char *input, const regmatch_t * pmatch,
match_pattern_callback macrosub)
/*
* Do \-substitution. Call with string == NULL to get length only.
* "start" indicates an offset into the input buffer where the pattern
* match was started.
*/
static int do_genmatchstring(char *string, const char *pattern,
const char *ibuf,
const regmatch_t * pmatch,
match_pattern_callback macrosub,
int start, int *nextp)
{
int (*xform) (int) = xform_null;
int len = 0;
int n, mlen, sublen;
int endbytes;
const char *input = ibuf + start;
/* Get section before match; note pmatch[0] is the whole match */
endbytes = strlen(input) - pmatch[0].rm_eo;
len = pmatch[0].rm_so + endbytes;
len = start + pmatch[0].rm_so;
if (string) {
memcpy(string, input, pmatch[0].rm_so);
string += pmatch[0].rm_so;
/* Copy the prefix before "start" as well! */
memcpy(string, ibuf, start + pmatch[0].rm_so);
string += start + pmatch[0].rm_so;
}
/* Transform matched section */
@ -102,7 +111,7 @@ static int genmatchstring(char *string, const char *pattern,
mlen = pmatch[n].rm_eo - pmatch[n].rm_so;
len += mlen;
if (string) {
const char *p = input + pmatch[n].rm_so;
const char *p = input + start + pmatch[n].rm_so;
while (mlen--)
*string++ = xform(*p++);
}
@ -145,7 +154,12 @@ static int genmatchstring(char *string, const char *pattern,
}
}
/* Pointer to post-substitution tail */
if (nextp)
*nextp = len;
/* Copy section after match */
len += endbytes;
if (string) {
memcpy(string, input + pmatch[0].rm_eo, endbytes);
string[endbytes] = '\0';
@ -154,6 +168,26 @@ static int genmatchstring(char *string, const char *pattern,
return len;
}
/*
* Ditto, but allocate the string in a new buffer
*/
static int genmatchstring(char **string, const char *pattern,
const char *ibuf,
const regmatch_t * pmatch,
match_pattern_callback macrosub,
int start, int *nextp)
{
int len;
char *buf;
len = do_genmatchstring(NULL, pattern, ibuf, pmatch,
macrosub, start, NULL);
buf = tf_malloc(len + 1);
return do_genmatchstring(buf, pattern, ibuf, pmatch,
macrosub, start, nextp);
}
/*
* Extract a string terminated by non-escaped whitespace; ignoring
* leading whitespace. Consider an unescaped # to be a comment marker,
@ -165,7 +199,7 @@ static int readescstring(char *buf, char **str)
int wasbs = 0, len = 0;
while (*p && isspace(*p))
p++;
p++;
if (!*p) {
*buf = '\0';
@ -211,14 +245,17 @@ static int parseline(char *line, struct rule *r, int lineno)
r->rule_flags |= RULE_REWRITE;
break;
case 'g':
r->rule_flags |= RULE_GLOBAL;
if (r->rule_flags & RULE_GLOBAL)
r->rule_flags |= RULE_SEDG;
else
r->rule_flags |= RULE_GLOBAL;
break;
case 'e':
r->rule_flags |= RULE_EXIT;
break;
case 'E':
r->rule_flags |= RULE_HASFILE;
break;
case 'E':
r->rule_flags |= RULE_HASFILE;
break;
case 's':
r->rule_flags |= RULE_RESTART;
break;
@ -231,18 +268,18 @@ static int parseline(char *line, struct rule *r, int lineno)
case '~':
r->rule_flags |= RULE_INVERSE;
break;
case '4':
r->rule_flags |= RULE_IPV4;
break;
case '6':
r->rule_flags |= RULE_IPV6;
break;
case 'G':
r->rule_flags |= RULE_RRQ;
break;
case 'P':
r->rule_flags |= RULE_WRQ;
break;
case '4':
r->rule_flags |= RULE_IPV4;
break;
case '6':
r->rule_flags |= RULE_IPV6;
break;
case 'G':
r->rule_flags |= RULE_RRQ;
break;
case 'P':
r->rule_flags |= RULE_WRQ;
break;
default:
syslog(LOG_ERR,
"Remap command \"%s\" on line %d contains invalid char \"%c\"",
@ -253,20 +290,20 @@ static int parseline(char *line, struct rule *r, int lineno)
}
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 */
}
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_SEDG|RULE_HASFILE))
== (RULE_GLOBAL|RULE_HASFILE)) {
syslog(LOG_ERR, "E rules cannot be combined with g (but gg is OK), line %d: %s\n",
lineno, line);
return -1; /* Error */
}
} else {
/* RULE_GLOBAL is meaningless without RULE_REWRITE */
r->rule_flags &= ~RULE_GLOBAL;
/* RULE_GLOBAL and RULE_SEDG are meaningless without RULE_REWRITE */
r->rule_flags &= ~(RULE_GLOBAL|RULE_SEDG);
}
/* Read and compile the regex */
@ -348,19 +385,23 @@ void freerules(struct rule *r)
/* Execute a rule set on a string; returns a malloc'd new string. */
char *rewrite_string(const struct formats *pf,
const char *input, const struct rule *rules,
const char *input, const struct rule *rules,
int mode, int af, match_pattern_callback macrosub,
const char **errmsg)
{
char *current = tfstrdup(input);
char *newstr;
char *newstr, *newerstr;
const char *accerr;
const struct rule *ruleptr = rules;
regmatch_t pmatch[10];
int len;
int i;
int len, newlen;
int was_match = 0;
int deadman = DEADMAN_MAX_STEPS;
int matchsense;
int pmatches;
unsigned int bad_flags;
int ggoffset;
/* Default error */
*errmsg = "Remap table failure";
@ -376,30 +417,25 @@ char *rewrite_string(const struct formats *pf,
if (af != AF_INET6) bad_flags |= RULE_IPV6;
for (ruleptr = rules; ruleptr; ruleptr = ruleptr->next) {
if (ruleptr->rule_flags & bad_flags)
continue; /* This rule is excluded by flags */
if (ruleptr->rule_flags & bad_flags)
continue; /* This rule is excluded by flags */
if (!deadman--) {
syslog(LOG_WARNING,
"remap: Breaking loop, input = %s, last = %s", input,
current);
free(current);
return NULL; /* Did not terminate! */
}
matchsense = ruleptr->rule_flags & RULE_INVERSE ? REG_NOMATCH : 0;
pmatches = ruleptr->rule_flags & RULE_INVERSE ? 0 : 10;
/* Clear the pmatch[] array */
for (i = 0; i < 10; i++)
pmatch[i].rm_so = pmatch[i].rm_eo = -1;
do {
if (regexec(&ruleptr->rx, current, 10, pmatch, 0) ==
(ruleptr->rule_flags & RULE_INVERSE ? REG_NOMATCH : 0)) {
if (!deadman--)
goto dead;
if (regexec(&ruleptr->rx, current, pmatches, pmatch, 0)
== matchsense) {
/* Match on this rule */
was_match = 1;
if (ruleptr->rule_flags & RULE_INVERSE) {
/* No actual match, so clear out the pmatch array */
int i;
for (i = 0; i < 10; i++)
pmatch[i].rm_so = pmatch[i].rm_eo = -1;
}
if (ruleptr->rule_flags & RULE_ABORT) {
if (verbosity >= 3) {
syslog(LOG_INFO, "remap: rule %d: abort: %s",
@ -407,12 +443,8 @@ char *rewrite_string(const struct formats *pf,
}
if (ruleptr->pattern[0]) {
/* Custom error message */
len =
genmatchstring(NULL, ruleptr->pattern, current,
pmatch, macrosub);
newstr = tfmalloc(len + 1);
genmatchstring(newstr, ruleptr->pattern, current,
pmatch, macrosub);
genmatchstring(&newstr, ruleptr->pattern, current,
pmatch, macrosub, 0, NULL);
*errmsg = newstr;
} else {
*errmsg = NULL;
@ -422,58 +454,76 @@ char *rewrite_string(const struct formats *pf,
}
if (ruleptr->rule_flags & RULE_REWRITE) {
len = genmatchstring(NULL, ruleptr->pattern, current,
pmatch, macrosub);
newstr = tfmalloc(len + 1);
genmatchstring(newstr, ruleptr->pattern, current,
pmatch, macrosub);
if ((ruleptr->rule_flags & RULE_HASFILE) &&
pf->f_validate(newstr, mode, 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, 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 */
len = genmatchstring(&newstr, ruleptr->pattern, current,
pmatch, macrosub, 0, &ggoffset);
if (ruleptr->rule_flags & RULE_SEDG) {
/* sed-style partial-matching global */
while (ggoffset < len &&
regexec(ruleptr->rx, newstr + ggoffset,
pmatches, pmatch,
ggoffset ? REG_NOTBOL : 0)
== matchsense) {
if (!deadman--) {
free(current);
current = newstr;
goto dead;
}
len = genmatchstring(&newerstr, ruleptr->pattern,
newstr, pmatch, macrosub,
ggoffset, &ggoffset);
free(newstr);
newstr = newerstr;
}
}
if ((ruleptr->rule_flags & RULE_HASFILE) &&
pf->f_validate(newstr, mode, 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, pf, &accerr)) {
if (verbosity >= 3) {
syslog(LOG_INFO, "remap: rule %d: not exiting (%s)\n",
ruleptr->nrule, accerr);
}
was_match = 0;
break;
}
}
/* If the rule is global, keep going until no match */
} while (ruleptr->rule_flags & RULE_GLOBAL);
} else {
break; /* No match, terminate unconditionally */
}
/* If the rule is (old-style) global, keep going until no match */
} while ((ruleptr->rule_flags & (RULE_GLOBAL|RULE_SEDG)) == RULE_GLOBAL);
if (was_match) {
was_match = 0;
if (was_match) {
was_match = 0;
if (ruleptr->rule_flags & (RULE_EXIT|RULE_HASFILE)) {
if (verbosity >= 3) {
syslog(LOG_INFO, "remap: rule %d: exit",
ruleptr->nrule);
}
return current; /* Exit here, we're done */
} else if (ruleptr->rule_flags & RULE_RESTART) {
ruleptr = rules; /* Start from the top */
if (verbosity >= 3) {
syslog(LOG_INFO, "remap: rule %d: restart",
ruleptr->nrule);
}
if (ruleptr->rule_flags & (RULE_EXIT|RULE_HASFILE)) {
if (verbosity >= 3) {
syslog(LOG_INFO, "remap: rule %d: exit",
ruleptr->nrule);
}
return current; /* Exit here, we're done */
} else if (ruleptr->rule_flags & RULE_RESTART) {
ruleptr = rules; /* Start from the top */
if (verbosity >= 3) {
syslog(LOG_INFO, "remap: rule %d: restart",
ruleptr->nrule);
}
}
}
@ -482,4 +532,11 @@ char *rewrite_string(const struct formats *pf,
syslog(LOG_INFO, "remap: done");
}
return current;
dead: /* Deadman expired */
syslog(LOG_WARNING,
"remap: Breaking loop, input = %s, last = %s", input,
current);
free(current);
return NULL; /* Did not terminate! */
}

View file

@ -1,5 +1,5 @@
.\" -*- nroff -*- --------------------------------------------------------- *
.\"
.\"
.\" Copyright (c) 1990, 1993, 1994
.\" The Regents of the University of California. All rights reserved.
.\"
@ -270,7 +270,18 @@ by the
The replacement pattern may contain escape sequences; see below.
.TP
.B g
Repeat this rule until it no longer matches. This is always used with
Repeat this rule until it no longer matches. This is always used with
.BR r .
.TP
.B gg
Repeat this rule until it no longer matches, but only on the portion
of the string that has not yet been matched, similar to how the
.B s
command with the
.B g
option works in
.BR sed (1).
This is always used with
.BR r .
.TP
.B i
@ -288,7 +299,9 @@ 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 .
.BR g ,
but \fIcan\fP be combined with
.BR gg .
.TP
.B s
If this rule matches, start rule processing over from the very first
@ -400,7 +413,7 @@ Access to files can, and should, be restricted by invoking
.B tftpd
with a list of directories by including pathnames as server program
arguments on the command line. In this case access is restricted to
files whole names are prefixed by one of the given directories. If
files whose names are prefixed by one of the given directories. If
possible, it is recommended that the
.B \-\-secure
flag is used to set up a chroot() environment for the server to run in