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 "tftpd.h"
#include "remap.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 MAXLINE 16384 /* Truncate a line at this many bytes */
#define RULE_REWRITE 0x01 /* This is a rewrite rule */ #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_HASFILE 0x100 /* Valid if rule results in a valid filename */
#define RULE_RRQ 0x200 /* Get (read) only */ #define RULE_RRQ 0x200 /* Get (read) only */
#define RULE_WRQ 0x400 /* Put (write) only */ #define RULE_WRQ 0x400 /* Put (write) only */
#define RULE_SEDG 0x800 /* sed-style global */
struct rule { struct rule {
struct rule *next; struct rule *next;
@ -61,22 +62,30 @@ static int xform_tolower(int c)
return tolower(c); return tolower(c);
} }
/* Do \-substitution. Call with string == NULL to get length only. */ /*
static int genmatchstring(char *string, const char *pattern, * Do \-substitution. Call with string == NULL to get length only.
const char *input, const regmatch_t * pmatch, * "start" indicates an offset into the input buffer where the pattern
match_pattern_callback macrosub) * 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 (*xform) (int) = xform_null;
int len = 0; int len = 0;
int n, mlen, sublen; int n, mlen, sublen;
int endbytes; int endbytes;
const char *input = ibuf + start;
/* Get section before match; note pmatch[0] is the whole match */ /* Get section before match; note pmatch[0] is the whole match */
endbytes = strlen(input) - pmatch[0].rm_eo; endbytes = strlen(input) - pmatch[0].rm_eo;
len = pmatch[0].rm_so + endbytes; len = start + pmatch[0].rm_so;
if (string) { if (string) {
memcpy(string, input, pmatch[0].rm_so); /* Copy the prefix before "start" as well! */
string += pmatch[0].rm_so; memcpy(string, ibuf, start + pmatch[0].rm_so);
string += start + pmatch[0].rm_so;
} }
/* Transform matched section */ /* Transform matched section */
@ -102,7 +111,7 @@ static int genmatchstring(char *string, const char *pattern,
mlen = pmatch[n].rm_eo - pmatch[n].rm_so; mlen = pmatch[n].rm_eo - pmatch[n].rm_so;
len += mlen; len += mlen;
if (string) { if (string) {
const char *p = input + pmatch[n].rm_so; const char *p = input + start + pmatch[n].rm_so;
while (mlen--) while (mlen--)
*string++ = xform(*p++); *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 */ /* Copy section after match */
len += endbytes;
if (string) { if (string) {
memcpy(string, input + pmatch[0].rm_eo, endbytes); memcpy(string, input + pmatch[0].rm_eo, endbytes);
string[endbytes] = '\0'; string[endbytes] = '\0';
@ -154,6 +168,26 @@ static int genmatchstring(char *string, const char *pattern,
return len; 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 * Extract a string terminated by non-escaped whitespace; ignoring
* leading whitespace. Consider an unescaped # to be a comment marker, * leading whitespace. Consider an unescaped # to be a comment marker,
@ -211,6 +245,9 @@ static int parseline(char *line, struct rule *r, int lineno)
r->rule_flags |= RULE_REWRITE; r->rule_flags |= RULE_REWRITE;
break; break;
case 'g': case 'g':
if (r->rule_flags & RULE_GLOBAL)
r->rule_flags |= RULE_SEDG;
else
r->rule_flags |= RULE_GLOBAL; r->rule_flags |= RULE_GLOBAL;
break; break;
case 'e': case 'e':
@ -258,15 +295,15 @@ static int parseline(char *line, struct rule *r, int lineno)
lineno, line); lineno, line);
return -1; /* Error */ return -1; /* Error */
} }
if ((r->rule_flags & (RULE_GLOBAL|RULE_HASFILE)) if ((r->rule_flags & (RULE_GLOBAL|RULE_SEDG|RULE_HASFILE))
== (RULE_GLOBAL|RULE_HASFILE)) { == (RULE_GLOBAL|RULE_HASFILE)) {
syslog(LOG_ERR, "E rules cannot be combined with g, line %d: %s\n", syslog(LOG_ERR, "E rules cannot be combined with g (but gg is OK), line %d: %s\n",
lineno, line); lineno, line);
return -1; /* Error */ return -1; /* Error */
} }
} else { } else {
/* RULE_GLOBAL is meaningless without RULE_REWRITE */ /* RULE_GLOBAL and RULE_SEDG are meaningless without RULE_REWRITE */
r->rule_flags &= ~RULE_GLOBAL; r->rule_flags &= ~(RULE_GLOBAL|RULE_SEDG);
} }
/* Read and compile the regex */ /* Read and compile the regex */
@ -353,14 +390,18 @@ char *rewrite_string(const struct formats *pf,
const char **errmsg) const char **errmsg)
{ {
char *current = tfstrdup(input); char *current = tfstrdup(input);
char *newstr; char *newstr, *newerstr;
const char *accerr; const char *accerr;
const struct rule *ruleptr = rules; const struct rule *ruleptr = rules;
regmatch_t pmatch[10]; regmatch_t pmatch[10];
int len; int i;
int len, newlen;
int was_match = 0; int was_match = 0;
int deadman = DEADMAN_MAX_STEPS; int deadman = DEADMAN_MAX_STEPS;
int matchsense;
int pmatches;
unsigned int bad_flags; unsigned int bad_flags;
int ggoffset;
/* Default error */ /* Default error */
*errmsg = "Remap table failure"; *errmsg = "Remap table failure";
@ -379,26 +420,21 @@ char *rewrite_string(const struct formats *pf,
if (ruleptr->rule_flags & bad_flags) if (ruleptr->rule_flags & bad_flags)
continue; /* This rule is excluded by flags */ continue; /* This rule is excluded by flags */
if (!deadman--) { matchsense = ruleptr->rule_flags & RULE_INVERSE ? REG_NOMATCH : 0;
syslog(LOG_WARNING, pmatches = ruleptr->rule_flags & RULE_INVERSE ? 0 : 10;
"remap: Breaking loop, input = %s, last = %s", input,
current);
free(current);
return NULL; /* Did not terminate! */
}
do { /* Clear the pmatch[] array */
if (regexec(&ruleptr->rx, current, 10, pmatch, 0) ==
(ruleptr->rule_flags & RULE_INVERSE ? REG_NOMATCH : 0)) {
/* 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++) for (i = 0; i < 10; i++)
pmatch[i].rm_so = pmatch[i].rm_eo = -1; pmatch[i].rm_so = pmatch[i].rm_eo = -1;
}
do {
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_ABORT) { if (ruleptr->rule_flags & RULE_ABORT) {
if (verbosity >= 3) { if (verbosity >= 3) {
@ -407,12 +443,8 @@ char *rewrite_string(const struct formats *pf,
} }
if (ruleptr->pattern[0]) { if (ruleptr->pattern[0]) {
/* Custom error message */ /* Custom error message */
len = genmatchstring(&newstr, ruleptr->pattern, current,
genmatchstring(NULL, ruleptr->pattern, current, pmatch, macrosub, 0, NULL);
pmatch, macrosub);
newstr = tfmalloc(len + 1);
genmatchstring(newstr, ruleptr->pattern, current,
pmatch, macrosub);
*errmsg = newstr; *errmsg = newstr;
} else { } else {
*errmsg = NULL; *errmsg = NULL;
@ -422,11 +454,29 @@ char *rewrite_string(const struct formats *pf,
} }
if (ruleptr->rule_flags & RULE_REWRITE) { if (ruleptr->rule_flags & RULE_REWRITE) {
len = genmatchstring(NULL, ruleptr->pattern, current, len = genmatchstring(&newstr, ruleptr->pattern, current,
pmatch, macrosub); pmatch, macrosub, 0, &ggoffset);
newstr = tfmalloc(len + 1);
genmatchstring(newstr, ruleptr->pattern, current, if (ruleptr->rule_flags & RULE_SEDG) {
pmatch, macrosub); /* 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) && if ((ruleptr->rule_flags & RULE_HASFILE) &&
pf->f_validate(newstr, mode, pf, &accerr)) { pf->f_validate(newstr, mode, pf, &accerr)) {
if (verbosity >= 3) { if (verbosity >= 3) {
@ -437,6 +487,7 @@ char *rewrite_string(const struct formats *pf,
was_match = 0; was_match = 0;
break; break;
} }
}
free(current); free(current);
current = newstr; current = newstr;
if (verbosity >= 3) { if (verbosity >= 3) {
@ -456,8 +507,8 @@ char *rewrite_string(const struct formats *pf,
} else { } else {
break; /* No match, terminate unconditionally */ break; /* No match, terminate unconditionally */
} }
/* If the rule is global, keep going until no match */ /* If the rule is (old-style) global, keep going until no match */
} while (ruleptr->rule_flags & RULE_GLOBAL); } while ((ruleptr->rule_flags & (RULE_GLOBAL|RULE_SEDG)) == RULE_GLOBAL);
if (was_match) { if (was_match) {
was_match = 0; was_match = 0;
@ -476,10 +527,16 @@ char *rewrite_string(const struct formats *pf,
} }
} }
} }
}
if (verbosity >= 3) { if (verbosity >= 3) {
syslog(LOG_INFO, "remap: done"); syslog(LOG_INFO, "remap: done");
} }
return current; 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

@ -273,6 +273,17 @@ The replacement pattern may contain escape sequences; see below.
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 . .BR r .
.TP .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 .B i
Match the Match the
.I regex .I regex
@ -288,7 +299,9 @@ end rule processing after executing the rule. If this is combined with
.BR r , .BR r ,
then if the substitution does \fInot\fP result in a valid filename, then if the substitution does \fInot\fP result in a valid filename,
the substitution is undone. This cannot be combined with the substitution is undone. This cannot be combined with
.BR g . .BR g ,
but \fIcan\fP be combined with
.BR gg .
.TP .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
@ -400,7 +413,7 @@ Access to files can, and should, be restricted by invoking
.B tftpd .B tftpd
with a list of directories by including pathnames as server program with a list of directories by including pathnames as server program
arguments on the command line. In this case access is restricted to 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 possible, it is recommended that the
.B \-\-secure .B \-\-secure
flag is used to set up a chroot() environment for the server to run in flag is used to set up a chroot() environment for the server to run in