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 * ----------------------------------------------------------------------- */
14 * Perform regular-expression based filename remapping.
17 #include "config.h" /* Must be included first! */
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 */
45 static int xform_null(int c
)
50 static int xform_toupper(int c
)
55 static int xform_tolower(int 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
;
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
;
74 memcpy(string
, input
, pmatch
[0].rm_so
);
75 string
+= pmatch
[0].rm_so
;
78 /* Transform matched section */
82 if (*pattern
== '\\' && pattern
[1] != '\0') {
83 char macro
= pattern
[1];
97 if (pmatch
[n
].rm_so
!= -1) {
98 mlen
= pmatch
[n
].rm_eo
- pmatch
[n
].rm_so
;
101 const char *p
= input
+ pmatch
[n
].rm_so
;
103 *string
++ = xform(*p
++);
109 xform
= xform_tolower
;
113 xform
= xform_toupper
;
121 if (macrosub
&& (sublen
= macrosub(macro
, string
)) >= 0) {
125 *string
= xform(*string
);
132 *string
++ = xform(pattern
[1]);
139 *string
++ = xform(*pattern
);
144 /* Copy section after match */
146 memcpy(string
, input
+ pmatch
[0].rm_eo
, endbytes
);
147 string
[endbytes
] = '\0';
154 * Extract a string terminated by non-escaped whitespace; ignoring
155 * leading whitespace. Consider an unescaped # to be a comment marker,
158 static int readescstring(char *buf
, char **str
)
161 int wasbs
= 0, len
= 0;
163 while (*p
&& isspace(*p
))
173 if (!wasbs
&& (isspace(*p
) || *p
== '#')) {
178 /* Important: two backslashes leave us in the !wasbs state! */
179 wasbs
= !wasbs
&& (*p
== '\\');
189 /* Parse a line into a set of instructions */
190 static int parseline(char *line
, struct rule
*r
, int lineno
)
192 char buffer
[MAXLINE
];
195 int rxflags
= REG_EXTENDED
;
198 memset(r
, 0, sizeof *r
);
201 if (!readescstring(buffer
, &line
))
202 return 0; /* No rule found */
204 for (p
= buffer
; *p
; p
++) {
207 r
->rule_flags
|= RULE_REWRITE
;
210 r
->rule_flags
|= RULE_GLOBAL
;
213 r
->rule_flags
|= RULE_EXIT
;
216 r
->rule_flags
|= RULE_RESTART
;
219 r
->rule_flags
|= RULE_ABORT
;
222 rxflags
|= REG_ICASE
;
225 r
->rule_flags
|= RULE_GETONLY
;
228 r
->rule_flags
|= RULE_PUTONLY
;
231 r
->rule_flags
|= RULE_INVERSE
;
235 "Remap command \"%s\" on line %d contains invalid char \"%c\"",
237 return -1; /* Error */
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",
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) {
261 regerror(rv
, &r
->rx
, errbuf
, BUFSIZ
);
262 syslog(LOG_ERR
, "Bad regex in remap line %d: %s\n", lineno
,
264 return -1; /* Error */
267 /* Read the rewrite pattern, if any */
268 if (readescstring(buffer
, &line
)) {
269 r
->pattern
= tfstrdup(buffer
);
275 return 1; /* Rule found */
278 /* Read a rule file */
279 struct rule
*parserulefile(FILE * f
)
282 struct rule
*first_rule
= NULL
;
283 struct rule
**last_rule
= &first_rule
;
284 struct rule
*this_rule
= tfmalloc(sizeof(struct rule
));
289 while (lineno
++, fgets(line
, MAXLINE
, f
)) {
290 rv
= parseline(line
, this_rule
, lineno
);
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 */
303 /* Bail on error, we have already logged an error message */
310 /* Destroy a rule file data structure */
311 void freerules(struct rule
*r
)
320 /* "" patterns aren't allocated by malloc() */
321 if (r
->pattern
&& *r
->pattern
)
322 free((void *)r
->pattern
);
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
,
335 char *current
= tfstrdup(input
);
337 const struct rule
*ruleptr
= rules
;
338 regmatch_t pmatch
[10];
341 int deadman
= DEADMAN_MAX_STEPS
;
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 */
358 "remap: Breaking loop, input = %s, last = %s", input
,
361 return NULL
; /* Did not terminate! */
365 if (regexec(&ruleptr
->rx
, current
, 10, pmatch
, 0) ==
366 (ruleptr
->rule_flags
& RULE_INVERSE
? REG_NOMATCH
: 0)) {
367 /* Match on this rule */
370 if (ruleptr
->rule_flags
& RULE_INVERSE
) {
371 /* No actual match, so clear out the pmatch array */
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 */
385 genmatchstring(NULL
, ruleptr
->pattern
, current
,
387 newstr
= tfmalloc(len
+ 1);
388 genmatchstring(newstr
, ruleptr
->pattern
, current
,
398 if (ruleptr
->rule_flags
& RULE_REWRITE
) {
399 len
= genmatchstring(NULL
, ruleptr
->pattern
, current
,
401 newstr
= tfmalloc(len
+ 1);
402 genmatchstring(newstr
, ruleptr
->pattern
, current
,
406 if (verbosity
>= 3) {
407 syslog(LOG_INFO
, "remap: rule %d: rewrite: %s",
408 ruleptr
->nrule
, current
);
412 break; /* No match, terminate unconditionally */
414 /* If the rule is global, keep going until no match */
415 } while (ruleptr
->rule_flags
& RULE_GLOBAL
);
420 if (ruleptr
->rule_flags
& RULE_EXIT
) {
421 if (verbosity
>= 3) {
422 syslog(LOG_INFO
, "remap: rule %d: exit",
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",
436 if (verbosity
>= 3) {
437 syslog(LOG_INFO
, "remap: done");