Major overhaul of the portability stuff; port to autoconf 2.52
[tftp-hpa.git] / tftpd / remap.c
blobee93c98fd84bb298a1b6f6bd4f6a7bbf5ad87f80
1 /* $Id$ */
2 /* ----------------------------------------------------------------------- *
3 *
4 * Copyright 2001 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 */
37 struct rule {
38 struct rule *next;
39 int nrule;
40 int rule_flags;
41 regex_t rx;
42 const char *pattern;
45 /* Do \-substitution. Call with string == NULL to get length only. */
46 static int genmatchstring(char *string, const char *pattern, const char *input, const regmatch_t *pmatch)
48 int len = 0;
49 int n, mlen;
50 int endbytes;
52 /* Get section before match; note pmatch[0] is the whole match */
53 endbytes = strlen(input) - pmatch[0].rm_eo;
54 len = pmatch[0].rm_so + endbytes;
55 if ( string ) {
56 memcpy(string, input, pmatch[0].rm_so);
57 string += pmatch[0].rm_so;
60 /* Transform matched section */
61 while ( *pattern ) {
62 if ( *pattern == '\\' && pattern[1] != '\0' ) {
63 if ( pattern[1] < '0' || pattern[1] > '9' ) {
64 len++;
65 if ( string )
66 *string++ = pattern[1];
67 } else {
68 n = pattern[1] - '0';
70 if ( pmatch[n].rm_so != -1 ) {
71 mlen = pmatch[n].rm_eo - pmatch[n].rm_so;
72 len += mlen;
73 if ( string ) {
74 memcpy(string, input+pmatch[n].rm_so, mlen);
75 string += mlen;
79 pattern += 2;
80 } else {
81 len++;
82 if ( string )
83 *string++ = *pattern;
84 pattern++;
88 /* Copy section after match */
89 if ( string ) {
90 memcpy(string, input+pmatch[0].rm_eo, endbytes);
91 string[endbytes] = '\0';
94 return len;
97 /* Extract a string terminated by non-escaped whitespace; ignore leading whitespace */
98 /* Consider an unescaped # to be a comment marker, functionally \n */
99 static int readescstring(char *buf, char **str)
101 char *p = *str;
102 int wasbs = 0, len = 0;
104 while ( *p && isspace(*p) )
105 p++;
107 if ( ! *p ) {
108 *buf = '\0';
109 *str = p;
110 return 0;
113 while ( *p ) {
114 if ( !wasbs && (isspace(*p) || *p == '#') ) {
115 *buf = '\0';
116 *str = p;
117 return len;
119 /* Important: two backslashes leave us in the !wasbs state! */
120 wasbs = !wasbs && ( *p == '\\' );
121 *buf++ = *p++;
122 len++;
125 *buf = '\0';
126 *str = p;
127 return len;
130 /* Parse a line into a set of instructions */
131 static int parseline(char *line, struct rule *r, int lineno)
133 char buffer[MAXLINE];
134 char *p;
135 int rv;
136 int rxflags = REG_EXTENDED;
137 static int nrule;
139 memset(r, 0, sizeof r);
140 r->nrule = nrule;
142 if ( !readescstring(buffer, &line) )
143 return 0; /* No rule found */
145 for ( p = buffer ; *p ; p++ ) {
146 switch(*p) {
147 case 'r':
148 r->rule_flags |= RULE_REWRITE;
149 break;
150 case 'g':
151 r->rule_flags |= RULE_GLOBAL;
152 break;
153 case 'e':
154 r->rule_flags |= RULE_EXIT;
155 break;
156 case 's':
157 r->rule_flags |= RULE_RESTART;
158 break;
159 case 'a':
160 r->rule_flags |= RULE_ABORT;
161 break;
162 case 'i':
163 rxflags |= REG_ICASE;
164 break;
165 case 'G':
166 r->rule_flags |= RULE_GETONLY;
167 break;
168 case 'P':
169 r->rule_flags |= RULE_PUTONLY;
170 break;
171 default:
172 syslog(LOG_ERR, "Remap command \"%s\" on line %d contains invalid char \"%c\"",
173 buffer, lineno, *p);
174 return -1; /* Error */
175 break;
179 /* RULE_GLOBAL only applies when RULE_REWRITE specified */
180 if ( !(r->rule_flags & RULE_REWRITE) )
181 r->rule_flags &= ~RULE_GLOBAL;
183 /* Read and compile the regex */
184 if ( !readescstring(buffer, &line) ) {
185 syslog(LOG_ERR, "No regex on remap line %d: %s\n", lineno, line);
186 return -1; /* Error */
189 if ( (rv = regcomp(&r->rx, buffer, rxflags)) != 0 ) {
190 char errbuf[BUFSIZ];
191 regerror(rv, &r->rx, errbuf, BUFSIZ);
192 syslog(LOG_ERR, "Bad regex in remap line %d: %s\n", lineno, errbuf);
193 return -1; /* Error */
196 /* Read the rewrite pattern, if any */
197 if ( readescstring(buffer, &line) ) {
198 r->pattern = tfstrdup(buffer);
199 } else {
200 r->pattern = "";
203 nrule++;
204 return 1; /* Rule found */
207 /* Read a rule file */
208 struct rule *parserulefile(FILE *f)
210 char line[MAXLINE];
211 struct rule *first_rule = NULL;
212 struct rule **last_rule = &first_rule;
213 struct rule *this_rule = tfmalloc(sizeof(struct rule));
214 int rv;
215 int lineno = 0;
216 int err = 0;
218 while ( lineno++, fgets(line, MAXLINE, f) ) {
219 rv = parseline(line, this_rule, lineno);
220 if ( rv < 0 )
221 err = 1;
222 if ( rv > 0 ) {
223 *last_rule = this_rule;
224 last_rule = &this_rule->next;
225 this_rule = tfmalloc(sizeof(struct rule));
229 free(this_rule); /* Last one is always unused */
231 if ( err ) {
232 /* Bail on error, we have already logged an error message */
233 exit(EX_CONFIG);
236 return first_rule;
239 /* Destroy a rule file data structure */
240 void freerules(struct rule *r)
242 struct rule *next;
244 while ( r ) {
245 next = r->next;
247 regfree(&r->rx);
249 /* "" patterns aren't allocated by malloc() */
250 if ( r->pattern && *r->pattern )
251 free((void *)r->pattern);
253 free(r);
255 r = next;
259 /* Execute a rule set on a string; returns a malloc'd new string. */
260 char *rewrite_string(const char *input, const struct rule *rules, int is_put)
262 char *current = tfstrdup(input);
263 char *newstr;
264 const struct rule *ruleptr = rules;
265 regmatch_t pmatch[10];
266 int len;
267 int was_match = 0;
268 int deadman = DEADMAN_MAX_STEPS;
270 if ( verbosity >= 3 ) {
271 syslog(LOG_INFO, "remap: input: %s", current);
274 for ( ruleptr = rules ; ruleptr ; ruleptr = ruleptr->next ) {
275 if ( ((ruleptr->rule_flags & RULE_GETONLY) && is_put) ||
276 ((ruleptr->rule_flags & RULE_PUTONLY) && !is_put) ) {
277 continue; /* Rule not applicable, try next */
280 if ( ! deadman-- ) {
281 syslog(LOG_WARNING, "remap: Breaking loop, input = %s, last = %s",
282 input, current);
283 free(current);
284 return NULL; /* Did not terminate! */
287 do {
288 if ( regexec(&ruleptr->rx, current, 10, pmatch, 0) == 0 ) {
289 /* Match on this rule */
290 was_match = 1;
292 if ( ruleptr->rule_flags & RULE_ABORT ) {
293 if ( verbosity >= 3 ) {
294 syslog(LOG_INFO, "remap: rule %d: abort: %s",
295 ruleptr->nrule, current);
297 free(current);
298 return(NULL);
301 if ( ruleptr->rule_flags & RULE_REWRITE ) {
302 len = genmatchstring(NULL, ruleptr->pattern, current, pmatch);
303 newstr = tfmalloc(len+1);
304 genmatchstring(newstr, ruleptr->pattern, current, pmatch);
305 free(current);
306 current = newstr;
307 if ( verbosity >= 3 ) {
308 syslog(LOG_INFO, "remap: rule %d: rewrite: %s",
309 ruleptr->nrule, current);
312 } else {
313 break; /* No match, terminate unconditionally */
315 /* If the rule is global, keep going until no match */
316 } while ( ruleptr->rule_flags & RULE_GLOBAL );
318 if ( was_match ) {
319 was_match = 0;
321 if ( ruleptr->rule_flags & RULE_EXIT ) {
322 if ( verbosity >= 3 ) {
323 syslog(LOG_INFO, "remap: rule %d: exit", ruleptr->nrule);
325 return current; /* Exit here, we're done */
326 } else if ( ruleptr->rule_flags & RULE_RESTART ) {
327 ruleptr = rules; /* Start from the top */
328 if ( verbosity >= 3 ) {
329 syslog(LOG_INFO, "remap: rule %d: restart", ruleptr->nrule);
335 if ( verbosity >= 3 ) {
336 syslog(LOG_INFO, "remap: done");
338 return current;