2 /* ----------------------------------------------------------------------- *
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 * ----------------------------------------------------------------------- */
15 * Perform regular-expression based filename remapping.
18 #include "config.h" /* Must be included first! */
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 */
46 static int xform_null(int c
)
51 static int xform_toupper(int c
)
56 static int xform_tolower(int 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
;
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];
85 case '0': case '1': case '2': case '3': case '4':
86 case '5': case '6': case '7': case '8': case '9':
89 if ( pmatch
[n
].rm_so
!= -1 ) {
90 mlen
= pmatch
[n
].rm_eo
- pmatch
[n
].rm_so
;
93 const char *p
= input
+pmatch
[n
].rm_so
;
95 *string
++ = xform(*p
++);
101 xform
= xform_tolower
;
105 xform
= xform_toupper
;
114 (sublen
= macrosub(macro
, string
)) >= 0 ) {
118 *string
= xform(*string
);
125 *string
++ = xform(pattern
[1]);
132 *string
++ = xform(*pattern
);
137 /* Copy section after match */
139 memcpy(string
, input
+pmatch
[0].rm_eo
, endbytes
);
140 string
[endbytes
] = '\0';
147 * Extract a string terminated by non-escaped whitespace; ignoring
148 * leading whitespace. Consider an unescaped # to be a comment marker,
151 static int readescstring(char *buf
, char **str
)
154 int wasbs
= 0, len
= 0;
156 while ( *p
&& isspace(*p
) )
166 if ( !wasbs
&& (isspace(*p
) || *p
== '#') ) {
171 /* Important: two backslashes leave us in the !wasbs state! */
172 wasbs
= !wasbs
&& ( *p
== '\\' );
182 /* Parse a line into a set of instructions */
183 static int parseline(char *line
, struct rule
*r
, int lineno
)
185 char buffer
[MAXLINE
];
188 int rxflags
= REG_EXTENDED
;
191 memset(r
, 0, sizeof *r
);
194 if ( !readescstring(buffer
, &line
) )
195 return 0; /* No rule found */
197 for ( p
= buffer
; *p
; p
++ ) {
200 r
->rule_flags
|= RULE_REWRITE
;
203 r
->rule_flags
|= RULE_GLOBAL
;
206 r
->rule_flags
|= RULE_EXIT
;
209 r
->rule_flags
|= RULE_RESTART
;
212 r
->rule_flags
|= RULE_ABORT
;
215 rxflags
|= REG_ICASE
;
218 r
->rule_flags
|= RULE_GETONLY
;
221 r
->rule_flags
|= RULE_PUTONLY
;
224 r
->rule_flags
|= RULE_INVERSE
;
227 syslog(LOG_ERR
, "Remap command \"%s\" on line %d contains invalid char \"%c\"",
229 return -1; /* Error */
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 ) {
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
);
265 return 1; /* Rule found */
268 /* Read a rule file */
269 struct rule
*parserulefile(FILE *f
)
272 struct rule
*first_rule
= NULL
;
273 struct rule
**last_rule
= &first_rule
;
274 struct rule
*this_rule
= tfmalloc(sizeof(struct rule
));
279 while ( lineno
++, fgets(line
, MAXLINE
, f
) ) {
280 rv
= parseline(line
, this_rule
, lineno
);
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 */
293 /* Bail on error, we have already logged an error message */
300 /* Destroy a rule file data structure */
301 void freerules(struct rule
*r
)
310 /* "" patterns aren't allocated by malloc() */
311 if ( r
->pattern
&& *r
->pattern
)
312 free((void *)r
->pattern
);
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
,
325 char *current
= tfstrdup(input
);
327 const struct rule
*ruleptr
= rules
;
328 regmatch_t pmatch
[10];
331 int deadman
= DEADMAN_MAX_STEPS
;
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 */
347 syslog(LOG_WARNING
, "remap: Breaking loop, input = %s, last = %s",
350 return NULL
; /* Did not terminate! */
354 if ( regexec(&ruleptr
->rx
, current
, 10, pmatch
, 0) ==
355 (ruleptr
->rule_flags
& RULE_INVERSE
? REG_NOMATCH
: 0) ) {
356 /* Match on this rule */
359 if ( ruleptr
->rule_flags
& RULE_INVERSE
) {
360 /* No actual match, so clear out the pmatch array */
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
,
375 newstr
= tfmalloc(len
+1);
376 genmatchstring(newstr
, ruleptr
->pattern
, current
,
386 if ( ruleptr
->rule_flags
& RULE_REWRITE
) {
387 len
= genmatchstring(NULL
, ruleptr
->pattern
, current
,
389 newstr
= tfmalloc(len
+1);
390 genmatchstring(newstr
, ruleptr
->pattern
, current
,
394 if ( verbosity
>= 3 ) {
395 syslog(LOG_INFO
, "remap: rule %d: rewrite: %s",
396 ruleptr
->nrule
, current
);
400 break; /* No match, terminate unconditionally */
402 /* If the rule is global, keep going until no match */
403 } while ( ruleptr
->rule_flags
& RULE_GLOBAL
);
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");