From ac7f98e4d8036d6194b41920e46e37be40b496a4 Mon Sep 17 00:00:00 2001 From: "H. Peter Anvin" Date: Mon, 10 Jun 2024 20:19:55 -0700 Subject: [PATCH] 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 --- tftpd/remap.c | 287 +++++++++++++++++++++++++++++++++---------------------- tftpd/tftpd.8.in | 21 +++- 2 files changed, 189 insertions(+), 119 deletions(-) diff --git a/tftpd/remap.c b/tftpd/remap.c index 16801c1..d74b2db 100644 --- a/tftpd/remap.c +++ b/tftpd/remap.c @@ -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'; @@ -155,6 +169,26 @@ static int genmatchstring(char *string, const char *pattern, } /* + * 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, * functionally \n. @@ -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 (!deadman--) { - syslog(LOG_WARNING, - "remap: Breaking loop, input = %s, last = %s", input, - current); - free(current); - return NULL; /* Did not terminate! */ - } + if (ruleptr->rule_flags & bad_flags) + continue; /* This rule is excluded by flags */ + + 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 */ - } - /* If the rule is global, keep going until no match */ - } while (ruleptr->rule_flags & RULE_GLOBAL); - - if (was_match) { - was_match = 0; + 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_EXIT|RULE_HASFILE)) { - if (verbosity >= 3) { - syslog(LOG_INFO, "remap: rule %d: exit", - ruleptr->nrule); + 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; + } } - return current; /* Exit here, we're done */ - } else if (ruleptr->rule_flags & RULE_RESTART) { - ruleptr = rules; /* Start from the top */ + free(current); + current = newstr; if (verbosity >= 3) { - syslog(LOG_INFO, "remap: rule %d: restart", - ruleptr->nrule); + 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 */ + } + /* 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 (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! */ } diff --git a/tftpd/tftpd.8.in b/tftpd/tftpd.8.in index c9722cf..9baf7fd 100644 --- a/tftpd/tftpd.8.in +++ b/tftpd/tftpd.8.in @@ -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 -- 2.11.4.GIT