Document user-visible changes
[tftp-hpa.git] / tftpd / remap.c
blobdda563202a902f600915d503ac72cdd94cb9c1bc
1 /* $Id$ */
2 /* ----------------------------------------------------------------------- *
3 *
4 * Copyright 2001-2004 H. Peter Anvin - All Rights Reserved
6 * This program is free software available under the same license
7 * as the "OpenBSD" operating system, distributed at
8 * http://www.openbsd.org/.
10 * ----------------------------------------------------------------------- */
13 * remap.c
15 * Perform regular-expression based filename remapping.
18 #include "config.h" /* Must be included first! */
19 #include <ctype.h>
20 #include <syslog.h>
21 #include <regex.h>
23 #include "tftpd.h"
24 #include "remap.h"
26 #define DEADMAN_MAX_STEPS 1024 /* Timeout after this many steps */
27 #define MAXLINE 16384 /* Truncate a line at this many bytes */
29 #define RULE_REWRITE 0x01 /* This is a rewrite rule */
30 #define RULE_GLOBAL 0x02 /* Global rule (repeat until no match) */
31 #define RULE_EXIT 0x04 /* Exit after matching this rule */
32 #define RULE_RESTART 0x08 /* Restart at the top after matching this rule */
33 #define RULE_ABORT 0x10 /* Terminate processing with an error */
34 #define RULE_GETONLY 0x20 /* Applicable to GET only */
35 #define RULE_PUTONLY 0x40 /* Applicable to PUT only */
36 #define RULE_INVERSE 0x80 /* Execute if regex *doesn't* match */
38 struct rule {
39 struct rule *next;
40 int nrule;
41 int rule_flags;
42 regex_t rx;
43 const char *pattern;
46 static int xform_null(int c)
48 return c;
51 static int xform_toupper(int c)
53 return toupper(c);
56 static int xform_tolower(int c)
58 return tolower(c);
61 /* Do \-substitution. Call with string == NULL to get length only. */
62 static int genmatchstring(char *string, const char *pattern, const char *input,
63 const regmatch_t *pmatch, match_pattern_callback macrosub)
65 int (*xform)(int) = xform_null;
66 int len = 0;
67 int n, mlen, sublen;
68 int endbytes;
70 /* Get section before match; note pmatch[0] is the whole match */
71 endbytes = strlen(input) - pmatch[0].rm_eo;
72 len = pmatch[0].rm_so + endbytes;
73 if ( string ) {
74 memcpy(string, input, pmatch[0].rm_so);
75 string += pmatch[0].rm_so;
78 /* Transform matched section */
79 while ( *pattern ) {
80 mlen = 0;
82 if ( *pattern == '\\' && pattern[1] != '\0' ) {
83 char macro = pattern[1];
84 switch ( macro ) {
85 case '0': case '1': case '2': case '3': case '4':
86 case '5': case '6': case '7': case '8': case '9':
87 n = pattern[1] - '0';
89 if ( pmatch[n].rm_so != -1 ) {
90 mlen = pmatch[n].rm_eo - pmatch[n].rm_so;
91 len += mlen;
92 if ( string ) {
93 const char *p = input+pmatch[n].rm_so;
94 while ( mlen-- )
95 *string++ = xform(*p++);
98 break;
100 case 'L':
101 xform = xform_tolower;
102 break;
104 case 'U':
105 xform = xform_toupper;
106 break;
108 case 'E':
109 xform = xform_null;
110 break;
112 default:
113 if ( macrosub &&
114 (sublen = macrosub(macro, string)) >= 0 ) {
115 while ( sublen-- ) {
116 len++;
117 if ( string ) {
118 *string = xform(*string);
119 string++;
122 } else {
123 len++;
124 if ( string )
125 *string++ = xform(pattern[1]);
128 pattern += 2;
129 } else {
130 len++;
131 if ( string )
132 *string++ = xform(*pattern);
133 pattern++;
137 /* Copy section after match */
138 if ( string ) {
139 memcpy(string, input+pmatch[0].rm_eo, endbytes);
140 string[endbytes] = '\0';
143 return len;
147 * Extract a string terminated by non-escaped whitespace; ignoring
148 * leading whitespace. Consider an unescaped # to be a comment marker,
149 * functionally \n.
151 static int readescstring(char *buf, char **str)
153 char *p = *str;
154 int wasbs = 0, len = 0;
156 while ( *p && isspace(*p) )
157 p++;
159 if ( ! *p ) {
160 *buf = '\0';
161 *str = p;
162 return 0;
165 while ( *p ) {
166 if ( !wasbs && (isspace(*p) || *p == '#') ) {
167 *buf = '\0';
168 *str = p;
169 return len;
171 /* Important: two backslashes leave us in the !wasbs state! */
172 wasbs = !wasbs && ( *p == '\\' );
173 *buf++ = *p++;
174 len++;
177 *buf = '\0';
178 *str = p;
179 return len;
182 /* Parse a line into a set of instructions */
183 static int parseline(char *line, struct rule *r, int lineno)
185 char buffer[MAXLINE];
186 char *p;
187 int rv;
188 int rxflags = REG_EXTENDED;
189 static int nrule;
191 memset(r, 0, sizeof *r);
192 r->nrule = nrule;
194 if ( !readescstring(buffer, &line) )
195 return 0; /* No rule found */
197 for ( p = buffer ; *p ; p++ ) {
198 switch(*p) {
199 case 'r':
200 r->rule_flags |= RULE_REWRITE;
201 break;
202 case 'g':
203 r->rule_flags |= RULE_GLOBAL;
204 break;
205 case 'e':
206 r->rule_flags |= RULE_EXIT;
207 break;
208 case 's':
209 r->rule_flags |= RULE_RESTART;
210 break;
211 case 'a':
212 r->rule_flags |= RULE_ABORT;
213 break;
214 case 'i':
215 rxflags |= REG_ICASE;
216 break;
217 case 'G':
218 r->rule_flags |= RULE_GETONLY;
219 break;
220 case 'P':
221 r->rule_flags |= RULE_PUTONLY;
222 break;
223 case '~':
224 r->rule_flags |= RULE_INVERSE;
225 break;
226 default:
227 syslog(LOG_ERR, "Remap command \"%s\" on line %d contains invalid char \"%c\"",
228 buffer, lineno, *p);
229 return -1; /* Error */
230 break;
234 /* RULE_GLOBAL only applies when RULE_REWRITE specified */
235 if ( !(r->rule_flags & RULE_REWRITE) )
236 r->rule_flags &= ~RULE_GLOBAL;
238 if ( (r->rule_flags & (RULE_INVERSE|RULE_REWRITE)) ==
239 (RULE_INVERSE|RULE_REWRITE) ) {
240 syslog(LOG_ERR, "r rules cannot be inverted, line %d: %s\n", lineno, line);
241 return -1; /* Error */
244 /* Read and compile the regex */
245 if ( !readescstring(buffer, &line) ) {
246 syslog(LOG_ERR, "No regex on remap line %d: %s\n", lineno, line);
247 return -1; /* Error */
250 if ( (rv = regcomp(&r->rx, buffer, rxflags)) != 0 ) {
251 char errbuf[BUFSIZ];
252 regerror(rv, &r->rx, errbuf, BUFSIZ);
253 syslog(LOG_ERR, "Bad regex in remap line %d: %s\n", lineno, errbuf);
254 return -1; /* Error */
257 /* Read the rewrite pattern, if any */
258 if ( readescstring(buffer, &line) ) {
259 r->pattern = tfstrdup(buffer);
260 } else {
261 r->pattern = "";
264 nrule++;
265 return 1; /* Rule found */
268 /* Read a rule file */
269 struct rule *parserulefile(FILE *f)
271 char line[MAXLINE];
272 struct rule *first_rule = NULL;
273 struct rule **last_rule = &first_rule;
274 struct rule *this_rule = tfmalloc(sizeof(struct rule));
275 int rv;
276 int lineno = 0;
277 int err = 0;
279 while ( lineno++, fgets(line, MAXLINE, f) ) {
280 rv = parseline(line, this_rule, lineno);
281 if ( rv < 0 )
282 err = 1;
283 if ( rv > 0 ) {
284 *last_rule = this_rule;
285 last_rule = &this_rule->next;
286 this_rule = tfmalloc(sizeof(struct rule));
290 free(this_rule); /* Last one is always unused */
292 if ( err ) {
293 /* Bail on error, we have already logged an error message */
294 exit(EX_CONFIG);
297 return first_rule;
300 /* Destroy a rule file data structure */
301 void freerules(struct rule *r)
303 struct rule *next;
305 while ( r ) {
306 next = r->next;
308 regfree(&r->rx);
310 /* "" patterns aren't allocated by malloc() */
311 if ( r->pattern && *r->pattern )
312 free((void *)r->pattern);
314 free(r);
316 r = next;
320 /* Execute a rule set on a string; returns a malloc'd new string. */
321 char *rewrite_string(const char *input, const struct rule *rules,
322 int is_put, match_pattern_callback macrosub,
323 const char **errmsg)
325 char *current = tfstrdup(input);
326 char *newstr;
327 const struct rule *ruleptr = rules;
328 regmatch_t pmatch[10];
329 int len;
330 int was_match = 0;
331 int deadman = DEADMAN_MAX_STEPS;
333 /* Default error */
334 *errmsg = "Remap table failure";
336 if ( verbosity >= 3 ) {
337 syslog(LOG_INFO, "remap: input: %s", current);
340 for ( ruleptr = rules ; ruleptr ; ruleptr = ruleptr->next ) {
341 if ( ((ruleptr->rule_flags & RULE_GETONLY) && is_put) ||
342 ((ruleptr->rule_flags & RULE_PUTONLY) && !is_put) ) {
343 continue; /* Rule not applicable, try next */
346 if ( ! deadman-- ) {
347 syslog(LOG_WARNING, "remap: Breaking loop, input = %s, last = %s",
348 input, current);
349 free(current);
350 return NULL; /* Did not terminate! */
353 do {
354 if ( regexec(&ruleptr->rx, current, 10, pmatch, 0) ==
355 (ruleptr->rule_flags & RULE_INVERSE ? REG_NOMATCH : 0) ) {
356 /* Match on this rule */
357 was_match = 1;
359 if ( ruleptr->rule_flags & RULE_INVERSE ) {
360 /* No actual match, so clear out the pmatch array */
361 int i;
362 for ( i = 0 ; i < 10 ; i++ )
363 pmatch[i].rm_so = pmatch[i].rm_eo = -1;
366 if ( ruleptr->rule_flags & RULE_ABORT ) {
367 if ( verbosity >= 3 ) {
368 syslog(LOG_INFO, "remap: rule %d: abort: %s",
369 ruleptr->nrule, current);
371 if ( ruleptr->pattern[0] ) {
372 /* Custom error message */
373 len = genmatchstring(NULL, ruleptr->pattern, current,
374 pmatch, macrosub);
375 newstr = tfmalloc(len+1);
376 genmatchstring(newstr, ruleptr->pattern, current,
377 pmatch, macrosub);
378 *errmsg = newstr;
379 } else {
380 *errmsg = NULL;
382 free(current);
383 return(NULL);
386 if ( ruleptr->rule_flags & RULE_REWRITE ) {
387 len = genmatchstring(NULL, ruleptr->pattern, current,
388 pmatch, macrosub);
389 newstr = tfmalloc(len+1);
390 genmatchstring(newstr, ruleptr->pattern, current,
391 pmatch, macrosub);
392 free(current);
393 current = newstr;
394 if ( verbosity >= 3 ) {
395 syslog(LOG_INFO, "remap: rule %d: rewrite: %s",
396 ruleptr->nrule, current);
399 } else {
400 break; /* No match, terminate unconditionally */
402 /* If the rule is global, keep going until no match */
403 } while ( ruleptr->rule_flags & RULE_GLOBAL );
405 if ( was_match ) {
406 was_match = 0;
408 if ( ruleptr->rule_flags & RULE_EXIT ) {
409 if ( verbosity >= 3 ) {
410 syslog(LOG_INFO, "remap: rule %d: exit", ruleptr->nrule);
412 return current; /* Exit here, we're done */
413 } else if ( ruleptr->rule_flags & RULE_RESTART ) {
414 ruleptr = rules; /* Start from the top */
415 if ( verbosity >= 3 ) {
416 syslog(LOG_INFO, "remap: rule %d: restart", ruleptr->nrule);
422 if ( verbosity >= 3 ) {
423 syslog(LOG_INFO, "remap: done");
425 return current;