1 /*
2 * mod_authz_svn.c: an Apache mod_dav_svn sub-module to provide path
3 * based authorization for a Subversion repository.
4 *
5 * ====================================================================
6 * Licensed to the Apache Software Foundation (ASF) under one
7 * or more contributor license agreements. See the NOTICE file
8 * distributed with this work for additional information
9 * regarding copyright ownership. The ASF licenses this file
10 * to you under the Apache License, Version 2.0 (the
11 * "License"); you may not use this file except in compliance
12 * with the License. You may obtain a copy of the License at
13 *
14 * http://www.apache.org/licenses/LICENSE-2.0
15 *
16 * Unless required by applicable law or agreed to in writing,
17 * software distributed under the License is distributed on an
18 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19 * KIND, either express or implied. See the License for the
20 * specific language governing permissions and limitations
21 * under the License.
22 * ====================================================================
23 */
24
25
26
27 #include <httpd.h>
28 #include <http_config.h>
29 #include <http_core.h>
30 #include <http_request.h>
31 #include <http_protocol.h>
32 #include <http_log.h>
33 #include <http_config.h>
34 #include <ap_config.h>
35 #include <ap_provider.h>
36 #include <ap_mmn.h>
37 #include <apr_uri.h>
38 #include <apr_lib.h>
39 #include <mod_dav.h>
40
41 #include "mod_dav_svn.h"
42 #include "mod_authz_svn.h"
43 #include "svn_path.h"
44 #include "svn_config.h"
45 #include "svn_string.h"
46 #include "svn_repos.h"
47 #include "svn_pools.h"
48 #include "svn_dirent_uri.h"
49 #include "private/svn_fspath.h"
50
51 /* The apache headers define these and they conflict with our definitions. */
52 #ifdef PACKAGE_BUGREPORT
53 #undef PACKAGE_BUGREPORT
54 #endif
55 #ifdef PACKAGE_NAME
56 #undef PACKAGE_NAME
57 #endif
58 #ifdef PACKAGE_STRING
59 #undef PACKAGE_STRING
60 #endif
61 #ifdef PACKAGE_TARNAME
62 #undef PACKAGE_TARNAME
63 #endif
64 #ifdef PACKAGE_VERSION
65 #undef PACKAGE_VERSION
66 #endif
67 #include "svn_private_config.h"
68
69 #ifdef APLOG_USE_MODULE
70 APLOG_USE_MODULE(authz_svn);
71 #else
72 /* This is part of the APLOG_USE_MODULE() macro in httpd-2.3 */
73 extern module AP_MODULE_DECLARE_DATA authz_svn_module;
74 #endif
75
76 typedef struct authz_svn_config_rec {
77 int authoritative;
78 int anonymous;
79 int no_auth_when_anon_ok;
80 const char *base_path;
81 const char *access_file;
82 const char *repo_relative_access_file;
83 const char *groups_file;
84 const char *force_username_case;
85 } authz_svn_config_rec;
86
87 /* version where ap_some_auth_required breaks */
88 #if AP_MODULE_MAGIC_AT_LEAST(20060110,0)
89 /* first version with force_authn hook and ap_some_authn_required()
90 which allows us to work without ap_some_auth_required() */
91 # if AP_MODULE_MAGIC_AT_LEAST(20120211,47) || defined(SVN_USE_FORCE_AUTHN)
92 # define USE_FORCE_AUTHN 1
93 # define IN_SOME_AUTHN_NOTE "authz_svn-in-some-authn"
94 # define FORCE_AUTHN_NOTE "authz_svn-force-authn"
95 # else
96 /* ap_some_auth_required() is busted and no viable alternative exists */
97 # ifndef SVN_ALLOW_BROKEN_HTTPD_AUTH
98 # error This Apache httpd has broken auth (CVE-2015-3184)
99 # else
100 /* user wants to build anyway */
101 # define USE_FORCE_AUTHN 0
102 # endif
103 # endif
104 #else
105 /* old enough that ap_some_auth_required() still works */
106 # define USE_FORCE_AUTHN 0
107 #endif
108
109 /*
110 * Configuration
111 */
112
113 /* Implements the #create_dir_config method of Apache's #module vtable. */
114 static void *
create_authz_svn_dir_config(apr_pool_t * p,char * d)115 create_authz_svn_dir_config(apr_pool_t *p, char *d)
116 {
117 authz_svn_config_rec *conf = apr_pcalloc(p, sizeof(*conf));
118 conf->base_path = d;
119
120 if (d)
121 conf->base_path = svn_urlpath__canonicalize(d, p);
122
123 /* By default keep the fortress secure */
124 conf->authoritative = 1;
125 conf->anonymous = 1;
126
127 return conf;
128 }
129
130 /* canonicalize ACCESS_FILE based on the type of argument.
131 * If SERVER_RELATIVE is true, ACCESS_FILE is a relative
132 * path then ACCESS_FILE is converted to an absolute
133 * path rooted at the server root.
134 * Returns NULL if path is not valid.*/
135 static const char *
canonicalize_access_file(const char * access_file,svn_boolean_t server_relative,apr_pool_t * pool)136 canonicalize_access_file(const char *access_file,
137 svn_boolean_t server_relative,
138 apr_pool_t *pool)
139 {
140 if (svn_path_is_url(access_file))
141 {
142 access_file = svn_uri_canonicalize(access_file, pool);
143 }
144 else if (!svn_path_is_repos_relative_url(access_file))
145 {
146 if (server_relative)
147 {
148 access_file = ap_server_root_relative(pool, access_file);
149 if (access_file == NULL)
150 return NULL;
151 }
152
153 access_file = svn_dirent_internal_style(access_file, pool);
154 }
155
156 /* We don't canonicalize repos relative urls since they get
157 * canonicalized before calling svn_repos_authz_read3() when they
158 * are resolved. */
159
160 return access_file;
161 }
162
163 static const char *
AuthzSVNAccessFile_cmd(cmd_parms * cmd,void * config,const char * arg1)164 AuthzSVNAccessFile_cmd(cmd_parms *cmd, void *config, const char *arg1)
165 {
166 authz_svn_config_rec *conf = config;
167
168 if (conf->repo_relative_access_file != NULL)
169 return "AuthzSVNAccessFile and AuthzSVNReposRelativeAccessFile "
170 "directives are mutually exclusive.";
171
172 conf->access_file = canonicalize_access_file(arg1, TRUE, cmd->pool);
173 if (!conf->access_file)
174 return apr_pstrcat(cmd->pool, "Invalid file path ", arg1, SVN_VA_NULL);
175
176 return NULL;
177 }
178
179
180 static const char *
AuthzSVNReposRelativeAccessFile_cmd(cmd_parms * cmd,void * config,const char * arg1)181 AuthzSVNReposRelativeAccessFile_cmd(cmd_parms *cmd,
182 void *config,
183 const char *arg1)
184 {
185 authz_svn_config_rec *conf = config;
186
187 if (conf->access_file != NULL)
188 return "AuthzSVNAccessFile and AuthzSVNReposRelativeAccessFile "
189 "directives are mutually exclusive.";
190
191 conf->repo_relative_access_file = canonicalize_access_file(arg1, FALSE,
192 cmd->pool);
193
194 if (!conf->repo_relative_access_file)
195 return apr_pstrcat(cmd->pool, "Invalid file path ", arg1, SVN_VA_NULL);
196
197 return NULL;
198 }
199
200 static const char *
AuthzSVNGroupsFile_cmd(cmd_parms * cmd,void * config,const char * arg1)201 AuthzSVNGroupsFile_cmd(cmd_parms *cmd, void *config, const char *arg1)
202 {
203 authz_svn_config_rec *conf = config;
204
205 conf->groups_file = canonicalize_access_file(arg1, TRUE, cmd->pool);
206
207 if (!conf->groups_file)
208 return apr_pstrcat(cmd->pool, "Invalid file path ", arg1, SVN_VA_NULL);
209
210 return NULL;
211 }
212
213 /* Implements the #cmds member of Apache's #module vtable. */
214 static const command_rec authz_svn_cmds[] =
215 {
216 AP_INIT_FLAG("AuthzSVNAuthoritative", ap_set_flag_slot,
217 (void *)APR_OFFSETOF(authz_svn_config_rec, authoritative),
218 OR_AUTHCFG,
219 "Set to 'Off' to allow access control to be passed along to "
220 "lower modules. (default is On.)"),
221 AP_INIT_TAKE1("AuthzSVNAccessFile", AuthzSVNAccessFile_cmd,
222 NULL,
223 OR_AUTHCFG,
224 "Path to text file containing permissions of repository "
225 "paths. Path may be an repository relative URL (^/) or "
226 "absolute file:// URL to a text file in a Subversion "
227 "repository."),
228 AP_INIT_TAKE1("AuthzSVNReposRelativeAccessFile",
229 AuthzSVNReposRelativeAccessFile_cmd,
230 NULL,
231 OR_AUTHCFG,
232 "Path (relative to repository 'conf' directory) to text "
233 "file containing permissions of repository paths. Path may "
234 "be an repository relative URL (^/) or absolute file:// URL "
235 "to a text file in a Subversion repository."),
236 AP_INIT_TAKE1("AuthzSVNGroupsFile",
237 AuthzSVNGroupsFile_cmd,
238 NULL,
239 OR_AUTHCFG,
240 "Path to text file containing group definitions for all "
241 "repositories. Path may be an repository relative URL (^/) "
242 "or absolute file:// URL to a text file in a Subversion "
243 "repository."),
244 AP_INIT_FLAG("AuthzSVNAnonymous", ap_set_flag_slot,
245 (void *)APR_OFFSETOF(authz_svn_config_rec, anonymous),
246 OR_AUTHCFG,
247 "Set to 'Off' to disable two special-case behaviours of "
248 "this module: (1) interaction with the 'Satisfy Any' "
249 "directive, and (2) enforcement of the authorization "
250 "policy even when no 'Require' directives are present. "
251 "(default is On.)"),
252 AP_INIT_FLAG("AuthzSVNNoAuthWhenAnonymousAllowed", ap_set_flag_slot,
253 (void *)APR_OFFSETOF(authz_svn_config_rec,
254 no_auth_when_anon_ok),
255 OR_AUTHCFG,
256 "Set to 'On' to suppress authentication and authorization "
257 "for requests which anonymous users are allowed to perform. "
258 "(default is Off.)"),
259 AP_INIT_TAKE1("AuthzForceUsernameCase", ap_set_string_slot,
260 (void *)APR_OFFSETOF(authz_svn_config_rec,
261 force_username_case),
262 OR_AUTHCFG,
263 "Set to 'Upper' or 'Lower' to convert the username before "
264 "checking for authorization."),
265 { NULL }
266 };
267
268
269 /* The macros LOG_ARGS_SIGNATURE and LOG_ARGS_CASCADE are expanded as formal
270 * and actual parameters to log_access_verdict with respect to HTTPD version.
271 */
272 #if AP_MODULE_MAGIC_AT_LEAST(20100606,0)
273 #define LOG_ARGS_SIGNATURE const char *file, int line, int module_index
274 #define LOG_ARGS_CASCADE file, line, module_index
275 #else
276 #define LOG_ARGS_SIGNATURE const char *file, int line
277 #define LOG_ARGS_CASCADE file, line
278 #endif
279
280 /* Log a message indicating the access control decision made about a
281 * request. The macro LOG_ARGS_SIGNATURE expands to FILE, LINE and
282 * MODULE_INDEX in HTTPD 2.3 as APLOG_MARK macro has been changed for
283 * per-module loglevel configuration. It expands to FILE and LINE
284 * in older server versions. ALLOWED is boolean.
285 * REPOS_PATH and DEST_REPOS_PATH are information
286 * about the request. DEST_REPOS_PATH may be NULL.
287 * Non-zero IS_SUBREQ_BYPASS means that this authorization check was
288 * implicitly requested using 'subrequest bypass' callback from
289 * mod_dav_svn.
290 */
291 static void
log_access_verdict(LOG_ARGS_SIGNATURE,const request_rec * r,int allowed,int is_subreq_bypass,const char * repos_path,const char * dest_repos_path)292 log_access_verdict(LOG_ARGS_SIGNATURE,
293 const request_rec *r, int allowed, int is_subreq_bypass,
294 const char *repos_path, const char *dest_repos_path)
295 {
296 int level = allowed ? APLOG_INFO : APLOG_ERR;
297 const char *verdict = allowed ? "granted" : "denied";
298
299 /* Use less important log level for implicit sub-request authorization
300 checks. */
301 if (is_subreq_bypass)
302 level = APLOG_INFO;
303 else if (r->main && r->method_number == M_GET)
304 level = APLOG_INFO;
305
306 if (r->user)
307 {
308 if (dest_repos_path)
309 ap_log_rerror(LOG_ARGS_CASCADE, level, 0, r,
310 "Access %s: '%s' %s %s %s", verdict, r->user,
311 r->method, repos_path, dest_repos_path);
312 else
313 ap_log_rerror(LOG_ARGS_CASCADE, level, 0, r,
314 "Access %s: '%s' %s %s", verdict, r->user,
315 r->method, repos_path);
316 }
317 else
318 {
319 if (dest_repos_path)
320 ap_log_rerror(LOG_ARGS_CASCADE, level, 0, r,
321 "Access %s: - %s %s %s", verdict,
322 r->method, repos_path, dest_repos_path);
323 else
324 ap_log_rerror(LOG_ARGS_CASCADE, level, 0, r,
325 "Access %s: - %s %s", verdict,
326 r->method, repos_path);
327 }
328 }
329
330 /* Log a message at LOG_LEVEL indiciating the ERR encountered during
331 * the request R.
332 * LOG_ARGS_SIGNATURE expands as in log_access_verdict() above.
333 * PREFIX is inserted at the start of the message. The rest of the
334 * message is generated by combining the message for each error in the
335 * chain of ERR, excluding for trace errors. ERR will be cleared
336 * when finished. */
337 static void
log_svn_message(LOG_ARGS_SIGNATURE,int log_level,request_rec * r,const char * prefix,svn_error_t * err,apr_pool_t * scratch_pool)338 log_svn_message(LOG_ARGS_SIGNATURE, int log_level,
339 request_rec *r, const char *prefix,
340 svn_error_t *err, apr_pool_t *scratch_pool)
341 {
342 svn_error_t *err_pos = svn_error_purge_tracing(err);
343 svn_stringbuf_t *buff = svn_stringbuf_create(prefix, scratch_pool);
344
345 /* Build the error chain into a space separated stringbuf. */
346 while (err_pos)
347 {
348 svn_stringbuf_appendbyte(buff, ' ');
349 if (err_pos->message)
350 {
351 svn_stringbuf_appendcstr(buff, err_pos->message);
352 }
353 else
354 {
355 char strerr[256];
356
357 svn_stringbuf_appendcstr(buff, svn_strerror(err->apr_err, strerr,
358 sizeof(strerr)));
359 }
360
361 err_pos = err_pos->child;
362 }
363
364 ap_log_rerror(LOG_ARGS_CASCADE, log_level,
365 /* If it is an error code that APR can make sense of, then
366 show it, otherwise, pass zero to avoid putting "APR does
367 not understand this error code" in the error log. */
368 ((err->apr_err >= APR_OS_START_USERERR &&
369 err->apr_err < APR_OS_START_CANONERR) ?
370 0 : err->apr_err),
371 r, "%s", buff->data);
372
373 svn_error_clear(err);
374 }
375
376 /* Log the error error ERR encountered during the request R.
377 * LOG_ARGS_SIGNATURE expands as in log_access_verdict() above.
378 * PREFIX is inserted at the start of the message. The rest of the
379 * message is generated by combining the message for each error in the
380 * chain of ERR, excluding for trace errors. ERR will be cleared
381 * when finished. */
382 static APR_INLINE void
log_svn_error(LOG_ARGS_SIGNATURE,request_rec * r,const char * prefix,svn_error_t * err,apr_pool_t * scratch_pool)383 log_svn_error(LOG_ARGS_SIGNATURE,
384 request_rec *r, const char *prefix,
385 svn_error_t *err, apr_pool_t *scratch_pool)
386 {
387 log_svn_message(LOG_ARGS_CASCADE, APLOG_ERR,
388 r, prefix, err, scratch_pool);
389 }
390
391 /* Baton for log_authz_warning. */
392 typedef struct authz_warning_baton_t
393 {
394 request_rec *r;
395 const char *prefix;
396 } authz_warning_baton_t;
397
398 /* Handle an authz parser warning. ERR will *not* be cleared.*/
399 static APR_INLINE void
log_authz_warning(void * baton,const svn_error_t * err,apr_pool_t * scratch_pool)400 log_authz_warning(void *baton,
401 const svn_error_t *err,
402 apr_pool_t *scratch_pool)
403 {
404 const authz_warning_baton_t *const warning_baton = baton;
405 log_svn_message(APLOG_MARK, APLOG_WARNING,
406 warning_baton->r, warning_baton->prefix,
407 svn_error_dup(err), scratch_pool);
408 }
409
410 /* Resolve *PATH into an absolute canonical URL iff *PATH is a repos-relative
411 * URL. If *REPOS_URL is NULL convert REPOS_PATH into a file URL stored
412 * in *REPOS_URL, if *REPOS_URL is not null REPOS_PATH is ignored. The
413 * resulting *REPOS_URL will be used as the root of the repos-relative URL.
414 * The result will be stored in *PATH. */
415 static svn_error_t *
resolve_repos_relative_url(const char ** path,const char ** repos_url,const char * repos_path,apr_pool_t * pool)416 resolve_repos_relative_url(const char **path, const char **repos_url,
417 const char *repos_path, apr_pool_t *pool)
418 {
419 if (svn_path_is_repos_relative_url(*path))
420 {
421 if (!*repos_url)
422 SVN_ERR(svn_uri_get_file_url_from_dirent(repos_url, repos_path, pool));
423
424 SVN_ERR(svn_path_resolve_repos_relative_url(path, *path,
425 *repos_url, pool));
426 *path = svn_uri_canonicalize(*path, pool);
427 }
428
429 return SVN_NO_ERROR;
430 }
431
432 /*
433 * Get the, possibly cached, svn_authz_t for this request.
434 */
435 static svn_authz_t *
get_access_conf(request_rec * r,authz_svn_config_rec * conf,apr_pool_t * scratch_pool)436 get_access_conf(request_rec *r, authz_svn_config_rec *conf,
437 apr_pool_t *scratch_pool)
438 {
439 const char *cache_key = NULL;
440 const char *access_file;
441 const char *groups_file;
442 const char *repos_path;
443 const char *repos_url = NULL;
444 void *user_data = NULL;
445 svn_authz_t *access_conf = NULL;
446 svn_error_t *svn_err = SVN_NO_ERROR;
447 dav_error *dav_err;
448
449 dav_err = dav_svn_get_repos_path2(r, conf->base_path, &repos_path, scratch_pool);
450 if (dav_err)
451 {
452 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "%s", dav_err->desc);
453 return NULL;
454 }
455
456 if (conf->repo_relative_access_file)
457 {
458 access_file = conf->repo_relative_access_file;
459 if (!svn_path_is_repos_relative_url(access_file) &&
460 !svn_path_is_url(access_file))
461 {
462 access_file = svn_dirent_join_many(scratch_pool, repos_path, "conf",
463 conf->repo_relative_access_file,
464 SVN_VA_NULL);
465 }
466 }
467 else
468 {
469 access_file = conf->access_file;
470 }
471 groups_file = conf->groups_file;
472
473 svn_err = resolve_repos_relative_url(&access_file, &repos_url, repos_path,
474 scratch_pool);
475 if (svn_err)
476 {
477 log_svn_error(APLOG_MARK, r,
478 conf->repo_relative_access_file ?
479 "Failed to load the AuthzSVNReposRelativeAccessFile:" :
480 "Failed to load the AuthzSVNAccessFile:",
481 svn_err, scratch_pool);
482 return NULL;
483 }
484
485 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
486 "Path to authz file is %s", access_file);
487
488 if (groups_file)
489 {
490 svn_err = resolve_repos_relative_url(&groups_file, &repos_url, repos_path,
491 scratch_pool);
492 if (svn_err)
493 {
494 log_svn_error(APLOG_MARK, r,
495 "Failed to load the AuthzSVNGroupsFile:",
496 svn_err, scratch_pool);
497 return NULL;
498 }
499
500 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
501 "Path to groups file is %s", groups_file);
502 }
503
504 cache_key = apr_pstrcat(scratch_pool, "mod_authz_svn:",
505 access_file, groups_file, SVN_VA_NULL);
506 apr_pool_userdata_get(&user_data, cache_key, r->connection->pool);
507 access_conf = user_data;
508 if (access_conf == NULL)
509 {
510 authz_warning_baton_t warning_baton;
511 warning_baton.r = r;
512 warning_baton.prefix = "mod_authz_svn: warning:";
513
514 svn_err = svn_repos_authz_read4(&access_conf, access_file,
515 groups_file, TRUE, NULL,
516 log_authz_warning, &warning_baton,
517 r->connection->pool,
518 scratch_pool);
519
520 if (svn_err)
521 {
522 log_svn_error(APLOG_MARK, r,
523 "Failed to load the mod_authz_svn config:",
524 svn_err, scratch_pool);
525 access_conf = NULL;
526 }
527 else
528 {
529 /* Cache the open repos for the next request on this connection */
530 apr_pool_userdata_set(access_conf, cache_key,
531 NULL, r->connection->pool);
532 }
533 }
534 return access_conf;
535 }
536
537 /* Convert TEXT to upper case if TO_UPPERCASE is TRUE, else
538 converts it to lower case. */
539 static void
convert_case(char * text,svn_boolean_t to_uppercase)540 convert_case(char *text, svn_boolean_t to_uppercase)
541 {
542 char *c = text;
543 while (*c)
544 {
545 *c = (char)(to_uppercase ? apr_toupper(*c) : apr_tolower(*c));
546 ++c;
547 }
548 }
549
550 /* Return the username to authorize, with case-conversion performed if
551 CONF->force_username_case is set. */
552 static char *
get_username_to_authorize(request_rec * r,authz_svn_config_rec * conf,apr_pool_t * pool)553 get_username_to_authorize(request_rec *r, authz_svn_config_rec *conf,
554 apr_pool_t *pool)
555 {
556 char *username_to_authorize = r->user;
557 if (username_to_authorize && conf->force_username_case)
558 {
559 username_to_authorize = apr_pstrdup(pool, r->user);
560 convert_case(username_to_authorize,
561 strcasecmp(conf->force_username_case, "upper") == 0);
562 }
563 return username_to_authorize;
564 }
565
566 /* Check if the current request R is allowed. Upon exit *REPOS_PATH_REF
567 * will contain the path and repository name that an operation was requested
568 * on in the form 'name:path'. *DEST_REPOS_PATH_REF will contain the
569 * destination path if the requested operation was a MOVE or a COPY.
570 * Returns OK when access is allowed, DECLINED when it isn't, or an HTTP_
571 * error code when an error occurred.
572 */
573 static int
req_check_access(request_rec * r,authz_svn_config_rec * conf,const char ** repos_path_ref,const char ** dest_repos_path_ref)574 req_check_access(request_rec *r,
575 authz_svn_config_rec *conf,
576 const char **repos_path_ref,
577 const char **dest_repos_path_ref)
578 {
579 const char *dest_uri;
580 apr_uri_t parsed_dest_uri;
581 const char *cleaned_uri;
582 int trailing_slash;
583 const char *repos_name;
584 const char *dest_repos_name;
585 const char *relative_path;
586 const char *repos_path;
587 const char *dest_repos_path = NULL;
588 dav_error *dav_err;
589 svn_repos_authz_access_t authz_svn_type = svn_authz_none;
590 svn_boolean_t authz_access_granted = FALSE;
591 svn_authz_t *access_conf = NULL;
592 svn_error_t *svn_err;
593 const char *username_to_authorize = get_username_to_authorize(r, conf,
594 r->pool);
595
596 switch (r->method_number)
597 {
598 /* All methods requiring read access to all subtrees of r->uri */
599 case M_COPY:
600 authz_svn_type |= svn_authz_recursive;
601
602 /* All methods requiring read access to r->uri */
603 case M_OPTIONS:
604 case M_GET:
605 case M_PROPFIND:
606 case M_REPORT:
607 authz_svn_type |= svn_authz_read;
608 break;
609
610 /* All methods requiring write access to all subtrees of r->uri */
611 case M_MOVE:
612 case M_DELETE:
613 authz_svn_type |= svn_authz_recursive;
614
615 /* All methods requiring write access to r->uri */
616 case M_MKCOL:
617 case M_PUT:
618 case M_PROPPATCH:
619 case M_CHECKOUT:
620 case M_MERGE:
621 case M_MKACTIVITY:
622 case M_LOCK:
623 case M_UNLOCK:
624 authz_svn_type |= svn_authz_write;
625 break;
626
627 default:
628 /* Require most strict access for unknown methods */
629 authz_svn_type |= svn_authz_write | svn_authz_recursive;
630 break;
631 }
632
633 if (strcmp(svn_urlpath__canonicalize(r->uri, r->pool), conf->base_path) == 0)
634 {
635 /* Do no access control when conf->base_path(as configured in <Location>)
636 * and given uri are same. The reason for such relaxation of access
637 * control is "This module is meant to control access inside the
638 * repository path, in this case inside PATH is empty and hence
639 * dav_svn_split_uri fails saying no repository name present".
640 * One may ask it will allow access to '/' inside the repository if
641 * repository is served via SVNPath instead of SVNParentPath.
642 * It does not, The other methods(PROPFIND, MKACTIVITY) for
643 * accomplishing the operation takes care of making a request to
644 * proper URL */
645 return OK;
646 }
647
648 dav_err = dav_svn_split_uri(r,
649 r->uri,
650 conf->base_path,
651 &cleaned_uri,
652 &trailing_slash,
653 &repos_name,
654 &relative_path,
655 &repos_path);
656 if (dav_err)
657 {
658 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
659 "%s [%d, #%d]",
660 dav_err->desc, dav_err->status, dav_err->error_id);
661 /* Ensure that we never allow access by dav_err->status */
662 return (dav_err->status != OK && dav_err->status != DECLINED) ?
663 dav_err->status : HTTP_INTERNAL_SERVER_ERROR;
664 }
665
666 /* Ignore the URI passed to MERGE, like mod_dav_svn does.
667 * See issue #1821.
668 * XXX: When we start accepting a broader range of DeltaV MERGE
669 * XXX: requests, this should be revisited.
670 */
671 if (r->method_number == M_MERGE)
672 repos_path = NULL;
673
674 if (repos_path)
675 repos_path = svn_fspath__canonicalize(repos_path, r->pool);
676
677 *repos_path_ref = apr_pstrcat(r->pool, repos_name, ":", repos_path,
678 SVN_VA_NULL);
679
680 if (r->method_number == M_MOVE || r->method_number == M_COPY)
681 {
682 apr_status_t status;
683
684 dest_uri = apr_table_get(r->headers_in, "Destination");
685
686 /* Decline MOVE or COPY when there is no Destination uri, this will
687 * cause failure.
688 */
689 if (!dest_uri)
690 return DECLINED;
691
692 status = apr_uri_parse(r->pool, dest_uri, &parsed_dest_uri);
693 if (status)
694 {
695 ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r,
696 "Invalid URI in Destination header");
697 return HTTP_BAD_REQUEST;
698 }
699 if (!parsed_dest_uri.path)
700 {
701 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
702 "Invalid URI in Destination header");
703 return HTTP_BAD_REQUEST;
704 }
705
706 ap_unescape_url(parsed_dest_uri.path);
707 dest_uri = parsed_dest_uri.path;
708 if (strncmp(dest_uri, conf->base_path, strlen(conf->base_path)))
709 {
710 /* If it is not the same location, then we don't allow it.
711 * XXX: Instead we could compare repository uuids, but that
712 * XXX: seems a bit over the top.
713 */
714 return HTTP_BAD_REQUEST;
715 }
716
717 dav_err = dav_svn_split_uri(r,
718 dest_uri,
719 conf->base_path,
720 &cleaned_uri,
721 &trailing_slash,
722 &dest_repos_name,
723 &relative_path,
724 &dest_repos_path);
725
726 if (dav_err)
727 {
728 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
729 "%s [%d, #%d]",
730 dav_err->desc, dav_err->status, dav_err->error_id);
731 /* Ensure that we never allow access by dav_err->status */
732 return (dav_err->status != OK && dav_err->status != DECLINED) ?
733 dav_err->status : HTTP_INTERNAL_SERVER_ERROR;
734 }
735
736 if (dest_repos_path)
737 dest_repos_path = svn_fspath__canonicalize(dest_repos_path, r->pool);
738
739 *dest_repos_path_ref = apr_pstrcat(r->pool, dest_repos_name, ":",
740 dest_repos_path, SVN_VA_NULL);
741 }
742
743 /* Retrieve/cache authorization file */
744 access_conf = get_access_conf(r,conf, r->pool);
745 if (access_conf == NULL)
746 return DECLINED;
747
748 /* Perform authz access control.
749 *
750 * First test the special case where repos_path == NULL, and skip
751 * calling the authz routines in that case. This is an oddity of
752 * the DAV RA method: some requests have no repos_path, but apache
753 * still triggers an authz lookup for the URI.
754 *
755 * However, if repos_path == NULL and the request requires write
756 * access, then perform a global authz lookup. The request is
757 * denied if the user commiting isn't granted any access anywhere
758 * in the repository. This is to avoid operations that involve no
759 * paths (commiting an empty revision, leaving a dangling
760 * transaction in the FS) being granted by default, letting
761 * unauthenticated users write some changes to the repository.
762 * This was issue #2388.
763 *
764 * XXX: For now, requesting access to the entire repository always
765 * XXX: succeeds, until we come up with a good way of figuring
766 * XXX: this out.
767 */
768 if (repos_path
769 || (!repos_path && (authz_svn_type & svn_authz_write)))
770 {
771 svn_err = svn_repos_authz_check_access(access_conf, repos_name,
772 repos_path,
773 username_to_authorize,
774 authz_svn_type,
775 &authz_access_granted,
776 r->pool);
777 if (svn_err)
778 {
779 log_svn_error(APLOG_MARK, r,
780 "Failed to perform access control:",
781 svn_err, r->pool);
782
783 return DECLINED;
784 }
785 if (!authz_access_granted)
786 return DECLINED;
787 }
788
789 /* XXX: MKCOL, MOVE, DELETE
790 * XXX: Require write access to the parent dir of repos_path.
791 */
792
793 /* XXX: PUT
794 * XXX: If the path doesn't exist, require write access to the
795 * XXX: parent dir of repos_path.
796 */
797
798 /* Only MOVE and COPY have a second uri we have to check access to. */
799 if (r->method_number != M_MOVE && r->method_number != M_COPY)
800 return OK;
801
802 /* Check access on the destination repos_path. Again, skip this if
803 repos_path == NULL (see above for explanations) */
804 if (repos_path)
805 {
806 svn_err = svn_repos_authz_check_access(access_conf,
807 dest_repos_name,
808 dest_repos_path,
809 username_to_authorize,
810 svn_authz_write
811 |svn_authz_recursive,
812 &authz_access_granted,
813 r->pool);
814 if (svn_err)
815 {
816 log_svn_error(APLOG_MARK, r,
817 "Failed to perform access control:",
818 svn_err, r->pool);
819
820 return DECLINED;
821 }
822 if (!authz_access_granted)
823 return DECLINED;
824 }
825
826 /* XXX: MOVE and COPY, if the path doesn't exist yet, also
827 * XXX: require write access to the parent dir of dest_repos_path.
828 */
829
830 return OK;
831 }
832
833 /*
834 * Implementation of subreq_bypass with scratch_pool parameter.
835 */
836 static int
subreq_bypass2(request_rec * r,const char * repos_path,const char * repos_name,apr_pool_t * scratch_pool)837 subreq_bypass2(request_rec *r,
838 const char *repos_path,
839 const char *repos_name,
840 apr_pool_t *scratch_pool)
841 {
842 svn_error_t *svn_err = NULL;
843 svn_authz_t *access_conf = NULL;
844 authz_svn_config_rec *conf = NULL;
845 svn_boolean_t authz_access_granted = FALSE;
846 const char *username_to_authorize;
847
848 conf = ap_get_module_config(r->per_dir_config,
849 &authz_svn_module);
850 username_to_authorize = get_username_to_authorize(r, conf, scratch_pool);
851
852 /* If configured properly, this should never be true, but just in case. */
853 if (!conf->anonymous
854 || (! (conf->access_file || conf->repo_relative_access_file)))
855 {
856 log_access_verdict(APLOG_MARK, r, 0, TRUE, repos_path, NULL);
857 return HTTP_FORBIDDEN;
858 }
859
860 /* Retrieve authorization file */
861 access_conf = get_access_conf(r, conf, scratch_pool);
862 if (access_conf == NULL)
863 return HTTP_FORBIDDEN;
864
865 /* Perform authz access control.
866 * See similarly labeled comment in req_check_access.
867 */
868 if (repos_path)
869 {
870 svn_err = svn_repos_authz_check_access(access_conf, repos_name,
871 repos_path,
872 username_to_authorize,
873 svn_authz_none|svn_authz_read,
874 &authz_access_granted,
875 scratch_pool);
876 if (svn_err)
877 {
878 log_svn_error(APLOG_MARK, r,
879 "Failed to perform access control:",
880 svn_err, scratch_pool);
881 return HTTP_FORBIDDEN;
882 }
883 if (!authz_access_granted)
884 {
885 log_access_verdict(APLOG_MARK, r, 0, TRUE, repos_path, NULL);
886 return HTTP_FORBIDDEN;
887 }
888 }
889
890 log_access_verdict(APLOG_MARK, r, 1, TRUE, repos_path, NULL);
891
892 return OK;
893 }
894
895 /*
896 * This function is used as a provider to allow mod_dav_svn to bypass the
897 * generation of an apache request when checking GET access from
898 * "mod_dav_svn/authz.c" .
899 */
900 static int
subreq_bypass(request_rec * r,const char * repos_path,const char * repos_name)901 subreq_bypass(request_rec *r,
902 const char *repos_path,
903 const char *repos_name)
904 {
905 int status;
906 apr_pool_t *scratch_pool;
907
908 scratch_pool = svn_pool_create(r->pool);
909 status = subreq_bypass2(r, repos_path, repos_name, scratch_pool);
910 svn_pool_destroy(scratch_pool);
911
912 return status;
913 }
914
915 /*
916 * Hooks
917 */
918
919 static int
access_checker(request_rec * r)920 access_checker(request_rec *r)
921 {
922 authz_svn_config_rec *conf = ap_get_module_config(r->per_dir_config,
923 &authz_svn_module);
924 const char *repos_path = NULL;
925 const char *dest_repos_path = NULL;
926 int status, authn_required;
927
928 #if USE_FORCE_AUTHN
929 /* Use the force_authn() hook available in 2.4.x to work securely
930 * given that ap_some_auth_required() is no longer functional for our
931 * purposes in 2.4.x.
932 */
933 int authn_configured;
934
935 /* We are not configured to run */
936 if (!conf->anonymous || apr_table_get(r->notes, IN_SOME_AUTHN_NOTE)
937 || (! (conf->access_file || conf->repo_relative_access_file)))
938 return DECLINED;
939
940 /* Authentication is configured */
941 authn_configured = ap_auth_type(r) != NULL;
942 if (authn_configured)
943 {
944 /* If the user is trying to authenticate, let him. It doesn't
945 * make much sense to grant anonymous access but deny authenticated
946 * users access, even though you can do that with '$anon' in the
947 * access file.
948 */
949 if (apr_table_get(r->headers_in,
950 (PROXYREQ_PROXY == r->proxyreq)
951 ? "Proxy-Authorization" : "Authorization"))
952 {
953 /* Set the note to force authn regardless of what access_checker_ex
954 hook requires */
955 apr_table_setn(r->notes, FORCE_AUTHN_NOTE, "1");
956
957 /* provide the proper return so the access_checker hook doesn't
958 * prevent the code from continuing on to the other auth hooks */
959 if (ap_satisfies(r) != SATISFY_ANY)
960 return OK;
961 else
962 return HTTP_FORBIDDEN;
963 }
964 }
965
966 #else
967 /* Support for older versions of httpd that have a working
968 * ap_some_auth_required() */
969
970 /* We are not configured to run */
971 if (!conf->anonymous
972 || (! (conf->access_file || conf->repo_relative_access_file)))
973 return DECLINED;
974
975 authn_required = ap_some_auth_required(r);
976 if (authn_required)
977 {
978 /* It makes no sense to check if a location is both accessible
979 * anonymous and by an authenticated user (in the same request!).
980 */
981 if (ap_satisfies(r) != SATISFY_ANY)
982 return DECLINED;
983
984 /* If the user is trying to authenticate, let him. It doesn't
985 * make much sense to grant anonymous access but deny authenticated
986 * users access, even though you can do that with '$anon' in the
987 * access file.
988 */
989 if (apr_table_get(r->headers_in,
990 (PROXYREQ_PROXY == r->proxyreq)
991 ? "Proxy-Authorization" : "Authorization"))
992 {
993 /* Given Satisfy Any is in effect, we have to forbid access
994 * to let the auth_checker hook have a go at it.
995 */
996 return HTTP_FORBIDDEN;
997 }
998 }
999 #endif
1000
1001 /* If anon access is allowed, return OK */
1002 status = req_check_access(r, conf, &repos_path, &dest_repos_path);
1003 if (status == DECLINED)
1004 {
1005 if (!conf->authoritative)
1006 return DECLINED;
1007
1008 #if USE_FORCE_AUTHN
1009 if (authn_configured) {
1010 /* We have to check to see if authn is required because if so we must
1011 * return DECLINED rather than FORBIDDEN (403) since returning
1012 * the 403 leaks information about what paths may exist to
1013 * unauthenticated users. Returning DECLINED means apache's request
1014 * handling will continue until the authn module itself generates
1015 * UNAUTHORIZED (401).
1016
1017 * We must set a note here in order to use
1018 * ap_some_authn_rquired() without triggering an infinite
1019 * loop since the call will trigger this function to be
1020 * called again. */
1021 apr_table_setn(r->notes, IN_SOME_AUTHN_NOTE, "1");
1022 authn_required = ap_some_authn_required(r);
1023 apr_table_unset(r->notes, IN_SOME_AUTHN_NOTE);
1024 if (authn_required)
1025 return DECLINED;
1026 }
1027 #else
1028 if (!authn_required)
1029 #endif
1030 log_access_verdict(APLOG_MARK, r, 0, FALSE, repos_path, dest_repos_path);
1031
1032 return HTTP_FORBIDDEN;
1033 }
1034
1035 if (status != OK)
1036 return status;
1037
1038 log_access_verdict(APLOG_MARK, r, 1, FALSE, repos_path, dest_repos_path);
1039
1040 return OK;
1041 }
1042
1043 static int
check_user_id(request_rec * r)1044 check_user_id(request_rec *r)
1045 {
1046 authz_svn_config_rec *conf = ap_get_module_config(r->per_dir_config,
1047 &authz_svn_module);
1048 const char *repos_path = NULL;
1049 const char *dest_repos_path = NULL;
1050 int status;
1051
1052 /* We are not configured to run, or, an earlier module has already
1053 * authenticated this request. */
1054 if (!conf->no_auth_when_anon_ok || r->user
1055 || (! (conf->access_file || conf->repo_relative_access_file)))
1056 return DECLINED;
1057
1058 /* If anon access is allowed, return OK, preventing later modules
1059 * from issuing an HTTP_UNAUTHORIZED. Also pass a note to our
1060 * auth_checker hook that access has already been checked. */
1061 status = req_check_access(r, conf, &repos_path, &dest_repos_path);
1062 if (status == OK)
1063 {
1064 apr_table_setn(r->notes, "authz_svn-anon-ok", "1");
1065 log_access_verdict(APLOG_MARK, r, 1, FALSE, repos_path, dest_repos_path);
1066 return OK;
1067 }
1068
1069 return status;
1070 }
1071
1072 static int
auth_checker(request_rec * r)1073 auth_checker(request_rec *r)
1074 {
1075 authz_svn_config_rec *conf = ap_get_module_config(r->per_dir_config,
1076 &authz_svn_module);
1077 const char *repos_path = NULL;
1078 const char *dest_repos_path = NULL;
1079 int status;
1080
1081 /* We are not configured to run */
1082 if (! (conf->access_file || conf->repo_relative_access_file))
1083 return DECLINED;
1084
1085 /* Previous hook (check_user_id) already did all the work,
1086 * and, as a sanity check, r->user hasn't been set since then? */
1087 if (!r->user && apr_table_get(r->notes, "authz_svn-anon-ok"))
1088 return OK;
1089
1090 status = req_check_access(r, conf, &repos_path, &dest_repos_path);
1091 if (status == DECLINED)
1092 {
1093 if (conf->authoritative)
1094 {
1095 log_access_verdict(APLOG_MARK, r, 0, FALSE, repos_path, dest_repos_path);
1096 ap_note_auth_failure(r);
1097 return HTTP_FORBIDDEN;
1098 }
1099 return DECLINED;
1100 }
1101
1102 if (status != OK)
1103 return status;
1104
1105 log_access_verdict(APLOG_MARK, r, 1, FALSE, repos_path, dest_repos_path);
1106
1107 return OK;
1108 }
1109
1110 #if USE_FORCE_AUTHN
1111 static int
force_authn(request_rec * r)1112 force_authn(request_rec *r)
1113 {
1114 if (apr_table_get(r->notes, FORCE_AUTHN_NOTE))
1115 return OK;
1116
1117 return DECLINED;
1118 }
1119 #endif
1120
1121 /*
1122 * Module flesh
1123 */
1124
1125 /* Implements the #register_hooks method of Apache's #module vtable. */
1126 static void
register_hooks(apr_pool_t * p)1127 register_hooks(apr_pool_t *p)
1128 {
1129 static const char * const mod_ssl[] = { "mod_ssl.c", NULL };
1130
1131 ap_hook_access_checker(access_checker, NULL, NULL, APR_HOOK_LAST);
1132 /* Our check_user_id hook must be before any module which will return
1133 * HTTP_UNAUTHORIZED (mod_auth_basic, etc.), but after mod_ssl, to
1134 * give SSLOptions +FakeBasicAuth a chance to work. */
1135 ap_hook_check_user_id(check_user_id, mod_ssl, NULL, APR_HOOK_FIRST);
1136 ap_hook_auth_checker(auth_checker, NULL, NULL, APR_HOOK_FIRST);
1137 #if USE_FORCE_AUTHN
1138 ap_hook_force_authn(force_authn, NULL, NULL, APR_HOOK_FIRST);
1139 #endif
1140 ap_register_provider(p,
1141 AUTHZ_SVN__SUBREQ_BYPASS_PROV_GRP,
1142 AUTHZ_SVN__SUBREQ_BYPASS_PROV_NAME,
1143 AUTHZ_SVN__SUBREQ_BYPASS_PROV_VER,
1144 (void*)subreq_bypass);
1145 }
1146
1147 module AP_MODULE_DECLARE_DATA authz_svn_module =
1148 {
1149 STANDARD20_MODULE_STUFF,
1150 create_authz_svn_dir_config, /* dir config creater */
1151 NULL, /* dir merger --- default is to override */
1152 NULL, /* server config */
1153 NULL, /* merge server config */
1154 authz_svn_cmds, /* command apr_table_t */
1155 register_hooks /* register hooks */
1156 };
1157