1 |
/* |
2 |
* mod_tomoyo.c - Apache module for TOMOYO Linux. |
3 |
* |
4 |
* About this module: |
5 |
* |
6 |
* This module allows Apache 2.x running on TOMOYO Linux kernels to process |
7 |
* requests under different TOMOYO Linux's domains based on requested |
8 |
* server's name (and optionally based on requested resource's pathname) by |
9 |
* requesting TOMOYO Linux's domain transition before processing requests. |
10 |
* |
11 |
* Access restrictions are provided by TOMOYO Linux kernel's Mandatory Access |
12 |
* Control functionality. Therefore, if you want Apache to process requests |
13 |
* with limited set of permissions, you have to configure TOMOYO Linux's |
14 |
* policy and assign "enforcing mode". |
15 |
* |
16 |
* Runtime dependency: |
17 |
* |
18 |
* TOMOYO Linux 2.5.0 (or later). |
19 |
* |
20 |
* How to build and install: |
21 |
* |
22 |
* Install packages needed for developing Apache modules and run below |
23 |
* command. If your system has apxs2, use apxs2 rather than apxs. |
24 |
* |
25 |
* apxs -i -a -c mod_tomoyo.c |
26 |
* |
27 |
* How to configure: |
28 |
* |
29 |
* TOMOYO_TransitionMap directive is provided by this module. |
30 |
* You may perform domain transition based on requested resource's pathname |
31 |
* using this directive. |
32 |
* |
33 |
* This directive can appear in the server-wide configuration files |
34 |
* (e.g., httpd.conf) outside <Directory> or <Location> containers. |
35 |
* |
36 |
* DocumentRoot /var/www/html/ |
37 |
* ServerName www.example.com |
38 |
* TOMOYO_TransitionMap /etc/tomoyo/apache2_transition_table.conf |
39 |
* |
40 |
* <VirtualHost *:80> |
41 |
* DocumentRoot /home/cat/html/ |
42 |
* ServerName cat.example.com |
43 |
* TOMOYO_TransitionMap /home/cat/apache2_transition_table.conf |
44 |
* </VirtualHost> |
45 |
* |
46 |
* <VirtualHost *:80> |
47 |
* DocumentRoot /home/dog/html/ |
48 |
* ServerName dog.example.com |
49 |
* TOMOYO_TransitionMap /home/dog/apache2_transition_table.conf |
50 |
* </VirtualHost> |
51 |
* |
52 |
* This directive takes one parameter which specifies pathname to mapping |
53 |
* table file. The mapping table file contains list of "pathname patterns" |
54 |
* and "domainname" pairs, written in accordance with TOMOYO Linux's pathname |
55 |
* representation rule and wildcard characters. For example, |
56 |
* |
57 |
* /var/www/cgi-bin/\* <kernel> //apache /www.example.com /cgi-programs |
58 |
* /usr/share/horde/\{\*\}/\* <kernel> //apache /www.example.com /horde |
59 |
* /var/www/html/\{\*\}/\* <kernel> //apache /www.example.com /static-files |
60 |
* |
61 |
* in /etc/tomoyo/apache2_transition_table.conf and |
62 |
* |
63 |
* /home/cat/html/\* <kernel> //apache /cat.example.com |
64 |
* /home/cat/html/\{\*\}/\* <kernel> //apache /cat.example.com |
65 |
* |
66 |
* in /home/cat/apache2_transition_table.conf and |
67 |
* |
68 |
* /home/dog/html/\* <kernel> //apache /dog.example.com |
69 |
* /home/dog/html/\{\*\}/\* <kernel> //apache /dog.example.com |
70 |
* |
71 |
* in /home/dog/apache2_transition_table.conf . |
72 |
* |
73 |
* You need to beforehand specify domainnames in the mapping table to |
74 |
* /sys/kernel/security/tomoyo/domain_policy using "task manual_domain_transition" directive |
75 |
* (e.g. |
76 |
* |
77 |
* <kernel> /usr/sbin/httpd |
78 |
* task manual_domain_transition <kernel> //apache /www.example.com /cgi-programs |
79 |
* task manual_domain_transition <kernel> //apache /www.example.com /horde |
80 |
* task manual_domain_transition <kernel> //apache /www.example.com /static-files |
81 |
* task manual_domain_transition <kernel> //apache /cat.example.com |
82 |
* task manual_domain_transition <kernel> //apache /dog.example.com |
83 |
* |
84 |
* ). |
85 |
* |
86 |
* If the requested pathname did not match the pathname patterns listed in |
87 |
* the mapping table file, the request will fail with internal error. |
88 |
* Be sure to cover all possible pathnames you want to allow access. |
89 |
* |
90 |
* If you want to use this module for separating virtual hosts and not for |
91 |
* separating permissions within a virtual host, you can specify like |
92 |
* |
93 |
* /var/www/cgi-bin/\* <kernel> //apache /www.example.com |
94 |
* /usr/share/horde/\{\*\}/\* <kernel> //apache /www.example.com |
95 |
* /var/www/html/\{\*\}/\* <kernel> //apache /www.example.com |
96 |
* |
97 |
* in /etc/tomoyo/apache2_transition_table.conf and |
98 |
* |
99 |
* /home/cat/html/\* <kernel> //apache /cat.example.com |
100 |
* /home/cat/html/\{\*\}/\* <kernel> //apache /cat.example.com |
101 |
* |
102 |
* in /home/cat/apache2_transition_table.conf and |
103 |
* |
104 |
* /home/dog/html/\* <kernel> //apache /dog.example.com |
105 |
* /home/dog/html/\{\*\}/\* <kernel> //apache /dog.example.com |
106 |
* |
107 |
* in /home/dog/apache2_transition_table.conf . |
108 |
* |
109 |
* Author: |
110 |
* |
111 |
* Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp> |
112 |
* |
113 |
* The idea to use one-time worker thread is borrowed from mod_selinux.c |
114 |
* developed by KaiGai Kohei. |
115 |
*/ |
116 |
#include <stdio.h> |
117 |
#include <string.h> |
118 |
#include <unistd.h> |
119 |
#include <fcntl.h> |
120 |
|
121 |
#define s8 char |
122 |
#define u8 unsigned char |
123 |
#define bool _Bool |
124 |
#define false 0 |
125 |
#define true 1 |
126 |
|
127 |
/** |
128 |
* ccs_is_byte_range - Check whether the string is a \ooo style octal value. |
129 |
* |
130 |
* @str: Pointer to the string. |
131 |
* |
132 |
* Returns true if @str is a \ooo style octal value, false otherwise. |
133 |
*/ |
134 |
static inline bool ccs_is_byte_range(const char *str) |
135 |
{ |
136 |
return *str >= '0' && *str++ <= '3' && |
137 |
*str >= '0' && *str++ <= '7' && |
138 |
*str >= '0' && *str <= '7'; |
139 |
} |
140 |
|
141 |
/** |
142 |
* ccs_is_decimal - Check whether the character is a decimal character. |
143 |
* |
144 |
* @c: The character to check. |
145 |
* |
146 |
* Returns true if @c is a decimal character, false otherwise. |
147 |
*/ |
148 |
static inline bool ccs_is_decimal(const char c) |
149 |
{ |
150 |
return c >= '0' && c <= '9'; |
151 |
} |
152 |
|
153 |
/** |
154 |
* ccs_is_hexadecimal - Check whether the character is a hexadecimal character. |
155 |
* |
156 |
* @c: The character to check. |
157 |
* |
158 |
* Returns true if @c is a hexadecimal character, false otherwise. |
159 |
*/ |
160 |
static inline bool ccs_is_hexadecimal(const char c) |
161 |
{ |
162 |
return (c >= '0' && c <= '9') || |
163 |
(c >= 'A' && c <= 'F') || |
164 |
(c >= 'a' && c <= 'f'); |
165 |
} |
166 |
|
167 |
/** |
168 |
* ccs_is_alphabet_char - Check whether the character is an alphabet. |
169 |
* |
170 |
* @c: The character to check. |
171 |
* |
172 |
* Returns true if @c is an alphabet character, false otherwise. |
173 |
*/ |
174 |
static inline bool ccs_is_alphabet_char(const char c) |
175 |
{ |
176 |
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); |
177 |
} |
178 |
|
179 |
/** |
180 |
* ccs_file_matches_pattern2 - Pattern matching without '/' character and "\-" pattern. |
181 |
* |
182 |
* @filename: The start of string to check. |
183 |
* @filename_end: The end of string to check. |
184 |
* @pattern: The start of pattern to compare. |
185 |
* @pattern_end: The end of pattern to compare. |
186 |
* |
187 |
* Returns true if @filename matches @pattern, false otherwise. |
188 |
*/ |
189 |
static bool ccs_file_matches_pattern2(const char *filename, |
190 |
const char *filename_end, |
191 |
const char *pattern, |
192 |
const char *pattern_end) |
193 |
{ |
194 |
while (filename < filename_end && pattern < pattern_end) { |
195 |
char c; |
196 |
if (*pattern != '\\') { |
197 |
if (*filename++ != *pattern++) |
198 |
return false; |
199 |
continue; |
200 |
} |
201 |
c = *filename; |
202 |
pattern++; |
203 |
switch (*pattern) { |
204 |
int i; |
205 |
int j; |
206 |
case '?': |
207 |
if (c == '/') { |
208 |
return false; |
209 |
} else if (c == '\\') { |
210 |
if (filename[1] == '\\') |
211 |
filename++; |
212 |
else if (ccs_is_byte_range(filename + 1)) |
213 |
filename += 3; |
214 |
else |
215 |
return false; |
216 |
} |
217 |
break; |
218 |
case '\\': |
219 |
if (c != '\\') |
220 |
return false; |
221 |
if (*++filename != '\\') |
222 |
return false; |
223 |
break; |
224 |
case '+': |
225 |
if (!ccs_is_decimal(c)) |
226 |
return false; |
227 |
break; |
228 |
case 'x': |
229 |
if (!ccs_is_hexadecimal(c)) |
230 |
return false; |
231 |
break; |
232 |
case 'a': |
233 |
if (!ccs_is_alphabet_char(c)) |
234 |
return false; |
235 |
break; |
236 |
case '0': |
237 |
case '1': |
238 |
case '2': |
239 |
case '3': |
240 |
if (c == '\\' && ccs_is_byte_range(filename + 1) |
241 |
&& !strncmp(filename + 1, pattern, 3)) { |
242 |
filename += 3; |
243 |
pattern += 2; |
244 |
break; |
245 |
} |
246 |
return false; /* Not matched. */ |
247 |
case '*': |
248 |
case '@': |
249 |
for (i = 0; i <= filename_end - filename; i++) { |
250 |
if (ccs_file_matches_pattern2(filename + i, |
251 |
filename_end, |
252 |
pattern + 1, |
253 |
pattern_end)) |
254 |
return true; |
255 |
c = filename[i]; |
256 |
if (c == '.' && *pattern == '@') |
257 |
break; |
258 |
if (c != '\\') |
259 |
continue; |
260 |
if (filename[i + 1] == '\\') |
261 |
i++; |
262 |
else if (ccs_is_byte_range(filename + i + 1)) |
263 |
i += 3; |
264 |
else |
265 |
break; /* Bad pattern. */ |
266 |
} |
267 |
return false; /* Not matched. */ |
268 |
default: |
269 |
j = 0; |
270 |
c = *pattern; |
271 |
if (c == '$') { |
272 |
while (ccs_is_decimal(filename[j])) |
273 |
j++; |
274 |
} else if (c == 'X') { |
275 |
while (ccs_is_hexadecimal(filename[j])) |
276 |
j++; |
277 |
} else if (c == 'A') { |
278 |
while (ccs_is_alphabet_char(filename[j])) |
279 |
j++; |
280 |
} |
281 |
for (i = 1; i <= j; i++) { |
282 |
if (ccs_file_matches_pattern2(filename + i, |
283 |
filename_end, |
284 |
pattern + 1, |
285 |
pattern_end)) |
286 |
return true; |
287 |
} |
288 |
return false; /* Not matched or bad pattern. */ |
289 |
} |
290 |
filename++; |
291 |
pattern++; |
292 |
} |
293 |
while (*pattern == '\\' && |
294 |
(*(pattern + 1) == '*' || *(pattern + 1) == '@')) |
295 |
pattern += 2; |
296 |
return filename == filename_end && pattern == pattern_end; |
297 |
} |
298 |
|
299 |
/** |
300 |
* ccs_file_matches_pattern - Pattern matching without '/' character. |
301 |
* |
302 |
* @filename: The start of string to check. |
303 |
* @filename_end: The end of string to check. |
304 |
* @pattern: The start of pattern to compare. |
305 |
* @pattern_end: The end of pattern to compare. |
306 |
* |
307 |
* Returns true if @filename matches @pattern, false otherwise. |
308 |
*/ |
309 |
static bool ccs_file_matches_pattern(const char *filename, |
310 |
const char *filename_end, |
311 |
const char *pattern, |
312 |
const char *pattern_end) |
313 |
{ |
314 |
const char *pattern_start = pattern; |
315 |
bool first = true; |
316 |
bool result; |
317 |
while (pattern < pattern_end - 1) { |
318 |
/* Split at "\-" pattern. */ |
319 |
if (*pattern++ != '\\' || *pattern++ != '-') |
320 |
continue; |
321 |
result = ccs_file_matches_pattern2(filename, filename_end, |
322 |
pattern_start, pattern - 2); |
323 |
if (first) |
324 |
result = !result; |
325 |
if (result) |
326 |
return false; |
327 |
first = false; |
328 |
pattern_start = pattern; |
329 |
} |
330 |
result = ccs_file_matches_pattern2(filename, filename_end, |
331 |
pattern_start, pattern_end); |
332 |
return first ? result : !result; |
333 |
} |
334 |
|
335 |
/** |
336 |
* ccs_path_matches_pattern2 - Do pathname pattern matching. |
337 |
* |
338 |
* @f: The start of string to check. |
339 |
* @p: The start of pattern to compare. |
340 |
* |
341 |
* Returns true if @f matches @p, false otherwise. |
342 |
*/ |
343 |
static bool ccs_path_matches_pattern2(const char *f, const char *p) |
344 |
{ |
345 |
const char *f_delimiter; |
346 |
const char *p_delimiter; |
347 |
while (*f && *p) { |
348 |
f_delimiter = strchr(f, '/'); |
349 |
if (!f_delimiter) |
350 |
f_delimiter = f + strlen(f); |
351 |
p_delimiter = strchr(p, '/'); |
352 |
if (!p_delimiter) |
353 |
p_delimiter = p + strlen(p); |
354 |
if (*p == '\\' && *(p + 1) == '{') |
355 |
goto recursive; |
356 |
if (!ccs_file_matches_pattern(f, f_delimiter, p, p_delimiter)) |
357 |
return false; |
358 |
f = f_delimiter; |
359 |
if (*f) |
360 |
f++; |
361 |
p = p_delimiter; |
362 |
if (*p) |
363 |
p++; |
364 |
} |
365 |
/* Ignore trailing "\*" and "\@" in @pattern. */ |
366 |
while (*p == '\\' && |
367 |
(*(p + 1) == '*' || *(p + 1) == '@')) |
368 |
p += 2; |
369 |
return !*f && !*p; |
370 |
recursive: |
371 |
/* |
372 |
* The "\{" pattern is permitted only after '/' character. |
373 |
* This guarantees that below "*(p - 1)" is safe. |
374 |
* Also, the "\}" pattern is permitted only before '/' character |
375 |
* so that "\{" + "\}" pair will not break the "\-" operator. |
376 |
*/ |
377 |
if (*(p - 1) != '/' || p_delimiter <= p + 3 || *p_delimiter != '/' || |
378 |
*(p_delimiter - 1) != '}' || *(p_delimiter - 2) != '\\') |
379 |
return false; /* Bad pattern. */ |
380 |
do { |
381 |
/* Compare current component with pattern. */ |
382 |
if (!ccs_file_matches_pattern(f, f_delimiter, p + 2, |
383 |
p_delimiter - 2)) |
384 |
break; |
385 |
/* Proceed to next component. */ |
386 |
f = f_delimiter; |
387 |
if (!*f) |
388 |
break; |
389 |
f++; |
390 |
/* Continue comparison. */ |
391 |
if (ccs_path_matches_pattern2(f, p_delimiter + 1)) |
392 |
return true; |
393 |
f_delimiter = strchr(f, '/'); |
394 |
} while (f_delimiter); |
395 |
return false; /* Not matched. */ |
396 |
} |
397 |
|
398 |
/** |
399 |
* ccs_const_part_length - Evaluate the initial length without a pattern in a token. |
400 |
* |
401 |
* @filename: The string to evaluate. |
402 |
* |
403 |
* Returns the initial length without a pattern in @filename. |
404 |
*/ |
405 |
static int ccs_const_part_length(const char *filename) |
406 |
{ |
407 |
char c; |
408 |
int len = 0; |
409 |
if (!filename) |
410 |
return 0; |
411 |
while (1) { |
412 |
c = *filename++; |
413 |
if (!c) |
414 |
break; |
415 |
if (c != '\\') { |
416 |
len++; |
417 |
continue; |
418 |
} |
419 |
c = *filename++; |
420 |
switch (c) { |
421 |
case '\\': /* "\\" */ |
422 |
len += 2; |
423 |
continue; |
424 |
case '0': /* "\ooo" */ |
425 |
case '1': |
426 |
case '2': |
427 |
case '3': |
428 |
c = *filename++; |
429 |
if (c < '0' || c > '7') |
430 |
break; |
431 |
c = *filename++; |
432 |
if (c < '0' || c > '7') |
433 |
break; |
434 |
len += 4; |
435 |
continue; |
436 |
} |
437 |
break; |
438 |
} |
439 |
return len; |
440 |
} |
441 |
|
442 |
/** |
443 |
* ccs_path_matches_pattern - Check whether the given filename matches the given pattern. |
444 |
* |
445 |
* @filename: The filename to check. |
446 |
* @pattern: The pattern to compare. |
447 |
* |
448 |
* Returns true if matches, false otherwise. |
449 |
* |
450 |
* The following patterns are available. |
451 |
* \\ \ itself. |
452 |
* \ooo Octal representation of a byte. |
453 |
* \* Zero or more repetitions of characters other than '/'. |
454 |
* \@ Zero or more repetitions of characters other than '/' or '.'. |
455 |
* \? 1 byte character other than '/'. |
456 |
* \$ One or more repetitions of decimal digits. |
457 |
* \+ 1 decimal digit. |
458 |
* \X One or more repetitions of hexadecimal digits. |
459 |
* \x 1 hexadecimal digit. |
460 |
* \A One or more repetitions of alphabet characters. |
461 |
* \a 1 alphabet character. |
462 |
* |
463 |
* \- Subtraction operator. |
464 |
* |
465 |
* /\{dir\}/ '/' + 'One or more repetitions of dir/' (e.g. /dir/ /dir/dir/ |
466 |
* /dir/dir/dir/ ). |
467 |
*/ |
468 |
static bool ccs_path_matches_pattern(const char *filename, const char *pattern) |
469 |
{ |
470 |
const char *f = filename; |
471 |
const char *p = pattern; |
472 |
const int len = ccs_const_part_length(pattern); |
473 |
/* If @pattern doesn't contain pattern, I can use strcmp(). */ |
474 |
if (len == strlen(pattern)) |
475 |
return !strcmp(filename, pattern); |
476 |
/* Compare the initial length without patterns. */ |
477 |
if (strncmp(f, p, len)) |
478 |
return false; |
479 |
f += len; |
480 |
p += len; |
481 |
return ccs_path_matches_pattern2(f, p); |
482 |
} |
483 |
|
484 |
/** |
485 |
* ccs_normalize_line - Format string. |
486 |
* |
487 |
* @line: The line to normalize. |
488 |
* |
489 |
* Leading and trailing whitespaces are removed. |
490 |
* Multiple whitespaces are packed into single space. |
491 |
* |
492 |
* Returns nothing. |
493 |
*/ |
494 |
static void ccs_normalize_line(unsigned char *line) |
495 |
{ |
496 |
unsigned char *sp = line; |
497 |
unsigned char *dp = line; |
498 |
_Bool first = true; |
499 |
while (*sp && (*sp <= ' ' || 127 <= *sp)) |
500 |
sp++; |
501 |
while (*sp) { |
502 |
if (!first) |
503 |
*dp++ = ' '; |
504 |
first = false; |
505 |
while (' ' < *sp && *sp < 127) |
506 |
*dp++ = *sp++; |
507 |
while (*sp && (*sp <= ' ' || 127 <= *sp)) |
508 |
sp++; |
509 |
} |
510 |
*dp = '\0'; |
511 |
} |
512 |
|
513 |
/** |
514 |
* ccs_make_byte - Make byte value from three octal characters. |
515 |
* |
516 |
* @c1: The first character. |
517 |
* @c2: The second character. |
518 |
* @c3: The third character. |
519 |
* |
520 |
* Returns byte value. |
521 |
*/ |
522 |
static u8 ccs_make_byte(const u8 c1, const u8 c2, const u8 c3) |
523 |
{ |
524 |
return ((c1 - '0') << 6) + ((c2 - '0') << 3) + (c3 - '0'); |
525 |
} |
526 |
|
527 |
/** |
528 |
* ccs_correct_word2 - Check whether the given string follows the naming rules. |
529 |
* |
530 |
* @string: The byte sequence to check. Not '\0'-terminated. |
531 |
* @len: Length of @string. |
532 |
* |
533 |
* Returns true if @string follows the naming rules, false otherwise. |
534 |
*/ |
535 |
static bool ccs_correct_word2(const char *string, size_t len) |
536 |
{ |
537 |
const char *const start = string; |
538 |
bool in_repetition = false; |
539 |
unsigned char c; |
540 |
unsigned char d; |
541 |
unsigned char e; |
542 |
if (!len) |
543 |
goto out; |
544 |
while (len--) { |
545 |
c = *string++; |
546 |
if (c == '\\') { |
547 |
if (!len--) |
548 |
goto out; |
549 |
c = *string++; |
550 |
switch (c) { |
551 |
case '\\': /* "\\" */ |
552 |
continue; |
553 |
case '$': /* "\$" */ |
554 |
case '+': /* "\+" */ |
555 |
case '?': /* "\?" */ |
556 |
case '*': /* "\*" */ |
557 |
case '@': /* "\@" */ |
558 |
case 'x': /* "\x" */ |
559 |
case 'X': /* "\X" */ |
560 |
case 'a': /* "\a" */ |
561 |
case 'A': /* "\A" */ |
562 |
case '-': /* "\-" */ |
563 |
continue; |
564 |
case '{': /* "/\{" */ |
565 |
if (string - 3 < start || *(string - 3) != '/') |
566 |
break; |
567 |
in_repetition = true; |
568 |
continue; |
569 |
case '}': /* "\}/" */ |
570 |
if (*string != '/') |
571 |
break; |
572 |
if (!in_repetition) |
573 |
break; |
574 |
in_repetition = false; |
575 |
continue; |
576 |
case '0': /* "\ooo" */ |
577 |
case '1': |
578 |
case '2': |
579 |
case '3': |
580 |
if (!len-- || !len--) |
581 |
break; |
582 |
d = *string++; |
583 |
e = *string++; |
584 |
if (d < '0' || d > '7' || e < '0' || e > '7') |
585 |
break; |
586 |
c = ccs_make_byte(c, d, e); |
587 |
if (c <= ' ' || c >= 127) |
588 |
continue; |
589 |
} |
590 |
goto out; |
591 |
} else if (in_repetition && c == '/') { |
592 |
goto out; |
593 |
} else if (c <= ' ' || c >= 127) { |
594 |
goto out; |
595 |
} |
596 |
} |
597 |
if (in_repetition) |
598 |
goto out; |
599 |
return true; |
600 |
out: |
601 |
return false; |
602 |
} |
603 |
|
604 |
/** |
605 |
* ccs_correct_word - Check whether the given string follows the naming rules. |
606 |
* |
607 |
* @string: The string to check. |
608 |
* |
609 |
* Returns true if @string follows the naming rules, false otherwise. |
610 |
*/ |
611 |
static bool ccs_correct_word(const char *string) |
612 |
{ |
613 |
return ccs_correct_word2(string, strlen(string)); |
614 |
} |
615 |
|
616 |
/** |
617 |
* ccs_correct_path - Check whether the given pathname follows the naming rules. |
618 |
* |
619 |
* @filename: The pathname to check. |
620 |
* |
621 |
* Returns true if @filename follows the naming rules, false otherwise. |
622 |
*/ |
623 |
static bool ccs_correct_path(const char *filename) |
624 |
{ |
625 |
return *filename == '/' && ccs_correct_word(filename); |
626 |
} |
627 |
|
628 |
/** |
629 |
* ccs_domain_def - Check whether the given token can be a domainname. |
630 |
* |
631 |
* @buffer: The token to check. |
632 |
* |
633 |
* Returns true if @buffer possibly be a domainname, false otherwise. |
634 |
*/ |
635 |
static bool ccs_domain_def(const char *buffer) |
636 |
{ |
637 |
const char *cp; |
638 |
int len; |
639 |
if (*buffer != '<') |
640 |
return false; |
641 |
cp = strchr(buffer, ' '); |
642 |
if (!cp) |
643 |
len = strlen(buffer); |
644 |
else |
645 |
len = cp - buffer; |
646 |
if (buffer[len - 1] != '>' || !ccs_correct_word2(buffer + 1, len - 2)) |
647 |
return false; |
648 |
return true; |
649 |
} |
650 |
|
651 |
/** |
652 |
* ccs_correct_domain - Check whether the given domainname follows the naming rules. |
653 |
* |
654 |
* @domainname: The domainname to check. |
655 |
* |
656 |
* Returns true if @domainname follows the naming rules, false otherwise. |
657 |
*/ |
658 |
static bool ccs_correct_domain(const char *domainname) |
659 |
{ |
660 |
if (!domainname || !ccs_domain_def(domainname)) |
661 |
return false; |
662 |
domainname = strchr(domainname, ' '); |
663 |
if (!domainname++) |
664 |
return true; |
665 |
while (1) { |
666 |
const char *cp = strchr(domainname, ' '); |
667 |
if (!cp) |
668 |
break; |
669 |
if (*domainname != '/' || |
670 |
!ccs_correct_word2(domainname, cp - domainname)) |
671 |
return false; |
672 |
domainname = cp + 1; |
673 |
} |
674 |
return ccs_correct_path(domainname); |
675 |
} |
676 |
|
677 |
#include "httpd.h" |
678 |
#include "apr_strings.h" |
679 |
#include "ap_listen.h" |
680 |
#include "http_log.h" |
681 |
|
682 |
module AP_MODULE_DECLARE_DATA ccs_module; |
683 |
|
684 |
static int ccs_transition_fd = EOF; |
685 |
static int ccs_open_error = 0; |
686 |
|
687 |
struct ccs_map_entry { |
688 |
const char *pathname; |
689 |
const char *domainname; |
690 |
}; |
691 |
|
692 |
struct ccs_map_table { |
693 |
struct ccs_map_entry *entry; |
694 |
int len; |
695 |
}; |
696 |
|
697 |
static char *ccs_encode_string(const char *str) |
698 |
{ |
699 |
char *cp = malloc(strlen(str) * 4 + 1); |
700 |
char *cp0 = cp; |
701 |
if (!cp) |
702 |
return NULL; |
703 |
while (*str) { |
704 |
const unsigned char c = *str++; |
705 |
if (c == '\\') { |
706 |
*cp++ = '\\'; |
707 |
*cp++ = '\\'; |
708 |
} else if (c > ' ' && c < 127) { |
709 |
*cp++ = c; |
710 |
} else { |
711 |
*cp++ = '\\'; |
712 |
*cp++ = (c >> 6) + '0'; |
713 |
*cp++ = ((c >> 3) & 7) + '0'; |
714 |
*cp++ = (c & 7) + '0'; |
715 |
} |
716 |
} |
717 |
*cp = '\0'; |
718 |
return cp0; |
719 |
} |
720 |
|
721 |
static _Bool ccs_set_context(request_rec *r) |
722 |
{ |
723 |
struct ccs_map_table *ptr = |
724 |
ap_get_module_config(r->server->module_config, &ccs_module); |
725 |
int i; |
726 |
/* Transit domain by requested pathname. */ |
727 |
const char *name = ccs_encode_string(r->filename); |
728 |
if (!name) { |
729 |
ap_log_rerror(APLOG_MARK, APLOG_ERR, EPERM, r, "mod_tomoyo: " |
730 |
"Unable to set security context. " |
731 |
"Out of memory."); |
732 |
return 0; |
733 |
} |
734 |
for (i = 0; i < ptr->len; i++) { |
735 |
int len; |
736 |
if (!ccs_path_matches_pattern(name, ptr->entry[i].pathname)) |
737 |
continue; |
738 |
free((void *) name); |
739 |
name = ptr->entry[i].domainname; |
740 |
len = strlen(name) + 1; |
741 |
if (write(ccs_transition_fd, name, len) == len) |
742 |
return 1; |
743 |
ap_log_rerror(APLOG_MARK, APLOG_ERR, EPERM, r, "mod_tomoyo: " |
744 |
"Unable to set security context. " |
745 |
"Can't transit to %s", name); |
746 |
return 0; |
747 |
} |
748 |
ap_log_rerror(APLOG_MARK, APLOG_ERR, EPERM, r, "mod_tomoyo: " |
749 |
"Unable to set security context. " |
750 |
"No matching entry for %s (%u entries for %s)", name, |
751 |
ptr->len, r->hostname); |
752 |
free((void *) name); |
753 |
return 0; |
754 |
} |
755 |
|
756 |
static int __thread volatile am_worker = 0; |
757 |
|
758 |
static void *APR_THREAD_FUNC ccs_worker_handler(apr_thread_t *thread, |
759 |
void *data) |
760 |
{ |
761 |
request_rec *r = (request_rec *) data; |
762 |
int result = HTTP_INTERNAL_SERVER_ERROR; |
763 |
am_worker = 1; |
764 |
/* Set security context. */ |
765 |
if (ccs_set_context(r)) { |
766 |
/* |
767 |
* Invoke content handler again. |
768 |
* Thread local variable am_worker prevents from |
769 |
* being called infinitely. |
770 |
*/ |
771 |
result = ap_run_handler(r); |
772 |
if (result == DECLINED) |
773 |
result = HTTP_INTERNAL_SERVER_ERROR; |
774 |
} |
775 |
apr_thread_exit(thread, result); |
776 |
return NULL; |
777 |
} |
778 |
|
779 |
static int ccs_handler(request_rec *r) |
780 |
{ |
781 |
apr_threadattr_t *thread_attr = NULL; |
782 |
apr_thread_t *thread = NULL; |
783 |
apr_status_t rv; |
784 |
apr_status_t thread_rv; |
785 |
if (am_worker) |
786 |
return DECLINED; |
787 |
if (ccs_open_error) { |
788 |
ap_log_rerror(APLOG_MARK, APLOG_ERR, ccs_open_error, r, |
789 |
"mod_tomoyo: Unable to open " |
790 |
"/sys/kernel/security/tomoyo/self_domain " |
791 |
"for writing."); |
792 |
return HTTP_INTERNAL_SERVER_ERROR; |
793 |
} |
794 |
if (ccs_transition_fd == EOF) |
795 |
return DECLINED; |
796 |
apr_threadattr_create(&thread_attr, r->pool); |
797 |
apr_threadattr_detach_set(thread_attr, 0); |
798 |
rv = apr_thread_create(&thread, thread_attr, ccs_worker_handler, r, |
799 |
r->pool); |
800 |
if (rv != APR_SUCCESS) { |
801 |
ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, "mod_tomoyo: " |
802 |
"Unable to launch a one-time worker thread."); |
803 |
return HTTP_INTERNAL_SERVER_ERROR; |
804 |
} |
805 |
rv = apr_thread_join(&thread_rv, thread); |
806 |
if (rv != APR_SUCCESS) { |
807 |
ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, "mod_tomoyo: " |
808 |
"Unable to join the one-time worker thread."); |
809 |
r->connection->aborted = 1; |
810 |
return HTTP_INTERNAL_SERVER_ERROR; |
811 |
} |
812 |
return thread_rv; |
813 |
} |
814 |
|
815 |
static void ccs_hooks(apr_pool_t *p) |
816 |
{ |
817 |
ap_hook_handler(ccs_handler, NULL, NULL, APR_HOOK_REALLY_FIRST); |
818 |
} |
819 |
|
820 |
static void *ccs_create_server_config(apr_pool_t *p, server_rec *s) |
821 |
{ |
822 |
void *ptr = apr_palloc(p, sizeof(struct ccs_map_table)); |
823 |
if (ptr) |
824 |
memset(ptr, 0, sizeof(struct ccs_map_table)); |
825 |
/* |
826 |
* We can share because /sys/kernel/security/tomoyo/self_domain |
827 |
* interface has no data. |
828 |
*/ |
829 |
if (ccs_transition_fd == EOF) { |
830 |
ccs_transition_fd = |
831 |
open("/sys/kernel/security/tomoyo/self_domain", |
832 |
O_WRONLY); |
833 |
/* |
834 |
* Some access control mechanisms might reject opening |
835 |
* /sys/kernel/security/tomoyo/self_domain for writing. |
836 |
* This failure is reported by ccs_parse_table() if |
837 |
* TOMOYO_TransitionMap keyword is specified, by ccs_handler() |
838 |
* otherwise. |
839 |
*/ |
840 |
if (ccs_transition_fd == EOF && errno != ENOENT) |
841 |
ccs_open_error = errno; |
842 |
} |
843 |
/* Allocation failure is reported by ccs_parse_table(). */ |
844 |
return ptr; |
845 |
} |
846 |
|
847 |
static const char *ccs_parse_table(cmd_parms *parms, void *mconfig, |
848 |
const char *args) |
849 |
{ |
850 |
static const int buffer_len = 8192; |
851 |
char *buffer = apr_palloc(parms->pool, buffer_len); |
852 |
int line = 0; |
853 |
FILE *fp = NULL; |
854 |
struct ccs_map_table *ptr = |
855 |
ap_get_module_config(parms->server->module_config, |
856 |
&ccs_module); |
857 |
if (!ptr || !buffer) |
858 |
goto no_memory; |
859 |
if (ccs_open_error) |
860 |
goto no_interface; |
861 |
fp = fopen(args, "r"); |
862 |
if (!fp) |
863 |
goto no_file; |
864 |
{ |
865 |
int c; |
866 |
while ((c = fgetc(fp)) != EOF) |
867 |
if (c == '\n') |
868 |
line++; |
869 |
if (!line) { |
870 |
fclose(fp); |
871 |
goto no_file; |
872 |
} |
873 |
} |
874 |
ptr->entry = apr_palloc(parms->pool, |
875 |
line * sizeof(struct ccs_map_entry)); |
876 |
if (!ptr->entry) |
877 |
goto no_memory; |
878 |
ptr->len = line; |
879 |
line = 0; |
880 |
rewind(fp); |
881 |
memset(buffer, 0, buffer_len); |
882 |
while (fgets(buffer, buffer_len - 1, fp)) { |
883 |
char *cp = strchr(buffer, '\n'); |
884 |
if (line == ptr->len) |
885 |
goto invalid_line; |
886 |
if (!cp) { |
887 |
fclose(fp); |
888 |
snprintf(buffer, buffer_len - 1, "mod_tomoyo: " |
889 |
"Line %u of %s : Too long.", line + 1, args); |
890 |
return buffer; |
891 |
} |
892 |
ccs_normalize_line((unsigned char *) buffer); |
893 |
cp = strchr(buffer, ' '); |
894 |
if (!cp) |
895 |
goto invalid_line; |
896 |
*cp++ = '\0'; |
897 |
if (!ccs_correct_path(buffer)) { |
898 |
fclose(fp); |
899 |
snprintf(buffer, buffer_len - 1, "mod_tomoyo: " |
900 |
"Line %u of %s : Bad pathname.", line + 1, |
901 |
args); |
902 |
return buffer; |
903 |
} |
904 |
if (!ccs_correct_domain(cp)) { |
905 |
fclose(fp); |
906 |
snprintf(buffer, buffer_len - 1, "mod_tomoyo: " |
907 |
"Line %u of %s : Bad domainname.", line + 1, |
908 |
args); |
909 |
return buffer; |
910 |
} |
911 |
cp = apr_pstrdup(parms->pool, cp); |
912 |
if (!cp) |
913 |
goto no_memory; |
914 |
ptr->entry[line].domainname = cp; |
915 |
cp = apr_pstrdup(parms->pool, buffer); |
916 |
if (!cp) |
917 |
goto no_memory; |
918 |
ptr->entry[line++].pathname = cp; |
919 |
} |
920 |
fclose(fp); |
921 |
return NULL; |
922 |
no_memory: |
923 |
if (fp) |
924 |
fclose(fp); |
925 |
return "mod_tomoyo: Out of memory."; |
926 |
no_interface: |
927 |
snprintf(buffer, buffer_len - 1, |
928 |
"mod_tomoyo: Unable to open " |
929 |
"/sys/kernel/security/tomoyo/self_domain for writing. " |
930 |
"(errno = %d)", ccs_open_error); |
931 |
return buffer; |
932 |
no_file: |
933 |
snprintf(buffer, buffer_len - 1, "mod_tomoyo: %s : Can't read.", args); |
934 |
return buffer; |
935 |
invalid_line: |
936 |
fclose(fp); |
937 |
snprintf(buffer, buffer_len - 1, "mod_tomoyo: " |
938 |
"Line %u of %s : Bad line.", line + 1, args); |
939 |
return buffer; |
940 |
} |
941 |
|
942 |
static command_rec ccs_cmds[2] = { |
943 |
AP_INIT_RAW_ARGS("TOMOYO_TransitionMap", ccs_parse_table, NULL, |
944 |
RSRC_CONF, "Path to path/domain mapping table."), |
945 |
{ NULL } |
946 |
}; |
947 |
|
948 |
module AP_MODULE_DECLARE_DATA ccs_module = { |
949 |
STANDARD20_MODULE_STUFF, NULL, NULL, |
950 |
ccs_create_server_config, NULL, ccs_cmds, ccs_hooks |
951 |
}; |