2 * mod_dontdothat.c: an Apache filter that allows you to return arbitrary
3 * errors for various types of Subversion requests.
5 * ====================================================================
6 * Copyright (c) 2006 CollabNet. All rights reserved.
8 * This software is licensed as described in the file COPYING, which
9 * you should have received as part of this distribution. The terms
10 * are also available at http://subversion.tigris.org/license-1.html.
11 * If newer versions of this license are posted there, you may use a
12 * newer version instead, at your option.
14 * This software consists of voluntary contributions made by many
15 * individuals. For exact contribution history, see the revision
16 * history and logs, available at http://subversion.tigris.org/.
17 * ====================================================================
21 #include <http_config.h>
22 #include <http_protocol.h>
23 #include <http_request.h>
25 #include <util_filter.h>
26 #include <ap_config.h>
27 #include <apr_strings.h>
31 #include "mod_dav_svn.h"
32 #include "svn_string.h"
33 #include "svn_config.h"
35 module AP_MODULE_DECLARE_DATA dontdothat_module
;
38 const char *config_file
;
39 const char *base_path
;
41 } dontdothat_config_rec
;
43 static void *create_dontdothat_dir_config(apr_pool_t
*pool
, char *dir
)
45 dontdothat_config_rec
*cfg
= apr_pcalloc(pool
, sizeof(*cfg
));
53 static const command_rec dontdothat_cmds
[] =
55 AP_INIT_TAKE1("DontDoThatConfigFile", ap_set_file_slot
,
56 (void *) APR_OFFSETOF(dontdothat_config_rec
, config_file
),
58 "Text file containing actions to take for specific requests"),
59 AP_INIT_FLAG("DontDoThatDisallowReplay", ap_set_flag_slot
,
60 (void *) APR_OFFSETOF(dontdothat_config_rec
, no_replay
),
61 OR_ALL
, "Disallow replay requests as if they are other recursive requests."),
74 /* Set to TRUE when we determine that the request is safe and should be
75 * allowed to continue. */
76 svn_boolean_t let_it_go
;
78 /* Set to TRUE when we determine that the request is unsafe and should be
79 * stopped in its tracks. */
80 svn_boolean_t no_soup_for_you
;
84 /* The current location in the REPORT body. */
87 /* A buffer to hold CDATA we encounter. */
88 svn_stringbuf_t
*buffer
;
90 dontdothat_config_rec
*cfg
;
92 /* An array of wildcards that are special cased to be allowed. */
93 apr_array_header_t
*allow_recursive_ops
;
95 /* An array of wildcards where recursive operations are not allowed. */
96 apr_array_header_t
*no_recursive_ops
;
98 /* TRUE if a path has failed a test already. */
99 svn_boolean_t path_failed
;
101 /* An error for when we're using this as a baton while parsing config
105 /* The current request. */
107 } dontdothat_filter_ctx
;
109 /* Return TRUE if wildcard WC matches path P, FALSE otherwise. */
111 matches(const char *wc
, const char *p
)
118 if (wc
[1] != '/' && wc
[1] != '\0')
119 abort(); /* This was checked for during parsing of the config. */
121 /* It's a wild card, so eat up until the next / in p. */
122 while (*p
&& p
[1] != '/')
125 /* If we ran out of p and we're out of wc then it matched. */
137 /* This means we hit the end of wc without running out of p. */
140 /* Or they were exactly the same length, so it's not lower. */
145 return FALSE
; /* If we don't match, then move on to the next
160 is_this_legal(dontdothat_filter_ctx
*ctx
, const char *uri
)
162 const char *relative_path
;
163 const char *cleaned_uri
;
164 const char *repos_name
;
168 /* Ok, so we need to skip past the scheme, host, etc. */
169 uri
= ap_strstr_c(uri
, "://");
171 uri
= ap_strchr_c(uri
+ 3, '/');
175 const char *repos_path
;
177 derr
= dav_svn_split_uri(ctx
->r
,
192 repos_path
= apr_psprintf(ctx
->r
->pool
, "/%s", repos_path
);
194 /* First check the special cases that are always legal... */
195 for (idx
= 0; idx
< ctx
->allow_recursive_ops
->nelts
; ++idx
)
197 const char *wc
= APR_ARRAY_IDX(ctx
->allow_recursive_ops
,
201 if (matches(wc
, repos_path
))
203 ap_log_rerror(APLOG_MARK
, APLOG_DEBUG
, 0, ctx
->r
,
204 "mod_dontdothat: rule %s allows %s",
210 /* Then look for stuff we explicitly don't allow. */
211 for (idx
= 0; idx
< ctx
->no_recursive_ops
->nelts
; ++idx
)
213 const char *wc
= APR_ARRAY_IDX(ctx
->no_recursive_ops
,
217 if (matches(wc
, repos_path
))
219 ap_log_rerror(APLOG_MARK
, APLOG_DEBUG
, 0, ctx
->r
,
220 "mod_dontdothat: rule %s forbids %s",
232 dontdothat_filter(ap_filter_t
*f
,
233 apr_bucket_brigade
*bb
,
234 ap_input_mode_t mode
,
235 apr_read_type_e block
,
238 dontdothat_filter_ctx
*ctx
= f
->ctx
;
242 if (mode
!= AP_MODE_READBYTES
)
243 return ap_get_brigade(f
->next
, bb
, mode
, block
, readbytes
);
245 rv
= ap_get_brigade(f
->next
, bb
, mode
, block
, readbytes
);
249 for (e
= APR_BRIGADE_FIRST(bb
);
250 e
!= APR_BRIGADE_SENTINEL(bb
);
251 e
= APR_BUCKET_NEXT(e
))
253 svn_boolean_t last
= APR_BUCKET_IS_EOS(e
);
264 rv
= apr_bucket_read(e
, &str
, &len
, APR_BLOCK_READ
);
269 if (! XML_Parse(ctx
->xmlp
, str
, len
, last
))
271 /* let_it_go so we clean up our parser, no_soup_for_you so that we
272 * bail out before bothering to parse this stuff a second time. */
273 ctx
->let_it_go
= TRUE
;
274 ctx
->no_soup_for_you
= TRUE
;
277 /* If we found something that isn't allowed, set the correct status
278 * and return an error so it'll bail out before it gets anywhere it
279 * can do real damage. */
280 if (ctx
->no_soup_for_you
)
282 /* XXX maybe set up the SVN-ACTION env var so that it'll show up
283 * in the Subversion operational logs? */
285 ap_log_rerror(APLOG_MARK
, APLOG_DEBUG
, 0, f
->r
,
286 "mod_dontdothat: client broke the rules, "
289 /* Ok, pass an error bucket and an eos bucket back to the client.
291 * NOTE: The custom error string passed here doesn't seem to be
292 * used anywhere by httpd. This is quite possibly a bug.
294 * TODO: Try and pass back a custom document body containing a
295 * serialized svn_error_t so the client displays a better
297 bb
= apr_brigade_create(f
->r
->pool
, f
->c
->bucket_alloc
);
298 e
= ap_bucket_error_create(403, "No Soup For You!",
299 f
->r
->pool
, f
->c
->bucket_alloc
);
300 APR_BRIGADE_INSERT_TAIL(bb
, e
);
301 e
= apr_bucket_eos_create(f
->c
->bucket_alloc
);
302 APR_BRIGADE_INSERT_TAIL(bb
, e
);
304 /* Don't forget to remove us, otherwise recursion blows the stack. */
305 ap_remove_input_filter(f
);
307 return ap_pass_brigade(f
->r
->output_filters
, bb
);
309 else if (ctx
->let_it_go
|| last
)
311 ap_remove_input_filter(f
);
313 ap_log_rerror(APLOG_MARK
, APLOG_DEBUG
, 0, f
->r
,
314 "mod_dontdothat: letting request go through");
324 cdata(void *baton
, const char *data
, int len
)
326 dontdothat_filter_ctx
*ctx
= baton
;
328 if (ctx
->no_soup_for_you
|| ctx
->let_it_go
)
333 case STATE_IN_SRC_PATH
:
336 case STATE_IN_DST_PATH
:
339 case STATE_IN_RECURSIVE
:
341 ctx
->buffer
= svn_stringbuf_ncreate(data
, len
, ctx
->r
->pool
);
343 svn_stringbuf_appendbytes(ctx
->buffer
, data
, len
);
352 start_element(void *baton
, const char *name
, const char **attrs
)
354 dontdothat_filter_ctx
*ctx
= baton
;
357 if (ctx
->no_soup_for_you
|| ctx
->let_it_go
)
360 /* XXX Hack. We should be doing real namespace support, but for now we
361 * just skip ahead of any namespace prefix. If someone's sending us
362 * an update-report element outside of the SVN namespace they'll get
363 * what they deserve... */
364 sep
= ap_strchr_c(name
, ':');
370 case STATE_BEGINNING
:
371 if (strcmp(name
, "update-report") == 0)
372 ctx
->state
= STATE_IN_UPDATE
;
373 else if (strcmp(name
, "replay-report") == 0 && ctx
->cfg
->no_replay
)
375 /* XXX it would be useful if there was a way to override this
376 * on a per-user basis... */
377 if (! is_this_legal(ctx
, ctx
->r
->unparsed_uri
))
378 ctx
->no_soup_for_you
= TRUE
;
380 ctx
->let_it_go
= TRUE
;
383 ctx
->let_it_go
= TRUE
;
386 case STATE_IN_UPDATE
:
387 if (strcmp(name
, "src-path") == 0)
389 ctx
->state
= STATE_IN_SRC_PATH
;
391 ctx
->buffer
->len
= 0;
393 else if (strcmp(name
, "dst-path") == 0)
395 ctx
->state
= STATE_IN_DST_PATH
;
397 ctx
->buffer
->len
= 0;
399 else if (strcmp(name
, "recursive") == 0)
401 ctx
->state
= STATE_IN_RECURSIVE
;
403 ctx
->buffer
->len
= 0;
406 ; /* XXX Figure out what else we need to deal with... Switch
407 * has that link-path thing we probably need to look out
417 end_element(void *baton
, const char *name
)
419 dontdothat_filter_ctx
*ctx
= baton
;
422 if (ctx
->no_soup_for_you
|| ctx
->let_it_go
)
425 /* XXX Hack. We should be doing real namespace support, but for now we
426 * just skip ahead of any namespace prefix. If someone's sending us
427 * an update-report element outside of the SVN namespace they'll get
428 * what they deserve... */
429 sep
= ap_strchr_c(name
, ':');
435 case STATE_IN_SRC_PATH
:
436 ctx
->state
= STATE_IN_UPDATE
;
438 svn_stringbuf_strip_whitespace(ctx
->buffer
);
440 if (! ctx
->path_failed
&& ! is_this_legal(ctx
, ctx
->buffer
->data
))
441 ctx
->path_failed
= TRUE
;
444 case STATE_IN_DST_PATH
:
445 ctx
->state
= STATE_IN_UPDATE
;
447 svn_stringbuf_strip_whitespace(ctx
->buffer
);
449 if (! ctx
->path_failed
&& ! is_this_legal(ctx
, ctx
->buffer
->data
))
450 ctx
->path_failed
= TRUE
;
453 case STATE_IN_RECURSIVE
:
454 ctx
->state
= STATE_IN_UPDATE
;
456 svn_stringbuf_strip_whitespace(ctx
->buffer
);
458 /* If this isn't recursive we let it go. */
459 if (strcmp(ctx
->buffer
->data
, "no") == 0)
461 ap_log_rerror(APLOG_MARK
, APLOG_DEBUG
, 0, ctx
->r
,
462 "mod_dontdothat: letting nonrecursive request go");
463 ctx
->let_it_go
= TRUE
;
467 case STATE_IN_UPDATE
:
468 if (strcmp(name
, "update-report") == 0)
470 /* If we made it here without figuring out that this is
471 * nonrecursive, then the path check is our final word
474 if (ctx
->path_failed
)
475 ctx
->no_soup_for_you
= TRUE
;
477 ctx
->let_it_go
= TRUE
;
480 ; /* XXX Is there other stuff we care about? */
489 is_valid_wildcard(const char *wc
)
495 if (wc
[1] && wc
[1] != '/')
506 config_enumerator(const char *wildcard
,
511 dontdothat_filter_ctx
*ctx
= baton
;
513 if (strcmp(action
, "deny") == 0)
515 if (is_valid_wildcard(wildcard
))
516 APR_ARRAY_PUSH(ctx
->no_recursive_ops
, const char *) = wildcard
;
518 ctx
->err
= svn_error_createf(APR_EINVAL
,
520 "'%s' is an invalid wildcard",
523 else if (strcmp(action
, "allow") == 0)
525 if (is_valid_wildcard(wildcard
))
526 APR_ARRAY_PUSH(ctx
->allow_recursive_ops
, const char *) = wildcard
;
528 ctx
->err
= svn_error_createf(APR_EINVAL
,
530 "'%s' is an invalid wildcard",
535 ctx
->err
= svn_error_createf(APR_EINVAL
,
537 "'%s' is not a valid action",
548 clean_up_parser(void *baton
)
550 XML_Parser xmlp
= baton
;
552 XML_ParserFree(xmlp
);
558 dontdothat_insert_filters(request_rec
*r
)
560 dontdothat_config_rec
*cfg
= ap_get_module_config(r
->per_dir_config
,
563 if (! cfg
->config_file
)
566 if (strcmp("REPORT", r
->method
) == 0)
568 dontdothat_filter_ctx
*ctx
= apr_pcalloc(r
->pool
, sizeof(*ctx
));
569 svn_config_t
*config
;
576 ctx
->allow_recursive_ops
= apr_array_make(r
->pool
, 5, sizeof(char *));
578 ctx
->no_recursive_ops
= apr_array_make(r
->pool
, 5, sizeof(char *));
580 /* XXX is there a way to error out from this point? Would be nice... */
582 err
= svn_config_read(&config
, cfg
->config_file
, TRUE
, r
->pool
);
587 ap_log_rerror(APLOG_MARK
, APLOG_ERR
,
588 ((err
->apr_err
>= APR_OS_START_USERERR
&&
589 err
->apr_err
< APR_OS_START_CANONERR
) ?
591 r
, "Failed to load DontDoThatConfigFile: %s",
592 svn_err_best_message(err
, buff
, sizeof(buff
)));
594 svn_error_clear(err
);
599 svn_config_enumerate2(config
,
608 ap_log_rerror(APLOG_MARK
, APLOG_ERR
,
609 ((ctx
->err
->apr_err
>= APR_OS_START_USERERR
&&
610 ctx
->err
->apr_err
< APR_OS_START_CANONERR
) ?
611 0 : ctx
->err
->apr_err
),
612 r
, "Failed to parse DontDoThatConfigFile: %s",
613 svn_err_best_message(ctx
->err
, buff
, sizeof(buff
)));
615 svn_error_clear(ctx
->err
);
620 ctx
->state
= STATE_BEGINNING
;
622 ctx
->xmlp
= XML_ParserCreate(NULL
);
624 apr_pool_cleanup_register(r
->pool
, ctx
->xmlp
,
626 apr_pool_cleanup_null
);
628 XML_SetUserData(ctx
->xmlp
, ctx
);
629 XML_SetElementHandler(ctx
->xmlp
, start_element
, end_element
);
630 XML_SetCharacterDataHandler(ctx
->xmlp
, cdata
);
632 ap_add_input_filter("DONTDOTHAT_FILTER", ctx
, r
, r
->connection
);
637 dontdothat_register_hooks(apr_pool_t
*pool
)
639 ap_hook_insert_filter(dontdothat_insert_filters
, NULL
, NULL
, APR_HOOK_FIRST
);
641 ap_register_input_filter("DONTDOTHAT_FILTER",
647 module AP_MODULE_DECLARE_DATA dontdothat_module
=
649 STANDARD20_MODULE_STUFF
,
650 create_dontdothat_dir_config
,
655 dontdothat_register_hooks