Index: subversion/mod_authz_svn/mod_authz_svn.c =================================================================== --- subversion/mod_authz_svn/mod_authz_svn.c (revision 19349) +++ subversion/mod_authz_svn/mod_authz_svn.c (working copy) @@ -33,6 +33,8 @@ extern APR_OPTIONAL_FN_TYPE(ap_satisfies) *ap_satisfies; #endif +#include + #include "mod_dav_svn.h" #include "svn_path.h" #include "svn_config.h" @@ -95,6 +97,174 @@ { NULL } }; +typedef enum { + REPORT_UNKNOWN = 0, + REPORT_UPDATE, + REPORT_DATED_REV, + REPORT_GET_LOCKS, + REPORT_GET_LOCATIONS, + REPORT_LOG, + REPORT_REPLAY +} report_type_t; + +typedef struct { + /* The type of report we see. */ + report_type_t report_type; + + /* Our XML parser. */ + XML_Parser xmlp; + + /* Set to TRUE if we hit the end of the REPORT body. */ + svn_boolean_t done; + + svn_authz_t *access_conf; + const char *repos_name; + const char *repos_path; + + /* The current request. */ + request_rec *r; +} report_filter_ctx; + +static apr_status_t report_filter(ap_filter_t *f, + apr_bucket_brigade *bb, + ap_input_mode_t mode, + apr_read_type_e block, + apr_off_t readbytes) +{ + svn_boolean_t no_soup_for_you = FALSE; + report_filter_ctx *ctx = f->ctx; + apr_status_t rv; + apr_bucket *e; + + if (mode != AP_MODE_READBYTES) + return ap_get_brigade(f->next, bb, mode, block, readbytes); + + rv = ap_get_brigade(f->next, bb, mode, block, readbytes); + if (rv) + return rv; + + for (e = APR_BRIGADE_FIRST(bb); + e != APR_BRIGADE_SENTINEL(bb); + e = APR_BUCKET_NEXT(e)) { + svn_boolean_t last = APR_BUCKET_IS_EOS(e); + const char *str; + apr_size_t len; + + if (last) { + str = ""; + len = 0; + } + else { + rv = apr_bucket_read(e, &str, &len, APR_NONBLOCK_READ); + if (rv) + return rv; + } + + if (! XML_Parse(ctx->xmlp, str, len, last)) { + /* A parse error means we might as well reject... */ + no_soup_for_you = TRUE; + } + + if (last || ctx->done) { + switch (ctx->report_type) { + case REPORT_REPLAY: + /* XXX check if we should allow this or not */ + { + svn_boolean_t authz_access_granted; + svn_error_t *err; + + err = svn_repos_authz_check_access(ctx->access_conf, + ctx->repos_name, + ctx->repos_path, + ctx->r->user, + svn_authz_replay, + &authz_access_granted, + ctx->r->pool); + + if (! authz_access_granted) + no_soup_for_you = TRUE; + } + break; + + default: + break; + } + } + + /* Clean up our parser if we're done. */ + if (last || ctx->done) { + XML_ParserFree(ctx->xmlp); + + ctx->xmlp = NULL; + } + + /* If we found something that isn't allowed, set the correct status + * and return an error so it'll bail out before it gets anywhere it + * can do real damage. */ + if (no_soup_for_you) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, + "mod_authz_svn: client broke the rules, " + "returning error"); + + f->r->status = 403; + f->r->status_line = "406 Forbidden, No Soup For You!"; + + return APR_EGENERAL; + } + else if (ctx->done || last) { + ap_remove_input_filter(f); + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, + "mod_authz_svn: removing input filter"); + + return rv; + } + } + + return rv; +} + + +static void +start_element(void *baton, const char *name, const char **attrs) +{ + report_filter_ctx *ctx = baton; + + /* XXX namespace handling needs to be fixed, right now this breaks down + * on anything other than the default prefixes we use in Subversion. */ + + if (strcmp(name, "S:update-report") == 0) + ctx->report_type = REPORT_UPDATE; + else if (strcmp(name, "S:dated-rev-report") == 0) + ctx->report_type = REPORT_DATED_REV; + else if (strcmp(name, "S:get-locks-report") == 0) + ctx->report_type = REPORT_GET_LOCKS; + else if (strcmp(name, "S:get-locations") == 0) + ctx->report_type = REPORT_GET_LOCATIONS; + else if (strcmp(name, "S:log-report") == 0) + ctx->report_type = REPORT_LOG; + else if (strcmp(name, "S:replay-report") == 0) + ctx->report_type = REPORT_REPLAY; + else + ctx->report_type = REPORT_UNKNOWN; + + /* Currently all we care about is the type of report, so we can stop + * parsing now. */ + ctx->done = TRUE; +} + +static void +end_element(void *baton, const char *name) +{ + /* Currently we don't do anything in here... */ +} + +static void +cdata(void *baton, const char *data, int len) +{ + /* Currently we don't do anything in here... */ +} + /* Check if the current request R is allowed. Upon exit *REPOS_PATH_REF * will contain the path and repository name that an operation was requested * on in the form 'name:path'. *DEST_REPOS_PATH_REF will contain the @@ -288,8 +458,7 @@ * XXX: this out. */ if (repos_path - || (!repos_path && (authz_svn_type & svn_authz_write))) - { + || (!repos_path && (authz_svn_type & svn_authz_write))) { svn_err = svn_repos_authz_check_access(access_conf, repos_name, repos_path, r->user, authz_svn_type, @@ -313,8 +482,28 @@ } if (!authz_access_granted) return DECLINED; - } + } + if (0 && r->method_number == M_REPORT) { + report_filter_ctx *ctx = apr_pcalloc(r->pool, sizeof(*ctx)); + + ctx->r = r; + + ctx->access_conf = access_conf; + ctx->repos_path = repos_path; + ctx->repos_name = repos_name; + + ctx->xmlp = XML_ParserCreate(NULL); + + XML_SetUserData(ctx->xmlp, ctx); + XML_SetElementHandler(ctx->xmlp, start_element, end_element); + XML_SetCharacterDataHandler(ctx->xmlp, cdata); + + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Setting up REPORT filter"); + + ap_add_input_filter("SVN-AUTHZ-REPORT-FILTER", ctx, r, r->connection); + } + /* XXX: MKCOL, MOVE, DELETE * XXX: Require write access to the parent dir of repos_path. */ @@ -542,6 +731,8 @@ * give SSLOptions +FakeBasicAuth a chance to work. */ ap_hook_check_user_id(check_user_id, mod_ssl, NULL, APR_HOOK_FIRST); ap_hook_auth_checker(auth_checker, NULL, NULL, APR_HOOK_FIRST); + ap_register_input_filter("SVN-AUTHZ-REPORT-FILTER", report_filter, NULL, + AP_FTYPE_RESOURCE); } module AP_MODULE_DECLARE_DATA authz_svn_module = Index: subversion/include/svn_repos.h =================================================================== --- subversion/include/svn_repos.h (revision 19349) +++ subversion/include/svn_repos.h (working copy) @@ -90,7 +90,9 @@ svn_authz_write = 2, /** The other access credentials are recursive. */ - svn_authz_recursive = 4 + svn_authz_recursive = 4, + + svn_authz_replay = 8 } svn_repos_authz_access_t; Index: subversion/libsvn_repos/authz.c =================================================================== --- subversion/libsvn_repos/authz.c (revision 19349) +++ subversion/libsvn_repos/authz.c (working copy) @@ -87,7 +87,7 @@ svn_repos_authz_access_t required) { svn_repos_authz_access_t stripped_req = - required & (svn_authz_read | svn_authz_write); + required & (svn_authz_read | svn_authz_write | svn_authz_replay); if ((deny & required) == svn_authz_none) return TRUE; @@ -197,6 +197,13 @@ else b->deny |= svn_authz_write; + /* Note that svn_authz_replay is reversed, its presence indicates + * that you're NOT allowed to replay that path. */ + if (strchr(value, 'p')) + b->deny |= svn_authz_replay; + else + b->allow |= svn_authz_replay; + return TRUE; } Index: subversion/libsvn_ra_svn/client.c =================================================================== --- subversion/libsvn_ra_svn/client.c (revision 19349) +++ subversion/libsvn_ra_svn/client.c (working copy) @@ -1964,6 +1964,10 @@ _("Server doesn't support the replay " "command"))); + /* We need to read a response before we fall into the editor drive + * so that we can successfully error out if the auth request fails. */ + SVN_ERR(svn_ra_svn_read_cmd_response(sess->conn, pool, "")); + SVN_ERR(svn_ra_svn_drive_editor(sess->conn, pool, editor, edit_baton, NULL)); Index: subversion/svnserve/serve.c =================================================================== --- subversion/svnserve/serve.c (revision 19349) +++ subversion/svnserve/serve.c (working copy) @@ -160,7 +160,8 @@ static svn_repos_authz_func_t authz_check_access_cb_func(server_baton_t *baton) { if (baton->authzdb) - return authz_check_access_cb; + return authz_check_access_cb; + return NULL; } @@ -1963,8 +1964,13 @@ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rrb", &rev, &low_water_mark, &send_deltas)); - SVN_ERR(trivial_auth_request(conn, pool, b)); + SVN_ERR(must_have_access(conn, pool, b, svn_authz_replay, b->fs_path->data, + FALSE)); + /* If we got here, write a response back to get past the first + * read_cmd_response, so we can move on to the editor drive. */ + SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "")); + svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL); SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));