/* $Id$ */ /* ----------------------------------------------------------------------- * * * Copyright 2001-2002 H. Peter Anvin - All Rights Reserved * * This program is free software available under the same license * as the "OpenBSD" operating system, distributed at * http://www.openbsd.org/. * * ----------------------------------------------------------------------- */ /* * remap.c * * Perform regular-expression based filename remapping. */ #include "config.h" /* Must be included first! */ #include #include #include #include "tftpd.h" #include "remap.h" #define DEADMAN_MAX_STEPS 1024 /* Timeout after this many steps */ #define MAXLINE 16384 /* Truncate a line at this many bytes */ #define RULE_REWRITE 0x01 /* This is a rewrite rule */ #define RULE_GLOBAL 0x02 /* Global rule (repeat until no match) */ #define RULE_EXIT 0x04 /* Exit after matching this rule */ #define RULE_RESTART 0x08 /* Restart at the top after matching this rule */ #define RULE_ABORT 0x10 /* Terminate processing with an error */ #define RULE_GETONLY 0x20 /* Applicable to GET only */ #define RULE_PUTONLY 0x40 /* Applicable to PUT only */ struct rule { struct rule *next; int nrule; int rule_flags; regex_t rx; const char *pattern; }; /* 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) { int len = 0; int n, mlen; int endbytes; /* Get section before match; note pmatch[0] is the whole match */ endbytes = strlen(input) - pmatch[0].rm_eo; len = pmatch[0].rm_so + endbytes; if ( string ) { memcpy(string, input, pmatch[0].rm_so); string += pmatch[0].rm_so; } /* Transform matched section */ while ( *pattern ) { if ( *pattern == '\\' && pattern[1] != '\0' ) { char macro = pattern[1]; if ( macro >= '0' && macro <= '9' ) { n = pattern[1] - '0'; if ( pmatch[n].rm_so != -1 ) { mlen = pmatch[n].rm_eo - pmatch[n].rm_so; len += mlen; if ( string ) { memcpy(string, input+pmatch[n].rm_so, mlen); string += mlen; } } } else { int sublen; if ( macrosub && (sublen = macrosub(macro, string)) >= 0 ) { len += sublen; if ( string ) string += sublen; } else { len++; if ( string ) *string++ = pattern[1]; } } pattern += 2; } else { len++; if ( string ) *string++ = *pattern; pattern++; } } /* Copy section after match */ if ( string ) { memcpy(string, input+pmatch[0].rm_eo, endbytes); string[endbytes] = '\0'; } return len; } /* * Extract a string terminated by non-escaped whitespace; ignoring * leading whitespace. Consider an unescaped # to be a comment marker, * functionally \n. */ static int readescstring(char *buf, char **str) { char *p = *str; int wasbs = 0, len = 0; while ( *p && isspace(*p) ) p++; if ( ! *p ) { *buf = '\0'; *str = p; return 0; } while ( *p ) { if ( !wasbs && (isspace(*p) || *p == '#') ) { *buf = '\0'; *str = p; return len; } /* Important: two backslashes leave us in the !wasbs state! */ wasbs = !wasbs && ( *p == '\\' ); *buf++ = *p++; len++; } *buf = '\0'; *str = p; return len; } /* Parse a line into a set of instructions */ static int parseline(char *line, struct rule *r, int lineno) { char buffer[MAXLINE]; char *p; int rv; int rxflags = REG_EXTENDED; static int nrule; memset(r, 0, sizeof *r); r->nrule = nrule; if ( !readescstring(buffer, &line) ) return 0; /* No rule found */ for ( p = buffer ; *p ; p++ ) { switch(*p) { case 'r': r->rule_flags |= RULE_REWRITE; break; case 'g': r->rule_flags |= RULE_GLOBAL; break; case 'e': r->rule_flags |= RULE_EXIT; break; case 's': r->rule_flags |= RULE_RESTART; break; case 'a': r->rule_flags |= RULE_ABORT; break; case 'i': rxflags |= REG_ICASE; break; case 'G': r->rule_flags |= RULE_GETONLY; break; case 'P': r->rule_flags |= RULE_PUTONLY; break; default: syslog(LOG_ERR, "Remap command \"%s\" on line %d contains invalid char \"%c\"", buffer, lineno, *p); return -1; /* Error */ break; } } /* RULE_GLOBAL only applies when RULE_REWRITE specified */ if ( !(r->rule_flags & RULE_REWRITE) ) r->rule_flags &= ~RULE_GLOBAL; /* Read and compile the regex */ if ( !readescstring(buffer, &line) ) { syslog(LOG_ERR, "No regex on remap line %d: %s\n", lineno, line); return -1; /* Error */ } if ( (rv = regcomp(&r->rx, buffer, rxflags)) != 0 ) { char errbuf[BUFSIZ]; regerror(rv, &r->rx, errbuf, BUFSIZ); syslog(LOG_ERR, "Bad regex in remap line %d: %s\n", lineno, errbuf); return -1; /* Error */ } /* Read the rewrite pattern, if any */ if ( readescstring(buffer, &line) ) { r->pattern = tfstrdup(buffer); } else { r->pattern = ""; } nrule++; return 1; /* Rule found */ } /* Read a rule file */ struct rule *parserulefile(FILE *f) { char line[MAXLINE]; struct rule *first_rule = NULL; struct rule **last_rule = &first_rule; struct rule *this_rule = tfmalloc(sizeof(struct rule)); int rv; int lineno = 0; int err = 0; while ( lineno++, fgets(line, MAXLINE, f) ) { rv = parseline(line, this_rule, lineno); if ( rv < 0 ) err = 1; if ( rv > 0 ) { *last_rule = this_rule; last_rule = &this_rule->next; this_rule = tfmalloc(sizeof(struct rule)); } } free(this_rule); /* Last one is always unused */ if ( err ) { /* Bail on error, we have already logged an error message */ exit(EX_CONFIG); } return first_rule; } /* Destroy a rule file data structure */ void freerules(struct rule *r) { struct rule *next; while ( r ) { next = r->next; regfree(&r->rx); /* "" patterns aren't allocated by malloc() */ if ( r->pattern && *r->pattern ) free((void *)r->pattern); free(r); r = next; } } /* Execute a rule set on a string; returns a malloc'd new string. */ char *rewrite_string(const char *input, const struct rule *rules, int is_put, match_pattern_callback macrosub, const char **errmsg) { char *current = tfstrdup(input); char *newstr; const struct rule *ruleptr = rules; regmatch_t pmatch[10]; int len; int was_match = 0; int deadman = DEADMAN_MAX_STEPS; /* Default error */ *errmsg = "Remap table failure"; if ( verbosity >= 3 ) { syslog(LOG_INFO, "remap: input: %s", current); } for ( ruleptr = rules ; ruleptr ; ruleptr = ruleptr->next ) { if ( ((ruleptr->rule_flags & RULE_GETONLY) && is_put) || ((ruleptr->rule_flags & RULE_PUTONLY) && !is_put) ) { continue; /* Rule not applicable, try next */ } if ( ! deadman-- ) { syslog(LOG_WARNING, "remap: Breaking loop, input = %s, last = %s", input, current); free(current); return NULL; /* Did not terminate! */ } do { if ( regexec(&ruleptr->rx, current, 10, pmatch, 0) == 0 ) { /* Match on this rule */ was_match = 1; if ( ruleptr->rule_flags & RULE_ABORT ) { if ( verbosity >= 3 ) { syslog(LOG_INFO, "remap: rule %d: abort: %s", ruleptr->nrule, current); } 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); *errmsg = newstr; } else { *errmsg = NULL; } free(current); return(NULL); } 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); free(current); current = newstr; if ( verbosity >= 3 ) { syslog(LOG_INFO, "remap: rule %d: rewrite: %s", ruleptr->nrule, current); } } } 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; if ( ruleptr->rule_flags & RULE_EXIT ) { 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 ( verbosity >= 3 ) { syslog(LOG_INFO, "remap: done"); } return current; }