1 /* ----------------------------------------------------------------------- *
3 * Copyright 2001-2014 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_INVERSE 0x20 /* Execute if regex *doesn't* match */
34 #define RULE_IPV4 0x40 /* IPv4 only */
35 #define RULE_IPV6 0x80 /* IPv6 only */
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
,
63 const char *input
, const regmatch_t
* pmatch
,
64 match_pattern_callback macrosub
)
66 int (*xform
) (int) = xform_null
;
71 /* Get section before match; note pmatch[0] is the whole match */
72 endbytes
= strlen(input
) - pmatch
[0].rm_eo
;
73 len
= pmatch
[0].rm_so
+ endbytes
;
75 memcpy(string
, input
, pmatch
[0].rm_so
);
76 string
+= pmatch
[0].rm_so
;
79 /* Transform matched section */
83 if (*pattern
== '\\' && pattern
[1] != '\0') {
84 char macro
= pattern
[1];
98 if (pmatch
[n
].rm_so
!= -1) {
99 mlen
= pmatch
[n
].rm_eo
- pmatch
[n
].rm_so
;
102 const char *p
= input
+ pmatch
[n
].rm_so
;
104 *string
++ = xform(*p
++);
110 xform
= xform_tolower
;
114 xform
= xform_toupper
;
122 if (macrosub
&& (sublen
= macrosub(macro
, string
)) >= 0) {
126 *string
= xform(*string
);
133 *string
++ = xform(pattern
[1]);
140 *string
++ = xform(*pattern
);
145 /* Copy section after match */
147 memcpy(string
, input
+ pmatch
[0].rm_eo
, endbytes
);
148 string
[endbytes
] = '\0';
155 * Extract a string terminated by non-escaped whitespace; ignoring
156 * leading whitespace. Consider an unescaped # to be a comment marker,
159 static int readescstring(char *buf
, char **str
)
162 int wasbs
= 0, len
= 0;
164 while (*p
&& isspace(*p
))
174 if (!wasbs
&& (isspace(*p
) || *p
== '#')) {
179 /* Important: two backslashes leave us in the !wasbs state! */
180 wasbs
= !wasbs
&& (*p
== '\\');
190 /* Parse a line into a set of instructions */
191 static int parseline(char *line
, struct rule
*r
, int lineno
)
193 char buffer
[MAXLINE
];
196 int rxflags
= REG_EXTENDED
;
199 memset(r
, 0, sizeof *r
);
202 if (!readescstring(buffer
, &line
))
203 return 0; /* No rule found */
205 for (p
= buffer
; *p
; p
++) {
208 r
->rule_flags
|= RULE_REWRITE
;
211 r
->rule_flags
|= RULE_GLOBAL
;
214 r
->rule_flags
|= RULE_EXIT
;
217 r
->rule_flags
|= RULE_RESTART
;
220 r
->rule_flags
|= RULE_ABORT
;
223 rxflags
|= REG_ICASE
;
226 r
->rule_flags
|= RULE_INVERSE
;
229 r
->rule_flags
|= RULE_IPV4
;
232 r
->rule_flags
|= RULE_IPV6
;
240 "Remap command \"%s\" on line %d contains invalid char \"%c\"",
242 return -1; /* Error */
247 /* RULE_GLOBAL only applies when RULE_REWRITE specified */
248 if (!(r
->rule_flags
& RULE_REWRITE
))
249 r
->rule_flags
&= ~RULE_GLOBAL
;
251 if ((r
->rule_flags
& (RULE_INVERSE
| RULE_REWRITE
)) ==
252 (RULE_INVERSE
| RULE_REWRITE
)) {
253 syslog(LOG_ERR
, "r rules cannot be inverted, line %d: %s\n",
255 return -1; /* Error */
258 /* Read and compile the regex */
259 if (!readescstring(buffer
, &line
)) {
260 syslog(LOG_ERR
, "No regex on remap line %d: %s\n", lineno
, line
);
261 return -1; /* Error */
264 if ((rv
= regcomp(&r
->rx
, buffer
, rxflags
)) != 0) {
266 regerror(rv
, &r
->rx
, errbuf
, BUFSIZ
);
267 syslog(LOG_ERR
, "Bad regex in remap line %d: %s\n", lineno
,
269 return -1; /* Error */
272 /* Read the rewrite pattern, if any */
273 if (readescstring(buffer
, &line
)) {
274 r
->pattern
= tfstrdup(buffer
);
280 return 1; /* Rule found */
283 /* Read a rule file */
284 struct rule
*parserulefile(FILE * f
)
287 struct rule
*first_rule
= NULL
;
288 struct rule
**last_rule
= &first_rule
;
289 struct rule
*this_rule
= tfmalloc(sizeof(struct rule
));
294 while (lineno
++, fgets(line
, MAXLINE
, f
)) {
295 rv
= parseline(line
, this_rule
, lineno
);
299 *last_rule
= this_rule
;
300 last_rule
= &this_rule
->next
;
301 this_rule
= tfmalloc(sizeof(struct rule
));
305 free(this_rule
); /* Last one is always unused */
308 /* Bail on error, we have already logged an error message */
315 /* Destroy a rule file data structure */
316 void freerules(struct rule
*r
)
325 /* "" patterns aren't allocated by malloc() */
326 if (r
->pattern
&& *r
->pattern
)
327 free((void *)r
->pattern
);
335 /* Execute a rule set on a string; returns a malloc'd new string. */
336 char *rewrite_string(const char *input
, const struct rule
*rules
,
337 char mode
, int af
, match_pattern_callback macrosub
,
340 char *current
= tfstrdup(input
);
342 const struct rule
*ruleptr
= rules
;
343 regmatch_t pmatch
[10];
346 int deadman
= DEADMAN_MAX_STEPS
;
349 *errmsg
= "Remap table failure";
351 if (verbosity
>= 3) {
352 syslog(LOG_INFO
, "remap: input: %s", current
);
355 for (ruleptr
= rules
; ruleptr
; ruleptr
= ruleptr
->next
) {
356 if (ruleptr
->rule_mode
&& ruleptr
->rule_mode
!= mode
)
357 continue; /* Rule not applicable, try next */
359 if ((ruleptr
->rule_flags
& RULE_IPV4
) && (af
!= AF_INET
))
360 continue; /* Rule not applicable, try next */
362 if ((ruleptr
->rule_flags
& RULE_IPV6
) && (af
!= AF_INET6
))
363 continue; /* Rule not applicable, try next */
367 "remap: Breaking loop, input = %s, last = %s", input
,
370 return NULL
; /* Did not terminate! */
374 if (regexec(&ruleptr
->rx
, current
, 10, pmatch
, 0) ==
375 (ruleptr
->rule_flags
& RULE_INVERSE
? REG_NOMATCH
: 0)) {
376 /* Match on this rule */
379 if (ruleptr
->rule_flags
& RULE_INVERSE
) {
380 /* No actual match, so clear out the pmatch array */
382 for (i
= 0; i
< 10; i
++)
383 pmatch
[i
].rm_so
= pmatch
[i
].rm_eo
= -1;
386 if (ruleptr
->rule_flags
& RULE_ABORT
) {
387 if (verbosity
>= 3) {
388 syslog(LOG_INFO
, "remap: rule %d: abort: %s",
389 ruleptr
->nrule
, current
);
391 if (ruleptr
->pattern
[0]) {
392 /* Custom error message */
394 genmatchstring(NULL
, ruleptr
->pattern
, current
,
396 newstr
= tfmalloc(len
+ 1);
397 genmatchstring(newstr
, ruleptr
->pattern
, current
,
407 if (ruleptr
->rule_flags
& RULE_REWRITE
) {
408 len
= genmatchstring(NULL
, ruleptr
->pattern
, current
,
410 newstr
= tfmalloc(len
+ 1);
411 genmatchstring(newstr
, ruleptr
->pattern
, current
,
415 if (verbosity
>= 3) {
416 syslog(LOG_INFO
, "remap: rule %d: rewrite: %s",
417 ruleptr
->nrule
, current
);
421 break; /* No match, terminate unconditionally */
423 /* If the rule is global, keep going until no match */
424 } while (ruleptr
->rule_flags
& RULE_GLOBAL
);
429 if (ruleptr
->rule_flags
& RULE_EXIT
) {
430 if (verbosity
>= 3) {
431 syslog(LOG_INFO
, "remap: rule %d: exit",
434 return current
; /* Exit here, we're done */
435 } else if (ruleptr
->rule_flags
& RULE_RESTART
) {
436 ruleptr
= rules
; /* Start from the top */
437 if (verbosity
>= 3) {
438 syslog(LOG_INFO
, "remap: rule %d: restart",
445 if (verbosity
>= 3) {
446 syslog(LOG_INFO
, "remap: done");