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! */
}