Remove workaround for autoconf 2.52
[tftp-hpa.git] / tftpd / remap.c
blobcdb90629333922cacc4a022676a7fa612dc85136
1 /* ----------------------------------------------------------------------- *
3 * Copyright 2001-2007 H. Peter Anvin - All Rights Reserved
5 * This program is free software available under the same license
6 * as the "OpenBSD" operating system, distributed at
7 * http://www.openbsd.org/.
9 * ----------------------------------------------------------------------- */
12 * remap.c
14 * Perform regular-expression based filename remapping.
17 #include "config.h" /* Must be included first! */
18 #include <ctype.h>
19 #include <syslog.h>
20 #include <regex.h>
22 #include "tftpd.h"
23 #include "remap.h"
25 #define DEADMAN_MAX_STEPS 1024 /* Timeout after this many steps */
26 #define MAXLINE 16384 /* Truncate a line at this many bytes */
28 #define RULE_REWRITE 0x01 /* This is a rewrite rule */
29 #define RULE_GLOBAL 0x02 /* Global rule (repeat until no match) */
30 #define RULE_EXIT 0x04 /* Exit after matching this rule */
31 #define RULE_RESTART 0x08 /* Restart at the top after matching this rule */
32 #define RULE_ABORT 0x10 /* Terminate processing with an error */
33 #define RULE_GETONLY 0x20 /* Applicable to GET only */
34 #define RULE_PUTONLY 0x40 /* Applicable to PUT only */
35 #define RULE_INVERSE 0x80 /* Execute if regex *doesn't* match */
37 struct rule {
38 struct rule *next;
39 int nrule;
40 int rule_flags;
41 regex_t rx;
42 const char *pattern;
45 static int xform_null(int c)
47 return c;
50 static int xform_toupper(int c)
52 return toupper(c);
55 static int xform_tolower(int c)
57 return tolower(c);
60 /* Do \-substitution. Call with string == NULL to get length only. */
61 static int genmatchstring(char *string, const char *pattern,
62 const char *input, const regmatch_t * pmatch,
63 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':
86 case '1':
87 case '2':
88 case '3':
89 case '4':
90 case '5':
91 case '6':
92 case '7':
93 case '8':
94 case '9':
95 n = pattern[1] - '0';
97 if (pmatch[n].rm_so != -1) {
98 mlen = pmatch[n].rm_eo - pmatch[n].rm_so;
99 len += mlen;
100 if (string) {
101 const char *p = input + pmatch[n].rm_so;
102 while (mlen--)
103 *string++ = xform(*p++);
106 break;
108 case 'L':
109 xform = xform_tolower;
110 break;
112 case 'U':
113 xform = xform_toupper;
114 break;
116 case 'E':
117 xform = xform_null;
118 break;
120 default:
121 if (macrosub && (sublen = macrosub(macro, string)) >= 0) {
122 while (sublen--) {
123 len++;
124 if (string) {
125 *string = xform(*string);
126 string++;
129 } else {
130 len++;
131 if (string)
132 *string++ = xform(pattern[1]);
135 pattern += 2;
136 } else {
137 len++;
138 if (string)
139 *string++ = xform(*pattern);
140 pattern++;
144 /* Copy section after match */
145 if (string) {
146 memcpy(string, input + pmatch[0].rm_eo, endbytes);
147 string[endbytes] = '\0';
150 return len;
154 * Extract a string terminated by non-escaped whitespace; ignoring
155 * leading whitespace. Consider an unescaped # to be a comment marker,
156 * functionally \n.
158 static int readescstring(char *buf, char **str)
160 char *p = *str;
161 int wasbs = 0, len = 0;
163 while (*p && isspace(*p))
164 p++;
166 if (!*p) {
167 *buf = '\0';
168 *str = p;
169 return 0;
172 while (*p) {
173 if (!wasbs && (isspace(*p) || *p == '#')) {
174 *buf = '\0';
175 *str = p;
176 return len;
178 /* Important: two backslashes leave us in the !wasbs state! */
179 wasbs = !wasbs && (*p == '\\');
180 *buf++ = *p++;
181 len++;
184 *buf = '\0';
185 *str = p;
186 return len;
189 /* Parse a line into a set of instructions */
190 static int parseline(char *line, struct rule *r, int lineno)
192 char buffer[MAXLINE];
193 char *p;
194 int rv;
195 int rxflags = REG_EXTENDED;
196 static int nrule;
198 memset(r, 0, sizeof *r);
199 r->nrule = nrule;
201 if (!readescstring(buffer, &line))
202 return 0; /* No rule found */
204 for (p = buffer; *p; p++) {
205 switch (*p) {
206 case 'r':
207 r->rule_flags |= RULE_REWRITE;
208 break;
209 case 'g':
210 r->rule_flags |= RULE_GLOBAL;
211 break;
212 case 'e':
213 r->rule_flags |= RULE_EXIT;
214 break;
215 case 's':
216 r->rule_flags |= RULE_RESTART;
217 break;
218 case 'a':
219 r->rule_flags |= RULE_ABORT;
220 break;
221 case 'i':
222 rxflags |= REG_ICASE;
223 break;
224 case 'G':
225 r->rule_flags |= RULE_GETONLY;
226 break;
227 case 'P':
228 r->rule_flags |= RULE_PUTONLY;
229 break;
230 case '~':
231 r->rule_flags |= RULE_INVERSE;
232 break;
233 default:
234 syslog(LOG_ERR,
235 "Remap command \"%s\" on line %d contains invalid char \"%c\"",
236 buffer, lineno, *p);
237 return -1; /* Error */
238 break;
242 /* RULE_GLOBAL only applies when RULE_REWRITE specified */
243 if (!(r->rule_flags & RULE_REWRITE))
244 r->rule_flags &= ~RULE_GLOBAL;
246 if ((r->rule_flags & (RULE_INVERSE | RULE_REWRITE)) ==
247 (RULE_INVERSE | RULE_REWRITE)) {
248 syslog(LOG_ERR, "r rules cannot be inverted, line %d: %s\n",
249 lineno, line);
250 return -1; /* Error */
253 /* Read and compile the regex */
254 if (!readescstring(buffer, &line)) {
255 syslog(LOG_ERR, "No regex on remap line %d: %s\n", lineno, line);
256 return -1; /* Error */
259 if ((rv = regcomp(&r->rx, buffer, rxflags)) != 0) {
260 char errbuf[BUFSIZ];
261 regerror(rv, &r->rx, errbuf, BUFSIZ);
262 syslog(LOG_ERR, "Bad regex in remap line %d: %s\n", lineno,
263 errbuf);
264 return -1; /* Error */
267 /* Read the rewrite pattern, if any */
268 if (readescstring(buffer, &line)) {
269 r->pattern = tfstrdup(buffer);
270 } else {
271 r->pattern = "";
274 nrule++;
275 return 1; /* Rule found */
278 /* Read a rule file */
279 struct rule *parserulefile(FILE * f)
281 char line[MAXLINE];
282 struct rule *first_rule = NULL;
283 struct rule **last_rule = &first_rule;
284 struct rule *this_rule = tfmalloc(sizeof(struct rule));
285 int rv;
286 int lineno = 0;
287 int err = 0;
289 while (lineno++, fgets(line, MAXLINE, f)) {
290 rv = parseline(line, this_rule, lineno);
291 if (rv < 0)
292 err = 1;
293 if (rv > 0) {
294 *last_rule = this_rule;
295 last_rule = &this_rule->next;
296 this_rule = tfmalloc(sizeof(struct rule));
300 free(this_rule); /* Last one is always unused */
302 if (err) {
303 /* Bail on error, we have already logged an error message */
304 exit(EX_CONFIG);
307 return first_rule;
310 /* Destroy a rule file data structure */
311 void freerules(struct rule *r)
313 struct rule *next;
315 while (r) {
316 next = r->next;
318 regfree(&r->rx);
320 /* "" patterns aren't allocated by malloc() */
321 if (r->pattern && *r->pattern)
322 free((void *)r->pattern);
324 free(r);
326 r = next;
330 /* Execute a rule set on a string; returns a malloc'd new string. */
331 char *rewrite_string(const char *input, const struct rule *rules,
332 int is_put, match_pattern_callback macrosub,
333 const char **errmsg)
335 char *current = tfstrdup(input);
336 char *newstr;
337 const struct rule *ruleptr = rules;
338 regmatch_t pmatch[10];
339 int len;
340 int was_match = 0;
341 int deadman = DEADMAN_MAX_STEPS;
343 /* Default error */
344 *errmsg = "Remap table failure";
346 if (verbosity >= 3) {
347 syslog(LOG_INFO, "remap: input: %s", current);
350 for (ruleptr = rules; ruleptr; ruleptr = ruleptr->next) {
351 if (((ruleptr->rule_flags & RULE_GETONLY) && is_put) ||
352 ((ruleptr->rule_flags & RULE_PUTONLY) && !is_put)) {
353 continue; /* Rule not applicable, try next */
356 if (!deadman--) {
357 syslog(LOG_WARNING,
358 "remap: Breaking loop, input = %s, last = %s", input,
359 current);
360 free(current);
361 return NULL; /* Did not terminate! */
364 do {
365 if (regexec(&ruleptr->rx, current, 10, pmatch, 0) ==
366 (ruleptr->rule_flags & RULE_INVERSE ? REG_NOMATCH : 0)) {
367 /* Match on this rule */
368 was_match = 1;
370 if (ruleptr->rule_flags & RULE_INVERSE) {
371 /* No actual match, so clear out the pmatch array */
372 int i;
373 for (i = 0; i < 10; i++)
374 pmatch[i].rm_so = pmatch[i].rm_eo = -1;
377 if (ruleptr->rule_flags & RULE_ABORT) {
378 if (verbosity >= 3) {
379 syslog(LOG_INFO, "remap: rule %d: abort: %s",
380 ruleptr->nrule, current);
382 if (ruleptr->pattern[0]) {
383 /* Custom error message */
384 len =
385 genmatchstring(NULL, ruleptr->pattern, current,
386 pmatch, macrosub);
387 newstr = tfmalloc(len + 1);
388 genmatchstring(newstr, ruleptr->pattern, current,
389 pmatch, macrosub);
390 *errmsg = newstr;
391 } else {
392 *errmsg = NULL;
394 free(current);
395 return (NULL);
398 if (ruleptr->rule_flags & RULE_REWRITE) {
399 len = genmatchstring(NULL, ruleptr->pattern, current,
400 pmatch, macrosub);
401 newstr = tfmalloc(len + 1);
402 genmatchstring(newstr, ruleptr->pattern, current,
403 pmatch, macrosub);
404 free(current);
405 current = newstr;
406 if (verbosity >= 3) {
407 syslog(LOG_INFO, "remap: rule %d: rewrite: %s",
408 ruleptr->nrule, current);
411 } else {
412 break; /* No match, terminate unconditionally */
414 /* If the rule is global, keep going until no match */
415 } while (ruleptr->rule_flags & RULE_GLOBAL);
417 if (was_match) {
418 was_match = 0;
420 if (ruleptr->rule_flags & RULE_EXIT) {
421 if (verbosity >= 3) {
422 syslog(LOG_INFO, "remap: rule %d: exit",
423 ruleptr->nrule);
425 return current; /* Exit here, we're done */
426 } else if (ruleptr->rule_flags & RULE_RESTART) {
427 ruleptr = rules; /* Start from the top */
428 if (verbosity >= 3) {
429 syslog(LOG_INFO, "remap: rule %d: restart",
430 ruleptr->nrule);
436 if (verbosity >= 3) {
437 syslog(LOG_INFO, "remap: done");
439 return current;