Followup to r29625: fix getopt tests.
[svn.git] / subversion / mod_authz_svn / mod_authz_svn.c
blobf7481558f9b1001ae03b40ad8fe50af67d55ed16
1 /*
2 * mod_authz_svn.c: an Apache mod_dav_svn sub-module to provide path
3 * based authorization for a Subversion repository.
5 * ====================================================================
6 * Copyright (c) 2003-2005 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 * ====================================================================
22 #include <httpd.h>
23 #include <http_config.h>
24 #include <http_core.h>
25 #include <http_request.h>
26 #include <http_protocol.h>
27 #include <http_log.h>
28 #include <ap_config.h>
29 #include <ap_provider.h>
30 #include <apr_uri.h>
31 #include <mod_dav.h>
33 #include "mod_dav_svn.h"
34 #include "mod_authz_svn.h"
35 #include "svn_path.h"
36 #include "svn_config.h"
37 #include "svn_string.h"
38 #include "svn_repos.h"
41 extern module AP_MODULE_DECLARE_DATA authz_svn_module;
43 typedef struct {
44 int authoritative;
45 int anonymous;
46 int no_auth_when_anon_ok;
47 const char *base_path;
48 const char *access_file;
49 } authz_svn_config_rec;
52 * Configuration
55 static void *create_authz_svn_dir_config(apr_pool_t *p, char *d)
57 authz_svn_config_rec *conf = apr_pcalloc(p, sizeof(*conf));
58 conf->base_path = d;
60 /* By default keep the fortress secure */
61 conf->authoritative = 1;
62 conf->anonymous = 1;
64 return conf;
67 static const command_rec authz_svn_cmds[] =
69 AP_INIT_FLAG("AuthzSVNAuthoritative", ap_set_flag_slot,
70 (void *)APR_OFFSETOF(authz_svn_config_rec, authoritative),
71 OR_AUTHCFG,
72 "Set to 'Off' to allow access control to be passed along to "
73 "lower modules. (default is On.)"),
74 AP_INIT_TAKE1("AuthzSVNAccessFile", ap_set_file_slot,
75 (void *)APR_OFFSETOF(authz_svn_config_rec, access_file),
76 OR_AUTHCFG,
77 "Text file containing permissions of repository paths."),
78 AP_INIT_FLAG("AuthzSVNAnonymous", ap_set_flag_slot,
79 (void *)APR_OFFSETOF(authz_svn_config_rec, anonymous),
80 OR_AUTHCFG,
81 "Set to 'Off' to disable two special-case behaviours of "
82 "this module: (1) interaction with the 'Satisfy Any' "
83 "directive, and (2) enforcement of the authorization "
84 "policy even when no 'Require' directives are present. "
85 "(default is On.)"),
86 AP_INIT_FLAG("AuthzSVNNoAuthWhenAnonymousAllowed", ap_set_flag_slot,
87 (void *)APR_OFFSETOF(authz_svn_config_rec,
88 no_auth_when_anon_ok),
89 OR_AUTHCFG,
90 "Set to 'On' to suppress authentication and authorization "
91 "for requests which anonymous users are allowed to perform. "
92 "(default is Off.)"),
93 { NULL }
97 * Get the, possibly cached, svn_authz_t for this request.
99 static svn_authz_t *get_access_conf(request_rec *r,
100 authz_svn_config_rec *conf)
102 const char *cache_key = NULL;
103 void *user_data = NULL;
104 svn_authz_t *access_conf = NULL;
105 svn_error_t *svn_err;
106 char errbuf[256];
108 cache_key = apr_pstrcat(r->pool, "mod_authz_svn:",
109 conf->access_file, NULL);
110 apr_pool_userdata_get(&user_data, cache_key, r->connection->pool);
111 access_conf = user_data;
112 if (access_conf == NULL) {
113 svn_err = svn_repos_authz_read(&access_conf, conf->access_file,
114 TRUE, r->connection->pool);
115 if (svn_err) {
116 ap_log_rerror(APLOG_MARK, APLOG_ERR,
117 /* If it is an error code that APR can make sense
118 of, then show it, otherwise, pass zero to avoid
119 putting "APR does not understand this error code"
120 in the error log. */
121 ((svn_err->apr_err >= APR_OS_START_USERERR &&
122 svn_err->apr_err < APR_OS_START_CANONERR) ?
123 0 : svn_err->apr_err),
124 r, "Failed to load the AuthzSVNAccessFile: %s",
125 svn_err_best_message(svn_err,
126 errbuf, sizeof(errbuf)));
127 svn_error_clear(svn_err);
128 access_conf = NULL;
129 } else {
130 /* Cache the open repos for the next request on this connection */
131 apr_pool_userdata_set(access_conf, cache_key,
132 NULL, r->connection->pool);
135 return access_conf;
138 /* Check if the current request R is allowed. Upon exit *REPOS_PATH_REF
139 * will contain the path and repository name that an operation was requested
140 * on in the form 'name:path'. *DEST_REPOS_PATH_REF will contain the
141 * destination path if the requested operation was a MOVE or a COPY.
142 * Returns OK when access is allowed, DECLINED when it isn't, or an HTTP_
143 * error code when an error occurred.
145 static int req_check_access(request_rec *r,
146 authz_svn_config_rec *conf,
147 const char **repos_path_ref,
148 const char **dest_repos_path_ref)
150 const char *dest_uri;
151 apr_uri_t parsed_dest_uri;
152 const char *cleaned_uri;
153 int trailing_slash;
154 const char *repos_name;
155 const char *dest_repos_name;
156 const char *relative_path;
157 const char *repos_path;
158 const char *dest_repos_path = NULL;
159 dav_error *dav_err;
160 svn_repos_authz_access_t authz_svn_type = svn_authz_none;
161 svn_boolean_t authz_access_granted = FALSE;
162 svn_authz_t *access_conf = NULL;
163 svn_error_t *svn_err;
164 char errbuf[256];
166 switch (r->method_number) {
167 /* All methods requiring read access to all subtrees of r->uri */
168 case M_COPY:
169 authz_svn_type |= svn_authz_recursive;
171 /* All methods requiring read access to r->uri */
172 case M_OPTIONS:
173 case M_GET:
174 case M_PROPFIND:
175 case M_REPORT:
176 authz_svn_type |= svn_authz_read;
177 break;
179 /* All methods requiring write access to all subtrees of r->uri */
180 case M_MOVE:
181 case M_DELETE:
182 authz_svn_type |= svn_authz_recursive;
184 /* All methods requiring write access to r->uri */
185 case M_MKCOL:
186 case M_PUT:
187 case M_PROPPATCH:
188 case M_CHECKOUT:
189 case M_MERGE:
190 case M_MKACTIVITY:
191 case M_LOCK:
192 case M_UNLOCK:
193 authz_svn_type |= svn_authz_write;
194 break;
196 default:
197 /* Require most strict access for unknown methods */
198 authz_svn_type |= svn_authz_write | svn_authz_recursive;
199 break;
202 dav_err = dav_svn_split_uri(r,
203 r->uri,
204 conf->base_path,
205 &cleaned_uri,
206 &trailing_slash,
207 &repos_name,
208 &relative_path,
209 &repos_path);
210 if (dav_err) {
211 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
212 "%s [%d, #%d]",
213 dav_err->desc, dav_err->status, dav_err->error_id);
214 /* Ensure that we never allow access by dav_err->status */
215 return (dav_err->status != OK && dav_err->status != DECLINED) ?
216 dav_err->status : HTTP_INTERNAL_SERVER_ERROR;
219 /* Ignore the URI passed to MERGE, like mod_dav_svn does.
220 * See issue #1821.
221 * XXX: When we start accepting a broader range of DeltaV MERGE
222 * XXX: requests, this should be revisited.
224 if (r->method_number == M_MERGE) {
225 repos_path = NULL;
228 if (repos_path)
229 repos_path = svn_path_join("/", repos_path, r->pool);
231 *repos_path_ref = apr_pstrcat(r->pool, repos_name, ":", repos_path, NULL);
233 if (r->method_number == M_MOVE || r->method_number == M_COPY) {
234 dest_uri = apr_table_get(r->headers_in, "Destination");
236 /* Decline MOVE or COPY when there is no Destination uri, this will
237 * cause failure.
239 if (!dest_uri)
240 return DECLINED;
242 apr_uri_parse(r->pool, dest_uri, &parsed_dest_uri);
244 ap_unescape_url(parsed_dest_uri.path);
245 dest_uri = parsed_dest_uri.path;
246 if (strncmp(dest_uri, conf->base_path, strlen(conf->base_path))) {
247 /* If it is not the same location, then we don't allow it.
248 * XXX: Instead we could compare repository uuids, but that
249 * XXX: seems a bit over the top.
251 return HTTP_BAD_REQUEST;
254 dav_err = dav_svn_split_uri(r,
255 dest_uri,
256 conf->base_path,
257 &cleaned_uri,
258 &trailing_slash,
259 &dest_repos_name,
260 &relative_path,
261 &dest_repos_path);
263 if (dav_err) {
264 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
265 "%s [%d, #%d]",
266 dav_err->desc, dav_err->status, dav_err->error_id);
267 /* Ensure that we never allow access by dav_err->status */
268 return (dav_err->status != OK && dav_err->status != DECLINED) ?
269 dav_err->status : HTTP_INTERNAL_SERVER_ERROR;
272 if (dest_repos_path)
273 dest_repos_path = svn_path_join("/", dest_repos_path, r->pool);
275 *dest_repos_path_ref = apr_pstrcat(r->pool, dest_repos_name, ":",
276 dest_repos_path, NULL);
279 /* Retrieve/cache authorization file */
280 access_conf = get_access_conf(r,conf);
281 if (access_conf == NULL)
283 return DECLINED;
286 /* Perform authz access control.
288 * First test the special case where repos_path == NULL, and skip
289 * calling the authz routines in that case. This is an oddity of
290 * the DAV RA method: some requests have no repos_path, but apache
291 * still triggers an authz lookup for the URI.
293 * However, if repos_path == NULL and the request requires write
294 * access, then perform a global authz lookup. The request is
295 * denied if the user commiting isn't granted any access anywhere
296 * in the repository. This is to avoid operations that involve no
297 * paths (commiting an empty revision, leaving a dangling
298 * transaction in the FS) being granted by default, letting
299 * unauthenticated users write some changes to the repository.
300 * This was issue #2388.
302 * XXX: For now, requesting access to the entire repository always
303 * XXX: succeeds, until we come up with a good way of figuring
304 * XXX: this out.
306 if (repos_path
307 || (!repos_path && (authz_svn_type & svn_authz_write)))
309 svn_err = svn_repos_authz_check_access(access_conf, repos_name,
310 repos_path, r->user,
311 authz_svn_type,
312 &authz_access_granted,
313 r->pool);
314 if (svn_err) {
315 ap_log_rerror(APLOG_MARK, APLOG_ERR,
316 /* If it is an error code that APR can make
317 sense of, then show it, otherwise, pass
318 zero to avoid putting "APR does not
319 understand this error code" in the error
320 log. */
321 ((svn_err->apr_err >= APR_OS_START_USERERR &&
322 svn_err->apr_err < APR_OS_START_CANONERR) ?
323 0 : svn_err->apr_err),
324 r, "Failed to perform access control: %s",
325 svn_err_best_message(svn_err, errbuf, sizeof(errbuf)));
326 svn_error_clear(svn_err);
328 return DECLINED;
330 if (!authz_access_granted)
331 return DECLINED;
334 /* XXX: MKCOL, MOVE, DELETE
335 * XXX: Require write access to the parent dir of repos_path.
338 /* XXX: PUT
339 * XXX: If the path doesn't exist, require write access to the
340 * XXX: parent dir of repos_path.
343 /* Only MOVE and COPY have a second uri we have to check access to. */
344 if (r->method_number != M_MOVE
345 && r->method_number != M_COPY) {
346 return OK;
349 /* Check access on the destination repos_path. Again, skip this if
350 repos_path == NULL (see above for explanations) */
351 if (repos_path)
353 svn_err = svn_repos_authz_check_access(access_conf,
354 dest_repos_name,
355 dest_repos_path,
356 r->user,
357 svn_authz_write
358 |svn_authz_recursive,
359 &authz_access_granted,
360 r->pool);
361 if (svn_err) {
362 ap_log_rerror(APLOG_MARK, APLOG_ERR,
363 /* If it is an error code that APR can make sense
364 of, then show it, otherwise, pass zero to avoid
365 putting "APR does not understand this error code"
366 in the error log. */
367 ((svn_err->apr_err >= APR_OS_START_USERERR &&
368 svn_err->apr_err < APR_OS_START_CANONERR) ?
369 0 : svn_err->apr_err),
370 r, "Failed to perform access control: %s",
371 svn_err_best_message(svn_err, errbuf, sizeof(errbuf)));
372 svn_error_clear(svn_err);
374 return DECLINED;
376 if (!authz_access_granted)
377 return DECLINED;
380 /* XXX: MOVE and COPY, if the path doesn't exist yet, also
381 * XXX: require write access to the parent dir of dest_repos_path.
384 return OK;
387 /* Log a message indicating the access control decision made about a
388 * request. FILE and LINE should be supplied via the APLOG_MARK macro.
389 * ALLOWED is boolean. REPOS_PATH and DEST_REPOS_PATH are information
390 * about the request. DEST_REPOS_PATH may be NULL. */
391 static void log_access_verdict(const char *file, int line,
392 const request_rec *r,
393 int allowed,
394 const char *repos_path,
395 const char *dest_repos_path)
397 int level = allowed ? APLOG_INFO : APLOG_ERR;
398 const char *verdict = allowed ? "granted" : "denied";
400 if (r->user) {
401 if (dest_repos_path) {
402 ap_log_rerror(file, line, level, 0, r,
403 "Access %s: '%s' %s %s %s", verdict, r->user,
404 r->method, repos_path, dest_repos_path);
406 else {
407 ap_log_rerror(file, line, level, 0, r,
408 "Access %s: '%s' %s %s", verdict, r->user,
409 r->method, repos_path);
412 else {
413 if (dest_repos_path) {
414 ap_log_rerror(file, line, level, 0, r,
415 "Access %s: - %s %s %s", verdict,
416 r->method, repos_path, dest_repos_path);
418 else {
419 ap_log_rerror(file, line, level, 0, r,
420 "Access %s: - %s %s", verdict,
421 r->method, repos_path);
427 * This function is used as a provider to allow mod_dav_svn to bypass the
428 * generation of an apache request when checking GET access from
429 * "mod_dav_svn/authz.c" .
431 static int subreq_bypass(request_rec *r,
432 const char *repos_path,
433 const char *repos_name)
435 svn_error_t *svn_err = NULL;
436 svn_authz_t *access_conf = NULL;
437 authz_svn_config_rec *conf = NULL;
438 svn_boolean_t authz_access_granted = FALSE;
439 char errbuf[256];
441 conf = ap_get_module_config(r->per_dir_config,
442 &authz_svn_module);
444 /* If configured properly, this should never be true, but just in case. */
445 if (!conf->anonymous || !conf->access_file) {
446 log_access_verdict(APLOG_MARK, r, 0, repos_path, NULL);
447 return HTTP_FORBIDDEN;
450 /* Retrieve authorization file */
451 access_conf = get_access_conf(r,conf);
452 if (access_conf == NULL)
453 return HTTP_FORBIDDEN;
455 /* Perform authz access control.
456 * See similarly labeled comment in req_check_access.
458 if (repos_path) {
459 svn_err = svn_repos_authz_check_access(access_conf, repos_name,
460 repos_path, r->user,
461 svn_authz_none|svn_authz_read,
462 &authz_access_granted,
463 r->pool);
464 if (svn_err) {
465 ap_log_rerror(APLOG_MARK, APLOG_ERR,
466 /* If it is an error code that APR can make
467 sense of, then show it, otherwise, pass
468 zero to avoid putting "APR does not
469 understand this error code" in the error
470 log. */
471 ((svn_err->apr_err >= APR_OS_START_USERERR &&
472 svn_err->apr_err < APR_OS_START_CANONERR) ?
473 0 : svn_err->apr_err),
474 r, "Failed to perform access control: %s",
475 svn_err_best_message(svn_err, errbuf, sizeof(errbuf)));
476 svn_error_clear(svn_err);
477 return HTTP_FORBIDDEN;
479 if (!authz_access_granted) {
480 log_access_verdict(APLOG_MARK, r, 0, repos_path, NULL);
481 return HTTP_FORBIDDEN;
485 log_access_verdict(APLOG_MARK, r, 1, repos_path, NULL);
487 return OK;
491 * Hooks
494 static int access_checker(request_rec *r)
496 authz_svn_config_rec *conf = ap_get_module_config(r->per_dir_config,
497 &authz_svn_module);
498 const char *repos_path;
499 const char *dest_repos_path = NULL;
500 int status;
502 /* We are not configured to run */
503 if (!conf->anonymous || !conf->access_file)
504 return DECLINED;
506 if (ap_some_auth_required(r)) {
507 /* It makes no sense to check if a location is both accessible
508 * anonymous and by an authenticated user (in the same request!).
510 if (ap_satisfies(r) != SATISFY_ANY)
511 return DECLINED;
513 /* If the user is trying to authenticate, let him. If anonymous
514 * access is allowed, so is authenticated access, by definition
515 * of the meaning of '*' in the access file.
517 if (apr_table_get(r->headers_in,
518 (PROXYREQ_PROXY == r->proxyreq)
519 ? "Proxy-Authorization" : "Authorization")) {
520 /* Given Satisfy Any is in effect, we have to forbid access
521 * to let the auth_checker hook have a go at it.
523 return HTTP_FORBIDDEN;
527 /* If anon access is allowed, return OK */
528 status = req_check_access(r, conf, &repos_path, &dest_repos_path);
529 if (status == DECLINED) {
530 if (!conf->authoritative)
531 return DECLINED;
533 if (!ap_some_auth_required(r)) {
534 log_access_verdict(APLOG_MARK, r, 0, repos_path, dest_repos_path);
537 return HTTP_FORBIDDEN;
540 if (status != OK)
541 return status;
543 log_access_verdict(APLOG_MARK, r, 1, repos_path, dest_repos_path);
545 return OK;
548 static int check_user_id(request_rec *r)
550 authz_svn_config_rec *conf = ap_get_module_config(r->per_dir_config,
551 &authz_svn_module);
552 const char *repos_path;
553 const char *dest_repos_path = NULL;
554 int status;
556 /* We are not configured to run, or, an earlier module has already
557 * authenticated this request. */
558 if (!conf->access_file || !conf->no_auth_when_anon_ok || r->user)
559 return DECLINED;
561 /* If anon access is allowed, return OK, preventing later modules
562 * from issuing an HTTP_UNAUTHORIZED. Also pass a note to our
563 * auth_checker hook that access has already been checked. */
564 status = req_check_access(r, conf, &repos_path, &dest_repos_path);
565 if (status == OK) {
566 apr_table_setn(r->notes, "authz_svn-anon-ok", (const char*)1);
567 log_access_verdict(APLOG_MARK, r, 1, repos_path, dest_repos_path);
568 return OK;
571 return status;
574 static int auth_checker(request_rec *r)
576 authz_svn_config_rec *conf = ap_get_module_config(r->per_dir_config,
577 &authz_svn_module);
578 const char *repos_path;
579 const char *dest_repos_path = NULL;
580 int status;
582 /* We are not configured to run */
583 if (!conf->access_file)
584 return DECLINED;
586 /* Previous hook (check_user_id) already did all the work,
587 * and, as a sanity check, r->user hasn't been set since then? */
588 if (!r->user && apr_table_get(r->notes, "authz_svn-anon-ok")) {
589 return OK;
592 status = req_check_access(r, conf, &repos_path, &dest_repos_path);
593 if (status == DECLINED) {
594 if (conf->authoritative) {
595 log_access_verdict(APLOG_MARK, r, 0, repos_path, dest_repos_path);
596 ap_note_auth_failure(r);
597 return HTTP_FORBIDDEN;
600 return DECLINED;
603 if (status != OK)
604 return status;
606 log_access_verdict(APLOG_MARK, r, 1, repos_path, dest_repos_path);
608 return OK;
612 * Module flesh
615 static void register_hooks(apr_pool_t *p)
617 static const char * const mod_ssl[] = { "mod_ssl.c", NULL };
619 ap_hook_access_checker(access_checker, NULL, NULL, APR_HOOK_LAST);
620 /* Our check_user_id hook must be before any module which will return
621 * HTTP_UNAUTHORIZED (mod_auth_basic, etc.), but after mod_ssl, to
622 * give SSLOptions +FakeBasicAuth a chance to work. */
623 ap_hook_check_user_id(check_user_id, mod_ssl, NULL, APR_HOOK_FIRST);
624 ap_hook_auth_checker(auth_checker, NULL, NULL, APR_HOOK_FIRST);
625 ap_register_provider(p,
626 AUTHZ_SVN__SUBREQ_BYPASS_PROV_GRP,
627 AUTHZ_SVN__SUBREQ_BYPASS_PROV_NAME,
628 AUTHZ_SVN__SUBREQ_BYPASS_PROV_VER,
629 (void*)subreq_bypass);
632 module AP_MODULE_DECLARE_DATA authz_svn_module =
634 STANDARD20_MODULE_STUFF,
635 create_authz_svn_dir_config, /* dir config creater */
636 NULL, /* dir merger --- default is to override */
637 NULL, /* server config */
638 NULL, /* merge server config */
639 authz_svn_cmds, /* command apr_table_t */
640 register_hooks /* register hooks */