/* * serve.c : Functions for serving the Subversion protocol * * ==================================================================== * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * ==================================================================== */ #include /* for UINT_MAX */ #include #define APR_WANT_STRFUNC #include #include #include #include #include "svn_compat.h" #include "svn_private_config.h" /* For SVN_PATH_LOCAL_SEPARATOR */ #include "svn_hash.h" #include "svn_types.h" #include "svn_string.h" #include "svn_pools.h" #include "svn_error.h" #include "svn_ra.h" /* for SVN_RA_CAPABILITY_* */ #include "svn_ra_svn.h" #include "svn_repos.h" #include "svn_dirent_uri.h" #include "svn_path.h" #include "svn_time.h" #include "svn_config.h" #include "svn_props.h" #include "svn_mergeinfo.h" #include "svn_user.h" #include "private/svn_log.h" #include "private/svn_mergeinfo_private.h" #include "private/svn_ra_svn_private.h" #include "private/svn_fspath.h" #ifdef HAVE_UNISTD_H #include /* For getpid() */ #endif #include "server.h" #include "logger.h" typedef struct commit_callback_baton_t { apr_pool_t *pool; svn_revnum_t *new_rev; const char **date; const char **author; const char **post_commit_err; } commit_callback_baton_t; typedef struct report_driver_baton_t { server_baton_t *sb; const char *repos_url; /* Decoded repository URL. */ void *report_baton; svn_error_t *err; /* so update() can distinguish checkout from update in logging */ int entry_counter; svn_boolean_t only_empty_entries; /* for diff() logging */ svn_revnum_t *from_rev; } report_driver_baton_t; typedef struct log_baton_t { const char *fs_path; svn_ra_svn_conn_t *conn; int stack_depth; /* Set to TRUE when at least one changed path has been sent. */ svn_boolean_t started; } log_baton_t; typedef struct file_revs_baton_t { svn_ra_svn_conn_t *conn; apr_pool_t *pool; /* Pool provided in the handler call. */ } file_revs_baton_t; typedef struct fs_warning_baton_t { server_baton_t *server; svn_ra_svn_conn_t *conn; } fs_warning_baton_t; typedef struct authz_baton_t { server_baton_t *server; svn_ra_svn_conn_t *conn; } authz_baton_t; /* Log an error. */ static void log_error(const svn_error_t *err, server_baton_t *server) { logger__log_error(server->logger, err, server->repository, server->client_info); } /* Log a warning. */ static void log_warning(const svn_error_t *err, server_baton_t *server) { logger__log_warning(server->logger, err, server->repository, server->client_info); } /* svn_error_create() a new error, log_server_error() it, and return it. */ static svn_error_t * error_create_and_log(apr_status_t apr_err, svn_error_t *child, const char *message, server_baton_t *server) { svn_error_t *err = svn_error_create(apr_err, child, message); log_error(err, server); return err; } /* Log a failure ERR, transmit ERR back to the client (as part of a "failure" notification), consume ERR, and flush the connection. */ static svn_error_t * log_fail_and_flush(svn_error_t *err, server_baton_t *server, svn_ra_svn_conn_t *conn, apr_pool_t *pool) { svn_error_t *io_err; log_error(err, server); io_err = svn_ra_svn__write_cmd_failure(conn, pool, err); svn_error_clear(err); SVN_ERR(io_err); return svn_ra_svn__flush(conn, pool); } /* Log a client command. */ static svn_error_t *log_command(server_baton_t *b, svn_ra_svn_conn_t *conn, apr_pool_t *pool, const char *fmt, ...) { const char *remote_host, *timestr, *log, *line; va_list ap; apr_size_t nbytes; if (b->logger == NULL) return SVN_NO_ERROR; remote_host = svn_ra_svn_conn_remote_host(conn); timestr = svn_time_to_cstring(apr_time_now(), pool); va_start(ap, fmt); log = apr_pvsprintf(pool, fmt, ap); va_end(ap); line = apr_psprintf(pool, "%" APR_PID_T_FMT " %s %s %s %s %s" APR_EOL_STR, getpid(), timestr, (remote_host ? remote_host : "-"), (b->client_info->user ? b->client_info->user : "-"), b->repository->repos_name, log); nbytes = strlen(line); return logger__write(b->logger, line, nbytes); } /* Log an authz failure */ static svn_error_t * log_authz_denied(const char *path, svn_repos_authz_access_t required, server_baton_t *b, apr_pool_t *pool) { const char *timestr, *remote_host, *line; if (!b->logger) return SVN_NO_ERROR; if (!b->client_info || !b->client_info->user) return SVN_NO_ERROR; timestr = svn_time_to_cstring(apr_time_now(), pool); remote_host = b->client_info->remote_host; line = apr_psprintf(pool, "%" APR_PID_T_FMT " %s %s %s %s Authorization Failed %s%s %s" APR_EOL_STR, getpid(), timestr, (remote_host ? remote_host : "-"), b->client_info->user, b->repository->repos_name, (required & svn_authz_recursive ? "recursive " : ""), (required & svn_authz_write ? "write" : "read"), (path && path[0] ? path : "/")); return logger__write(b->logger, line, strlen(line)); } /* If CFG specifies a path to the password DB, read that DB through * CONFIG_POOL and store it in REPOSITORY->PWDB. */ static svn_error_t * load_pwdb_config(repository_t *repository, svn_config_t *cfg, svn_repos__config_pool_t *config_pool, apr_pool_t *pool) { const char *pwdb_path; svn_error_t *err; svn_config_get(cfg, &pwdb_path, SVN_CONFIG_SECTION_GENERAL, SVN_CONFIG_OPTION_PASSWORD_DB, NULL); repository->pwdb = NULL; if (pwdb_path) { pwdb_path = svn_dirent_internal_style(pwdb_path, pool); pwdb_path = svn_dirent_join(repository->base, pwdb_path, pool); err = svn_repos__config_pool_get(&repository->pwdb, config_pool, pwdb_path, TRUE, repository->repos, pool); if (err) { /* Because it may be possible to read the pwdb file with some access methods and not others, ignore errors reading the pwdb file and just don't present password authentication as an option. Also, some authentications (e.g. --tunnel) can proceed without it anyway. ### Not entirely sure why SVN_ERR_BAD_FILENAME is checked ### for here. That seems to have been introduced in r856914, ### and only in r870942 was the APR_EACCES check introduced. */ if (err->apr_err != SVN_ERR_BAD_FILENAME && ! APR_STATUS_IS_EACCES(err->apr_err)) { return svn_error_create(SVN_ERR_AUTHN_FAILED, err, NULL); } else /* Ignore SVN_ERR_BAD_FILENAME and APR_EACCES and proceed. */ svn_error_clear(err); } } return SVN_NO_ERROR; } /* Canonicalize *ACCESS_FILE based on the type of argument. Results are * placed in *ACCESS_FILE. REPOSITORY is used to convert relative paths to * absolute paths rooted at the server root. REPOS_ROOT is used to calculate * an absolute URL for repos-relative URLs. */ static svn_error_t * canonicalize_access_file(const char **access_file, repository_t *repository, const char *repos_root, apr_pool_t *pool) { if (svn_path_is_url(*access_file)) { const char *canonical_url; SVN_ERR(svn_uri_canonicalize_safe(&canonical_url, NULL, *access_file, pool, pool)); *access_file = canonical_url; } else if (svn_path_is_repos_relative_url(*access_file)) { const char *repos_root_url; const char *canonical_url; SVN_ERR(svn_uri_get_file_url_from_dirent(&repos_root_url, repos_root, pool)); SVN_ERR(svn_path_resolve_repos_relative_url(access_file, *access_file, repos_root_url, pool)); SVN_ERR(svn_uri_canonicalize_safe(&canonical_url, NULL, *access_file, pool, pool)); *access_file = canonical_url; } else { *access_file = svn_dirent_internal_style(*access_file, pool); *access_file = svn_dirent_join(repository->base, *access_file, pool); } return SVN_NO_ERROR; } /* Load the authz database for the listening server based on the entries in the SERVER struct. SERVER and CONN must not be NULL. The real errors will be logged with SERVER and CONN but return generic errors to the client. */ static svn_error_t * load_authz_config(repository_t *repository, const char *repos_root, svn_config_t *cfg, svn_repos_authz_warning_func_t warning_func, void *warning_baton, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { const char *authzdb_path; const char *groupsdb_path; svn_error_t *err; /* Read authz configuration. */ svn_config_get(cfg, &authzdb_path, SVN_CONFIG_SECTION_GENERAL, SVN_CONFIG_OPTION_AUTHZ_DB, NULL); svn_config_get(cfg, &groupsdb_path, SVN_CONFIG_SECTION_GENERAL, SVN_CONFIG_OPTION_GROUPS_DB, NULL); if (authzdb_path) { const char *case_force_val; /* Canonicalize and add the base onto the authzdb_path (if needed). */ err = canonicalize_access_file(&authzdb_path, repository, repos_root, scratch_pool); /* Same for the groupsdb_path if it is present. */ if (groupsdb_path && !err) err = canonicalize_access_file(&groupsdb_path, repository, repos_root, scratch_pool); if (!err) err = svn_repos_authz_read4(&repository->authzdb, authzdb_path, groupsdb_path, TRUE, repository->repos, warning_func, warning_baton, result_pool, scratch_pool); if (err) return svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, err, NULL); /* Are we going to be case-normalizing usernames when we consult * this authz file? */ svn_config_get(cfg, &case_force_val, SVN_CONFIG_SECTION_GENERAL, SVN_CONFIG_OPTION_FORCE_USERNAME_CASE, NULL); if (case_force_val) { if (strcmp(case_force_val, "upper") == 0) repository->username_case = CASE_FORCE_UPPER; else if (strcmp(case_force_val, "lower") == 0) repository->username_case = CASE_FORCE_LOWER; else repository->username_case = CASE_ASIS; } } else { repository->authzdb = NULL; repository->username_case = CASE_ASIS; } return SVN_NO_ERROR; } /* If ERROR is a AUTH* error as returned by load_pwdb_config or * load_authz_config, write it to SERVER's log file. * Return a sanitized version of ERROR. */ static svn_error_t * handle_config_error(svn_error_t *error, server_baton_t *server) { if ( error && ( error->apr_err == SVN_ERR_AUTHZ_INVALID_CONFIG || error->apr_err == SVN_ERR_AUTHN_FAILED)) { apr_status_t apr_err = error->apr_err; log_error(error, server); /* Now that we've logged the error, clear it and return a * nice, generic error to the user: * https://issues.apache.org/jira/browse/SVN-2271 */ svn_error_clear(error); return svn_error_create(apr_err, NULL, NULL); } return error; } /* Set *FS_PATH to the portion of URL that is the path within the repository, if URL is inside REPOS_URL (if URL is not inside REPOS_URL, then error, with the effect on *FS_PATH undefined). If the resultant fs path would be the empty string (i.e., URL and REPOS_URL are the same), then set *FS_PATH to "/". Assume that REPOS_URL and URL are already URI-decoded. */ static svn_error_t *get_fs_path(const char *repos_url, const char *url, const char **fs_path) { apr_size_t len; len = strlen(repos_url); if (strncmp(url, repos_url, len) != 0) return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, "'%s' is not the same repository as '%s'", url, repos_url); *fs_path = url + len; if (! **fs_path) *fs_path = "/"; return SVN_NO_ERROR; } /* --- AUTHENTICATION AND AUTHORIZATION FUNCTIONS --- */ /* Convert TEXT to upper case if TO_UPPERCASE is TRUE, else converts it to lower case. */ static void convert_case(char *text, svn_boolean_t to_uppercase) { char *c = text; while (*c) { *c = (char)(to_uppercase ? apr_toupper(*c) : apr_tolower(*c)); ++c; } } /* Set *ALLOWED to TRUE if PATH is accessible in the REQUIRED mode to the user described in BATON according to the authz rules in BATON. Use POOL for temporary allocations only. If no authz rules are present in BATON, grant access by default. */ static svn_error_t *authz_check_access(svn_boolean_t *allowed, const char *path, svn_repos_authz_access_t required, server_baton_t *b, apr_pool_t *pool) { repository_t *repository = b->repository; client_info_t *client_info = b->client_info; /* If authz cannot be performed, grant access. This is NOT the same as the default policy when authz is performed on a path with no rules. In the latter case, the default is to deny access, and is set by svn_repos_authz_check_access. */ if (!repository->authzdb) { *allowed = TRUE; return SVN_NO_ERROR; } /* If the authz request is for the empty path (ie. ""), replace it with the root path. This happens because of stripping done at various levels in svnserve that remove the leading / on an absolute path. Passing such a malformed path to the authz routines throws them into an infinite loop and makes them miss ACLs. */ if (path && *path != '/') path = svn_fspath__canonicalize(path, pool); /* If we have a username, and we've not yet used it + any username case normalization that might be requested to determine "the username we used for authz purposes", do so now. */ if (client_info->user && (! client_info->authz_user)) { char *authz_user = apr_pstrdup(b->pool, client_info->user); if (repository->username_case == CASE_FORCE_UPPER) convert_case(authz_user, TRUE); else if (repository->username_case == CASE_FORCE_LOWER) convert_case(authz_user, FALSE); client_info->authz_user = authz_user; } SVN_ERR(svn_repos_authz_check_access(repository->authzdb, repository->authz_repos_name, path, client_info->authz_user, required, allowed, pool)); if (!*allowed) SVN_ERR(log_authz_denied(path, required, b, pool)); return SVN_NO_ERROR; } /* Set *ALLOWED to TRUE if PATH is readable by the user described in * BATON. Use POOL for temporary allocations only. ROOT is not used. * Implements the svn_repos_authz_func_t interface. */ static svn_error_t *authz_check_access_cb(svn_boolean_t *allowed, svn_fs_root_t *root, const char *path, void *baton, apr_pool_t *pool) { authz_baton_t *sb = baton; return authz_check_access(allowed, path, svn_authz_read, sb->server, pool); } /* If authz is enabled in the specified BATON, return a read authorization function. Otherwise, return NULL. */ static svn_repos_authz_func_t authz_check_access_cb_func(server_baton_t *baton) { if (baton->repository->authzdb) return authz_check_access_cb; return NULL; } /* Set *ALLOWED to TRUE if the REQUIRED access to PATH is granted, * according to the state in BATON. Use POOL for temporary * allocations only. ROOT is not used. Implements the * svn_repos_authz_callback_t interface. */ static svn_error_t *authz_commit_cb(svn_repos_authz_access_t required, svn_boolean_t *allowed, svn_fs_root_t *root, const char *path, void *baton, apr_pool_t *pool) { authz_baton_t *sb = baton; return authz_check_access(allowed, path, required, sb->server, pool); } /* Return the access level specified for OPTION in CFG. If no such * setting exists, use DEF. If READ_ONLY is set, unconditionally disable * write access. */ static enum access_type get_access(svn_config_t *cfg, const char *option, const char *def, svn_boolean_t read_only) { enum access_type result; const char *val; svn_config_get(cfg, &val, SVN_CONFIG_SECTION_GENERAL, option, def); result = (strcmp(val, "write") == 0 ? WRITE_ACCESS : strcmp(val, "read") == 0 ? READ_ACCESS : NO_ACCESS); return result == WRITE_ACCESS && read_only ? READ_ACCESS : result; } /* Set the *_ACCESS members in REPOSITORY according to the settings in * CFG. If READ_ONLY is set, unconditionally disable write access. */ static void set_access(repository_t *repository, svn_config_t *cfg, svn_boolean_t read_only) { repository->auth_access = get_access(cfg, SVN_CONFIG_OPTION_AUTH_ACCESS, "write", read_only); repository->anon_access = get_access(cfg, SVN_CONFIG_OPTION_ANON_ACCESS, "read", read_only); } /* Return the access level for the user in B. */ static enum access_type current_access(server_baton_t *b) { return b->client_info->user ? b->repository->auth_access : b->repository->anon_access; } /* Send authentication mechs for ACCESS_TYPE to the client. If NEEDS_USERNAME is true, don't send anonymous mech even if that would give the desired access. */ static svn_error_t *send_mechs(svn_ra_svn_conn_t *conn, apr_pool_t *pool, server_baton_t *b, enum access_type required, svn_boolean_t needs_username) { if (!needs_username && b->repository->anon_access >= required) SVN_ERR(svn_ra_svn__write_word(conn, pool, "ANONYMOUS")); if (b->client_info->tunnel_user && b->repository->auth_access >= required) SVN_ERR(svn_ra_svn__write_word(conn, pool, "EXTERNAL")); if (b->repository->pwdb && b->repository->auth_access >= required) SVN_ERR(svn_ra_svn__write_word(conn, pool, "CRAM-MD5")); return SVN_NO_ERROR; } /* Context for cleanup handler. */ struct cleanup_fs_access_baton { svn_fs_t *fs; apr_pool_t *pool; }; /* Pool cleanup handler. Make sure fs's access_t points to NULL when the command pool is destroyed. */ static apr_status_t cleanup_fs_access(void *data) { svn_error_t *serr; struct cleanup_fs_access_baton *baton = data; serr = svn_fs_set_access(baton->fs, NULL); if (serr) { apr_status_t apr_err = serr->apr_err; svn_error_clear(serr); return apr_err; } return APR_SUCCESS; } /* Create an svn_fs_access_t in POOL for USER and associate it with B's filesystem. Also, register a cleanup handler with POOL which de-associates the svn_fs_access_t from B's filesystem. */ static svn_error_t * create_fs_access(server_baton_t *b, apr_pool_t *pool) { svn_fs_access_t *fs_access; struct cleanup_fs_access_baton *cleanup_baton; if (!b->client_info->user) return SVN_NO_ERROR; SVN_ERR(svn_fs_create_access(&fs_access, b->client_info->user, pool)); SVN_ERR(svn_fs_set_access(b->repository->fs, fs_access)); cleanup_baton = apr_pcalloc(pool, sizeof(*cleanup_baton)); cleanup_baton->pool = pool; cleanup_baton->fs = b->repository->fs; apr_pool_cleanup_register(pool, cleanup_baton, cleanup_fs_access, apr_pool_cleanup_null); return SVN_NO_ERROR; } /* Authenticate, once the client has chosen a mechanism and possibly * sent an initial mechanism token. On success, set *success to true * and b->user to the authenticated username (or NULL for anonymous). * On authentication failure, report failure to the client and set * *success to FALSE. On communications failure, return an error. * If NEEDS_USERNAME is TRUE, don't allow anonymous authentication. */ static svn_error_t *auth(svn_boolean_t *success, svn_ra_svn_conn_t *conn, const char *mech, const char *mecharg, server_baton_t *b, enum access_type required, svn_boolean_t needs_username, apr_pool_t *scratch_pool) { const char *user; *success = FALSE; if (b->repository->auth_access >= required && b->client_info->tunnel_user && strcmp(mech, "EXTERNAL") == 0) { if (*mecharg && strcmp(mecharg, b->client_info->tunnel_user) != 0) return svn_ra_svn__write_tuple(conn, scratch_pool, "w(c)", "failure", "Requested username does not match"); b->client_info->user = b->client_info->tunnel_user; SVN_ERR(svn_ra_svn__write_tuple(conn, scratch_pool, "w()", "success")); *success = TRUE; return SVN_NO_ERROR; } if (b->repository->anon_access >= required && strcmp(mech, "ANONYMOUS") == 0 && ! needs_username) { SVN_ERR(svn_ra_svn__write_tuple(conn, scratch_pool, "w()", "success")); *success = TRUE; return SVN_NO_ERROR; } if (b->repository->auth_access >= required && b->repository->pwdb && strcmp(mech, "CRAM-MD5") == 0) { SVN_ERR(svn_ra_svn_cram_server(conn, scratch_pool, b->repository->pwdb, &user, success)); b->client_info->user = apr_pstrdup(b->pool, user); return SVN_NO_ERROR; } return svn_ra_svn__write_tuple(conn, scratch_pool, "w(c)", "failure", "Must authenticate with listed mechanism"); } /* Perform an authentication request using the built-in SASL implementation. */ static svn_error_t * internal_auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool, server_baton_t *b, enum access_type required, svn_boolean_t needs_username) { svn_boolean_t success; const char *mech, *mecharg; apr_pool_t *iterpool; SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success")); SVN_ERR(send_mechs(conn, pool, b, required, needs_username)); SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)c)", b->repository->realm)); iterpool = svn_pool_create(pool); do { svn_pool_clear(iterpool); SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "w(?c)", &mech, &mecharg)); if (!*mech) break; SVN_ERR(auth(&success, conn, mech, mecharg, b, required, needs_username, iterpool)); } while (!success); svn_pool_destroy(iterpool); return SVN_NO_ERROR; } /* Perform an authentication request in order to get an access level of * REQUIRED or higher. Since the client may escape the authentication * exchange, the caller should check current_access(b) to see if * authentication succeeded. */ static svn_error_t *auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool, server_baton_t *b, enum access_type required, svn_boolean_t needs_username) { #ifdef SVN_HAVE_SASL if (b->repository->use_sasl) return cyrus_auth_request(conn, pool, b, required, needs_username); #endif return internal_auth_request(conn, pool, b, required, needs_username); } /* Send a trivial auth notification on CONN which lists no mechanisms, * indicating that authentication is unnecessary. Usually called in * response to invocation of a svnserve command. */ static svn_error_t *trivial_auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool, server_baton_t *b) { return svn_ra_svn__write_cmd_response(conn, pool, "()c", ""); } /* Ensure that the client has the REQUIRED access by checking the * access directives (both blanket and per-directory) in BATON. If * PATH is NULL, then only the blanket access configuration will * impact the result. * * If NEEDS_USERNAME is TRUE, then a lookup is only successful if the * user described in BATON is authenticated and, well, has a username * assigned to him. * * Use POOL for temporary allocations only. */ static svn_boolean_t lookup_access(apr_pool_t *pool, server_baton_t *baton, svn_repos_authz_access_t required, const char *path, svn_boolean_t needs_username) { enum access_type req = (required & svn_authz_write) ? WRITE_ACCESS : READ_ACCESS; svn_boolean_t authorized; svn_error_t *err; /* Get authz's opinion on the access. */ err = authz_check_access(&authorized, path, required, baton, pool); /* If an error made lookup fail, deny access. */ if (err) { log_error(err, baton); svn_error_clear(err); return FALSE; } /* If the required access is blanket-granted AND granted by authz AND we already have a username if one is required, then the lookup has succeeded. */ if (current_access(baton) >= req && authorized && (! needs_username || baton->client_info->user)) return TRUE; return FALSE; } /* Check that the client has the REQUIRED access by consulting the * authentication and authorization states stored in BATON. If the * client does not have the required access credentials, attempt to * authenticate the client to get that access, using CONN for * communication. * * This function is supposed to be called to handle the authentication * half of a standard svn protocol reply. If an error is returned, it * probably means that the server can terminate the client connection * with an apologetic error, as it implies an authentication failure. * * PATH and NEEDS_USERNAME are passed along to lookup_access, their * behaviour is documented there. */ static svn_error_t *must_have_access(svn_ra_svn_conn_t *conn, apr_pool_t *pool, server_baton_t *b, svn_repos_authz_access_t required, const char *path, svn_boolean_t needs_username) { enum access_type req = (required & svn_authz_write) ? WRITE_ACCESS : READ_ACCESS; /* See whether the user already has the required access. If so, nothing needs to be done. Create the FS access and send a trivial auth request. */ if (lookup_access(pool, b, required, path, needs_username)) { SVN_ERR(create_fs_access(b, pool)); return trivial_auth_request(conn, pool, b); } /* If the required blanket access can be obtained by authenticating, try that. Unfortunately, we can't tell until after authentication whether authz will work or not. We force requiring a username because we need one to be able to check authz configuration again with a different user credentials than the first time round. */ if (b->client_info->user == NULL && b->repository->auth_access >= req && (b->client_info->tunnel_user || b->repository->pwdb || b->repository->use_sasl)) SVN_ERR(auth_request(conn, pool, b, req, TRUE)); /* Now that an authentication has been done get the new take of authz on the request. */ if (! lookup_access(pool, b, required, path, needs_username)) return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR, error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, NULL, b), NULL); /* Else, access is granted, and there is much rejoicing. */ SVN_ERR(create_fs_access(b, pool)); return SVN_NO_ERROR; } /* --- REPORTER COMMAND SET --- */ /* To allow for pipelining, reporter commands have no reponses. If we * get an error, we ignore all subsequent reporter commands and return * the error finish_report, to be handled by the calling command. */ static svn_error_t *set_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { report_driver_baton_t *b = baton; const char *path, *lock_token, *depth_word, *canonical_relpath; svn_revnum_t rev; /* Default to infinity, for old clients that don't send depth. */ svn_depth_t depth = svn_depth_infinity; svn_boolean_t start_empty; SVN_ERR(svn_ra_svn__parse_tuple(params, "crb?(?c)?w", &path, &rev, &start_empty, &lock_token, &depth_word)); if (depth_word) depth = svn_depth_from_word(depth_word); SVN_ERR(svn_relpath_canonicalize_safe(&canonical_relpath, NULL, path, pool, pool)); path = canonical_relpath; if (b->from_rev && strcmp(path, "") == 0) *b->from_rev = rev; if (!b->err) b->err = svn_repos_set_path3(b->report_baton, path, rev, depth, start_empty, lock_token, pool); b->entry_counter++; if (!start_empty) b->only_empty_entries = FALSE; return SVN_NO_ERROR; } static svn_error_t *delete_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { report_driver_baton_t *b = baton; const char *path, *canonical_relpath; SVN_ERR(svn_ra_svn__parse_tuple(params, "c", &path)); SVN_ERR(svn_relpath_canonicalize_safe(&canonical_relpath, NULL, path, pool, pool)); path = canonical_relpath; if (!b->err) b->err = svn_repos_delete_path(b->report_baton, path, pool); return SVN_NO_ERROR; } static svn_error_t *link_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { report_driver_baton_t *b = baton; const char *path, *url, *lock_token, *fs_path, *depth_word, *canonical_url; const char *canonical_path; svn_revnum_t rev; svn_boolean_t start_empty; /* Default to infinity, for old clients that don't send depth. */ svn_depth_t depth = svn_depth_infinity; SVN_ERR(svn_ra_svn__parse_tuple(params, "ccrb?(?c)?w", &path, &url, &rev, &start_empty, &lock_token, &depth_word)); /* ### WHAT?! The link path is an absolute URL?! Didn't see that coming... -- cmpilato */ SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, pool, pool)); path = canonical_path; SVN_ERR(svn_uri_canonicalize_safe(&canonical_url, NULL, url, pool, pool)); url = canonical_url; if (depth_word) depth = svn_depth_from_word(depth_word); if (!b->err) b->err = get_fs_path(svn_path_uri_decode(b->repos_url, pool), svn_path_uri_decode(url, pool), &fs_path); if (!b->err) b->err = svn_repos_link_path3(b->report_baton, path, fs_path, rev, depth, start_empty, lock_token, pool); b->entry_counter++; return SVN_NO_ERROR; } static svn_error_t *finish_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { report_driver_baton_t *b = baton; /* No arguments to parse. */ SVN_ERR(trivial_auth_request(conn, pool, b->sb)); if (!b->err) b->err = svn_repos_finish_report(b->report_baton, pool); return SVN_NO_ERROR; } static svn_error_t * abort_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { report_driver_baton_t *b = baton; /* No arguments to parse. */ svn_error_clear(svn_repos_abort_report(b->report_baton, pool)); return SVN_NO_ERROR; } static const svn_ra_svn__cmd_entry_t report_commands[] = { { "set-path", set_path }, { "delete-path", delete_path }, { "link-path", link_path }, { "finish-report", finish_report, NULL, TRUE }, { "abort-report", abort_report, NULL, TRUE }, { NULL } }; /* Accept a report from the client, drive the network editor with the * result, and then write an empty command response. If there is a * non-protocol failure, accept_report will abort the edit and return * a command error to be reported by handle_commands(). * * If only_empty_entry is not NULL and the report contains only one * item, and that item is empty, set *only_empty_entry to TRUE, else * set it to FALSE. * * If from_rev is not NULL, set *from_rev to the revision number from * the set-path on ""; if somehow set-path "" never happens, set * *from_rev to SVN_INVALID_REVNUM. */ static svn_error_t *accept_report(svn_boolean_t *only_empty_entry, svn_revnum_t *from_rev, svn_ra_svn_conn_t *conn, apr_pool_t *pool, server_baton_t *b, svn_revnum_t rev, const char *target, const char *tgt_path, svn_boolean_t text_deltas, svn_depth_t depth, svn_boolean_t send_copyfrom_args, svn_boolean_t ignore_ancestry) { const svn_delta_editor_t *editor; void *edit_baton, *report_baton; report_driver_baton_t rb; svn_error_t *err; authz_baton_t ab; ab.server = b; ab.conn = conn; /* Make an svn_repos report baton. Tell it to drive the network editor * when the report is complete. */ svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL); SVN_CMD_ERR(svn_repos_begin_report3(&report_baton, rev, b->repository->repos, b->repository->fs_path->data, target, tgt_path, text_deltas, depth, ignore_ancestry, send_copyfrom_args, editor, edit_baton, authz_check_access_cb_func(b), &ab, svn_ra_svn_zero_copy_limit(conn), pool)); rb.sb = b; rb.repos_url = svn_path_uri_decode(b->repository->repos_url, pool); rb.report_baton = report_baton; rb.err = NULL; rb.entry_counter = 0; rb.only_empty_entries = TRUE; rb.from_rev = from_rev; if (from_rev) *from_rev = SVN_INVALID_REVNUM; err = svn_ra_svn__handle_commands2(conn, pool, report_commands, &rb, TRUE); if (err) { /* Network or protocol error while handling commands. */ svn_error_clear(rb.err); return err; } else if (rb.err) { /* Some failure during the reporting or editing operations. */ SVN_CMD_ERR(rb.err); } SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); if (only_empty_entry) *only_empty_entry = rb.entry_counter == 1 && rb.only_empty_entries; return SVN_NO_ERROR; } /* --- MAIN COMMAND SET --- */ /* Write out a list of property diffs. PROPDIFFS is an array of svn_prop_t * values. */ static svn_error_t *write_prop_diffs(svn_ra_svn_conn_t *conn, apr_pool_t *pool, const apr_array_header_t *propdiffs) { int i; for (i = 0; i < propdiffs->nelts; ++i) { const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t); SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "c(?s)", prop->name, prop->value)); } return SVN_NO_ERROR; } /* Write out a lock to the client. */ static svn_error_t *write_lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool, const svn_lock_t *lock) { const char *cdate, *edate; cdate = svn_time_to_cstring(lock->creation_date, pool); edate = lock->expiration_date ? svn_time_to_cstring(lock->expiration_date, pool) : NULL; SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "ccc(?c)c(?c)", lock->path, lock->token, lock->owner, lock->comment, cdate, edate)); return SVN_NO_ERROR; } /* ### This really belongs in libsvn_repos. */ /* Get the explicit properties and/or inherited properties for a PATH in ROOT, with hardcoded committed-info values. */ static svn_error_t * get_props(apr_hash_t **props, apr_array_header_t **iprops, authz_baton_t *b, svn_fs_root_t *root, const char *path, apr_pool_t *pool) { /* Get the explicit properties. */ if (props) { svn_string_t *str; svn_revnum_t crev; const char *cdate, *cauthor, *uuid; SVN_ERR(svn_fs_node_proplist(props, root, path, pool)); /* Hardcode the values for the committed revision, date, and author. */ SVN_ERR(svn_repos_get_committed_info(&crev, &cdate, &cauthor, root, path, pool)); str = svn_string_createf(pool, "%ld", crev); svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_REV, str); str = (cdate) ? svn_string_create(cdate, pool) : NULL; svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_DATE, str); str = (cauthor) ? svn_string_create(cauthor, pool) : NULL; svn_hash_sets(*props, SVN_PROP_ENTRY_LAST_AUTHOR, str); /* Hardcode the values for the UUID. */ SVN_ERR(svn_fs_get_uuid(svn_fs_root_fs(root), &uuid, pool)); str = (uuid) ? svn_string_create(uuid, pool) : NULL; svn_hash_sets(*props, SVN_PROP_ENTRY_UUID, str); } /* Get any inherited properties the user is authorized to. */ if (iprops) { SVN_ERR(svn_repos_fs_get_inherited_props( iprops, root, path, NULL, authz_check_access_cb_func(b->server), b, pool, pool)); } return SVN_NO_ERROR; } /* Set BATON->FS_PATH for the repository URL found in PARAMS. */ static svn_error_t * reparent(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { server_baton_t *b = baton; const char *url, *canonical_url; const char *fs_path; SVN_ERR(svn_ra_svn__parse_tuple(params, "c", &url)); SVN_ERR(svn_uri_canonicalize_safe(&canonical_url, NULL, url, pool, pool)); url = canonical_url; SVN_ERR(trivial_auth_request(conn, pool, b)); SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repository->repos_url, pool), svn_path_uri_decode(url, pool), &fs_path)); SVN_ERR(log_command(b, conn, pool, "%s", svn_log__reparent(fs_path, pool))); svn_stringbuf_set(b->repository->fs_path, fs_path); SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); return SVN_NO_ERROR; } static svn_error_t * get_latest_rev(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { server_baton_t *b = baton; svn_revnum_t rev; SVN_ERR(log_command(b, conn, pool, "get-latest-rev")); SVN_ERR(trivial_auth_request(conn, pool, b)); SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool)); SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", rev)); return SVN_NO_ERROR; } static svn_error_t * get_dated_rev(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { server_baton_t *b = baton; svn_revnum_t rev; apr_time_t tm; const char *timestr; SVN_ERR(svn_ra_svn__parse_tuple(params, "c", ×tr)); SVN_ERR(log_command(b, conn, pool, "get-dated-rev %s", timestr)); SVN_ERR(trivial_auth_request(conn, pool, b)); SVN_CMD_ERR(svn_time_from_cstring(&tm, timestr, pool)); SVN_CMD_ERR(svn_repos_dated_revision(&rev, b->repository->repos, tm, pool)); SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", rev)); return SVN_NO_ERROR; } /* Common logic for change_rev_prop() and change_rev_prop2(). */ static svn_error_t *do_change_rev_prop(svn_ra_svn_conn_t *conn, server_baton_t *b, svn_revnum_t rev, const char *name, const svn_string_t *const *old_value_p, const svn_string_t *value, apr_pool_t *pool) { authz_baton_t ab; ab.server = b; ab.conn = conn; SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, FALSE)); SVN_ERR(log_command(b, conn, pool, "%s", svn_log__change_rev_prop(rev, name, pool))); SVN_CMD_ERR(svn_repos_fs_change_rev_prop4(b->repository->repos, rev, b->client_info->user, name, old_value_p, value, TRUE, TRUE, authz_check_access_cb_func(b), &ab, pool)); SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); return SVN_NO_ERROR; } static svn_error_t * change_rev_prop2(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { server_baton_t *b = baton; svn_revnum_t rev; const char *name; svn_string_t *value; const svn_string_t *const *old_value_p; svn_string_t *old_value; svn_boolean_t dont_care; SVN_ERR(svn_ra_svn__parse_tuple(params, "rc(?s)(b?s)", &rev, &name, &value, &dont_care, &old_value)); /* Argument parsing. */ if (dont_care) old_value_p = NULL; else old_value_p = (const svn_string_t *const *)&old_value; /* Input validation. */ if (dont_care && old_value) { svn_error_t *err; err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, "'previous-value' and 'dont-care' cannot both be " "set in 'change-rev-prop2' request"); return log_fail_and_flush(err, b, conn, pool); } /* Do it. */ SVN_ERR(do_change_rev_prop(conn, b, rev, name, old_value_p, value, pool)); return SVN_NO_ERROR; } static svn_error_t * change_rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { server_baton_t *b = baton; svn_revnum_t rev; const char *name; svn_string_t *value; /* Because the revprop value was at one time mandatory, the usual optional element pattern "(?s)" isn't used. */ SVN_ERR(svn_ra_svn__parse_tuple(params, "rc?s", &rev, &name, &value)); SVN_ERR(do_change_rev_prop(conn, b, rev, name, NULL, value, pool)); return SVN_NO_ERROR; } static svn_error_t * rev_proplist(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { server_baton_t *b = baton; svn_revnum_t rev; apr_hash_t *props; authz_baton_t ab; ab.server = b; ab.conn = conn; SVN_ERR(svn_ra_svn__parse_tuple(params, "r", &rev)); SVN_ERR(log_command(b, conn, pool, "%s", svn_log__rev_proplist(rev, pool))); SVN_ERR(trivial_auth_request(conn, pool, b)); SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repository->repos, rev, authz_check_access_cb_func(b), &ab, pool)); SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success")); SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props)); SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); return SVN_NO_ERROR; } static svn_error_t * rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { server_baton_t *b = baton; svn_revnum_t rev; const char *name; svn_string_t *value; authz_baton_t ab; ab.server = b; ab.conn = conn; SVN_ERR(svn_ra_svn__parse_tuple(params, "rc", &rev, &name)); SVN_ERR(log_command(b, conn, pool, "%s", svn_log__rev_prop(rev, name, pool))); SVN_ERR(trivial_auth_request(conn, pool, b)); SVN_CMD_ERR(svn_repos_fs_revision_prop(&value, b->repository->repos, rev, name, authz_check_access_cb_func(b), &ab, pool)); SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "(?s)", value)); return SVN_NO_ERROR; } static svn_error_t *commit_done(const svn_commit_info_t *commit_info, void *baton, apr_pool_t *pool) { commit_callback_baton_t *ccb = baton; *ccb->new_rev = commit_info->revision; *ccb->date = commit_info->date ? apr_pstrdup(ccb->pool, commit_info->date): NULL; *ccb->author = commit_info->author ? apr_pstrdup(ccb->pool, commit_info->author) : NULL; *ccb->post_commit_err = commit_info->post_commit_err ? apr_pstrdup(ccb->pool, commit_info->post_commit_err) : NULL; return SVN_NO_ERROR; } /* Add the LOCK_TOKENS (if any) to the filesystem access context, * checking path authorizations using the state in SB as we go. * LOCK_TOKENS is an array of svn_ra_svn__item_t structs. Return a * client error if LOCK_TOKENS is not a list of lists. If a lock * violates the authz configuration, return SVN_ERR_RA_NOT_AUTHORIZED * to the client. Use POOL for temporary allocations only. */ static svn_error_t * add_lock_tokens(const svn_ra_svn__list_t *lock_tokens, server_baton_t *sb, apr_pool_t *pool) { const char *canonical_path; int i; svn_fs_access_t *fs_access; SVN_ERR(svn_fs_get_access(&fs_access, sb->repository->fs)); /* If there is no access context, nowhere to add the tokens. */ if (! fs_access) return SVN_NO_ERROR; for (i = 0; i < lock_tokens->nelts; ++i) { const char *path, *token, *full_path; svn_ra_svn__item_t *path_item, *token_item; svn_ra_svn__item_t *item = &SVN_RA_SVN__LIST_ITEM(lock_tokens, i); if (item->kind != SVN_RA_SVN_LIST) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, "Lock tokens aren't a list of lists"); path_item = &SVN_RA_SVN__LIST_ITEM(&item->u.list, 0); if (path_item->kind != SVN_RA_SVN_STRING) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, "Lock path isn't a string"); token_item = &SVN_RA_SVN__LIST_ITEM(&item->u.list, 1); if (token_item->kind != SVN_RA_SVN_STRING) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, "Lock token isn't a string"); path = path_item->u.string.data; SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, pool, pool)); full_path = svn_fspath__join(sb->repository->fs_path->data, canonical_path, pool); if (! lookup_access(pool, sb, svn_authz_write, full_path, TRUE)) return error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, NULL, sb); token = token_item->u.string.data; SVN_ERR(svn_fs_access_add_lock_token2(fs_access, path, token)); } return SVN_NO_ERROR; } /* Implements svn_fs_lock_callback_t. */ static svn_error_t * lock_cb(void *baton, const char *path, const svn_lock_t *lock, svn_error_t *fs_err, apr_pool_t *pool) { server_baton_t *sb = baton; log_error(fs_err, sb); return SVN_NO_ERROR; } /* Unlock the paths with lock tokens in LOCK_TOKENS, ignoring any errors. LOCK_TOKENS contains svn_ra_svn__item_t elements, assumed to be lists. */ static svn_error_t * unlock_paths(const svn_ra_svn__list_t *lock_tokens, server_baton_t *sb, apr_pool_t *pool) { int i; apr_pool_t *subpool = svn_pool_create(pool); apr_hash_t *targets = apr_hash_make(subpool); const char *canonical_path; svn_error_t *err; for (i = 0; i < lock_tokens->nelts; ++i) { svn_ra_svn__item_t *item, *path_item, *token_item; const char *path, *token, *full_path; item = &SVN_RA_SVN__LIST_ITEM(lock_tokens, i); path_item = &SVN_RA_SVN__LIST_ITEM(&item->u.list, 0); token_item = &SVN_RA_SVN__LIST_ITEM(&item->u.list, 1); path = path_item->u.string.data; SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, subpool, subpool)); full_path = svn_fspath__join(sb->repository->fs_path->data, canonical_path, subpool); token = token_item->u.string.data; svn_hash_sets(targets, full_path, token); } /* The lock may have become defunct after the commit, so ignore such errors. */ err = svn_repos_fs_unlock_many(sb->repository->repos, targets, FALSE, lock_cb, sb, subpool, subpool); log_error(err, sb); svn_error_clear(err); svn_pool_destroy(subpool); return SVN_NO_ERROR; } static svn_error_t * commit(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { server_baton_t *b = baton; const char *log_msg, *date = NULL, *author = NULL, *post_commit_err = NULL; svn_ra_svn__list_t *lock_tokens; svn_boolean_t keep_locks; svn_ra_svn__list_t *revprop_list; apr_hash_t *revprop_table; const svn_delta_editor_t *editor; void *edit_baton; svn_boolean_t aborted; commit_callback_baton_t ccb; svn_revnum_t new_rev; authz_baton_t ab; ab.server = b; ab.conn = conn; if (params->nelts == 1) { /* Clients before 1.2 don't send lock-tokens, keep-locks, and rev-props fields. */ SVN_ERR(svn_ra_svn__parse_tuple(params, "c", &log_msg)); lock_tokens = NULL; keep_locks = TRUE; revprop_list = NULL; } else { /* Clients before 1.5 don't send the rev-props field. */ SVN_ERR(svn_ra_svn__parse_tuple(params, "clb?l", &log_msg, &lock_tokens, &keep_locks, &revprop_list)); } /* The handling for locks is a little problematic, because the protocol won't let us send several auth requests once one has succeeded. So we request write access and a username before adding tokens (if we have any), and subsequently fail if a lock violates authz. */ SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, (lock_tokens && lock_tokens->nelts))); /* Authorize the lock tokens and give them to the FS if we got any. */ if (lock_tokens && lock_tokens->nelts) SVN_CMD_ERR(add_lock_tokens(lock_tokens, b, pool)); /* Ignore LOG_MSG, per the protocol. See ra_svn_commit(). */ if (revprop_list) SVN_ERR(svn_ra_svn__parse_proplist(revprop_list, pool, &revprop_table)); else { revprop_table = apr_hash_make(pool); svn_hash_sets(revprop_table, SVN_PROP_REVISION_LOG, svn_string_create(log_msg, pool)); } /* Get author from the baton, making sure clients can't circumvent the authentication via the revision props. */ svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR, b->client_info->user ? svn_string_create(b->client_info->user, pool) : NULL); ccb.pool = pool; ccb.new_rev = &new_rev; ccb.date = &date; ccb.author = &author; ccb.post_commit_err = &post_commit_err; /* ### Note that svn_repos_get_commit_editor5 actually wants a decoded URL. */ SVN_CMD_ERR(svn_repos_get_commit_editor5 (&editor, &edit_baton, b->repository->repos, NULL, svn_path_uri_decode(b->repository->repos_url, pool), b->repository->fs_path->data, revprop_table, commit_done, &ccb, authz_commit_cb, &ab, pool)); SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); SVN_ERR(svn_ra_svn_drive_editor2(conn, pool, editor, edit_baton, &aborted, FALSE)); if (!aborted) { SVN_ERR(log_command(b, conn, pool, "%s", svn_log__commit(new_rev, pool))); SVN_ERR(trivial_auth_request(conn, pool, b)); /* In tunnel mode, deltify before answering the client, because answering may cause the client to terminate the connection and thus kill the server. But otherwise, deltify after answering the client, to avoid user-visible delay. */ if (b->client_info->tunnel) SVN_ERR(svn_fs_deltify_revision(b->repository->fs, new_rev, pool)); /* Unlock the paths. */ if (! keep_locks && lock_tokens && lock_tokens->nelts) SVN_ERR(unlock_paths(lock_tokens, b, pool)); SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "r(?c)(?c)(?c)", new_rev, date, author, post_commit_err)); if (! b->client_info->tunnel) SVN_ERR(svn_fs_deltify_revision(b->repository->fs, new_rev, pool)); } return SVN_NO_ERROR; } static svn_error_t * get_file(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { server_baton_t *b = baton; const char *path, *full_path, *hex_digest, *canonical_path; svn_revnum_t rev; svn_fs_root_t *root; svn_stream_t *contents; apr_hash_t *props = NULL; apr_array_header_t *inherited_props; svn_string_t write_str; char buf[4096]; apr_size_t len; svn_boolean_t want_props, want_contents; apr_uint64_t wants_inherited_props; svn_checksum_t *checksum; svn_error_t *err, *write_err; int i; authz_baton_t ab; ab.server = b; ab.conn = conn; /* Parse arguments. */ SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)bb?B", &path, &rev, &want_props, &want_contents, &wants_inherited_props)); if (wants_inherited_props == SVN_RA_SVN_UNSPECIFIED_NUMBER) wants_inherited_props = FALSE; SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, pool, pool)); full_path = svn_fspath__join(b->repository->fs_path->data, canonical_path, pool); /* Check authorizations */ SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, full_path, FALSE)); if (!SVN_IS_VALID_REVNUM(rev)) SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool)); SVN_ERR(log_command(b, conn, pool, "%s", svn_log__get_file(full_path, rev, want_contents, want_props, pool))); /* Fetch the properties and a stream for the contents. */ SVN_CMD_ERR(svn_fs_revision_root(&root, b->repository->fs, rev, pool)); SVN_CMD_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, root, full_path, TRUE, pool)); hex_digest = svn_checksum_to_cstring_display(checksum, pool); /* Fetch the file's explicit and/or inherited properties if requested. Although the wants-iprops boolean was added to the protocol in 1.8 a standard 1.8 client never requests iprops. */ if (want_props || wants_inherited_props) SVN_CMD_ERR(get_props(want_props ? &props : NULL, wants_inherited_props ? &inherited_props : NULL, &ab, root, full_path, pool)); if (want_contents) SVN_CMD_ERR(svn_fs_file_contents(&contents, root, full_path, pool)); /* Send successful command response with revision and props. */ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)r(!", "success", hex_digest, rev)); SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props)); if (wants_inherited_props) { apr_pool_t *iterpool = svn_pool_create(pool); SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?!")); for (i = 0; i < inherited_props->nelts; i++) { svn_prop_inherited_item_t *iprop = APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *); svn_pool_clear(iterpool); SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!", iprop->path_or_url)); SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash)); SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!", iprop->path_or_url)); } svn_pool_destroy(iterpool); } SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); /* Now send the file's contents. */ if (want_contents) { err = SVN_NO_ERROR; while (1) { len = sizeof(buf); err = svn_stream_read_full(contents, buf, &len); if (err) break; if (len > 0) { write_str.data = buf; write_str.len = len; SVN_ERR(svn_ra_svn__write_string(conn, pool, &write_str)); } if (len < sizeof(buf)) { err = svn_stream_close(contents); break; } } write_err = svn_ra_svn__write_cstring(conn, pool, ""); if (write_err) { svn_error_clear(err); return write_err; } SVN_CMD_ERR(err); SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); } return SVN_NO_ERROR; } /* Translate all the words in DIRENT_FIELDS_LIST into the flags in * DIRENT_FIELDS_P. If DIRENT_FIELDS_LIST is NULL, set all flags. */ static svn_error_t * parse_dirent_fields(apr_uint32_t *dirent_fields_p, svn_ra_svn__list_t *dirent_fields_list) { static const svn_string_t str_kind = SVN__STATIC_STRING(SVN_RA_SVN_DIRENT_KIND); static const svn_string_t str_size = SVN__STATIC_STRING(SVN_RA_SVN_DIRENT_SIZE); static const svn_string_t str_has_props = SVN__STATIC_STRING(SVN_RA_SVN_DIRENT_HAS_PROPS); static const svn_string_t str_created_rev = SVN__STATIC_STRING(SVN_RA_SVN_DIRENT_CREATED_REV); static const svn_string_t str_time = SVN__STATIC_STRING(SVN_RA_SVN_DIRENT_TIME); static const svn_string_t str_last_author = SVN__STATIC_STRING(SVN_RA_SVN_DIRENT_LAST_AUTHOR); apr_uint32_t dirent_fields; if (! dirent_fields_list) { dirent_fields = SVN_DIRENT_ALL; } else { int i; dirent_fields = 0; for (i = 0; i < dirent_fields_list->nelts; ++i) { svn_ra_svn__item_t *elt = &SVN_RA_SVN__LIST_ITEM(dirent_fields_list, i); if (elt->kind != SVN_RA_SVN_WORD) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, "Dirent field not a word"); if (svn_string_compare(&str_kind, &elt->u.word)) dirent_fields |= SVN_DIRENT_KIND; else if (svn_string_compare(&str_size, &elt->u.word)) dirent_fields |= SVN_DIRENT_SIZE; else if (svn_string_compare(&str_has_props, &elt->u.word)) dirent_fields |= SVN_DIRENT_HAS_PROPS; else if (svn_string_compare(&str_created_rev, &elt->u.word)) dirent_fields |= SVN_DIRENT_CREATED_REV; else if (svn_string_compare(&str_time, &elt->u.word)) dirent_fields |= SVN_DIRENT_TIME; else if (svn_string_compare(&str_last_author, &elt->u.word)) dirent_fields |= SVN_DIRENT_LAST_AUTHOR; } } *dirent_fields_p = dirent_fields; return SVN_NO_ERROR; } static svn_error_t * get_dir(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { server_baton_t *b = baton; const char *path, *full_path, *canonical_path; svn_revnum_t rev; apr_hash_t *entries, *props = NULL; apr_array_header_t *inherited_props; apr_hash_index_t *hi; svn_fs_root_t *root; apr_pool_t *subpool; svn_boolean_t want_props, want_contents; apr_uint64_t wants_inherited_props; apr_uint32_t dirent_fields; svn_ra_svn__list_t *dirent_fields_list = NULL; int i; authz_baton_t ab; ab.server = b; ab.conn = conn; SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)bb?l?B", &path, &rev, &want_props, &want_contents, &dirent_fields_list, &wants_inherited_props)); if (wants_inherited_props == SVN_RA_SVN_UNSPECIFIED_NUMBER) wants_inherited_props = FALSE; SVN_ERR(parse_dirent_fields(&dirent_fields, dirent_fields_list)); SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, pool, pool)); full_path = svn_fspath__join(b->repository->fs_path->data, canonical_path, pool); /* Check authorizations */ SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, full_path, FALSE)); if (!SVN_IS_VALID_REVNUM(rev)) SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool)); SVN_ERR(log_command(b, conn, pool, "%s", svn_log__get_dir(full_path, rev, want_contents, want_props, dirent_fields, pool))); /* Fetch the root of the appropriate revision. */ SVN_CMD_ERR(svn_fs_revision_root(&root, b->repository->fs, rev, pool)); /* Fetch the directory's explicit and/or inherited properties if requested. Although the wants-iprops boolean was added to the protocol in 1.8 a standard 1.8 client never requests iprops. */ if (want_props || wants_inherited_props) SVN_CMD_ERR(get_props(want_props ? &props : NULL, wants_inherited_props ? &inherited_props : NULL, &ab, root, full_path, pool)); /* Fetch the directories' entries before starting the response, to allow proper error handling in cases like when FULL_PATH doesn't exist */ if (want_contents) SVN_CMD_ERR(svn_fs_dir_entries(&entries, root, full_path, pool)); /* Begin response ... */ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(r(!", "success", rev)); SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props)); SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(!")); /* Fetch the directory entries if requested and send them immediately. */ if (want_contents) { /* Use epoch for a placeholder for a missing date. */ const char *missing_date = svn_time_to_cstring(0, pool); /* Transform the hash table's FS entries into dirents. This probably * belongs in libsvn_repos. */ subpool = svn_pool_create(pool); for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) { const char *name = apr_hash_this_key(hi); svn_fs_dirent_t *fsent = apr_hash_this_val(hi); const char *file_path; /* The fields in the entry tuple. */ svn_node_kind_t entry_kind = svn_node_none; svn_filesize_t entry_size = 0; svn_boolean_t has_props = FALSE; /* If 'created rev' was not requested, send 0. We can't use * SVN_INVALID_REVNUM as the tuple field is not optional. * See the email thread on dev@, 2012-03-28, subject * "buildbot failure in ASF Buildbot on svn-slik-w2k3-x64-ra", * . */ svn_revnum_t created_rev = 0; const char *cdate = NULL; const char *last_author = NULL; svn_pool_clear(subpool); file_path = svn_fspath__join(full_path, name, subpool); if (! lookup_access(subpool, b, svn_authz_read, file_path, FALSE)) continue; if (dirent_fields & SVN_DIRENT_KIND) entry_kind = fsent->kind; if (dirent_fields & SVN_DIRENT_SIZE) if (fsent->kind != svn_node_dir) SVN_CMD_ERR(svn_fs_file_length(&entry_size, root, file_path, subpool)); if (dirent_fields & SVN_DIRENT_HAS_PROPS) { /* has_props */ SVN_CMD_ERR(svn_fs_node_has_props(&has_props, root, file_path, subpool)); } if ((dirent_fields & SVN_DIRENT_LAST_AUTHOR) || (dirent_fields & SVN_DIRENT_TIME) || (dirent_fields & SVN_DIRENT_CREATED_REV)) { /* created_rev, last_author, time */ SVN_CMD_ERR(svn_repos_get_committed_info(&created_rev, &cdate, &last_author, root, file_path, subpool)); } /* The client does not properly handle a missing CDATE. For interoperability purposes, we must fill in some junk. See libsvn_ra_svn/client.c:ra_svn_get_dir() */ if (cdate == NULL) cdate = missing_date; /* Send the entry. */ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "cwnbr(?c)(?c)", name, svn_node_kind_to_word(entry_kind), (apr_uint64_t) entry_size, has_props, created_rev, cdate, last_author)); } svn_pool_destroy(subpool); } if (wants_inherited_props) { apr_pool_t *iterpool = svn_pool_create(pool); SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?!")); for (i = 0; i < inherited_props->nelts; i++) { svn_prop_inherited_item_t *iprop = APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *); svn_pool_clear(iterpool); SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!", iprop->path_or_url)); SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash)); SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!", iprop->path_or_url)); } svn_pool_destroy(iterpool); } /* Finish response. */ return svn_ra_svn__write_tuple(conn, pool, "!))"); } static svn_error_t * update(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { server_baton_t *b = baton; svn_revnum_t rev; const char *target, *full_path, *depth_word, *canonical_target; svn_boolean_t recurse; svn_tristate_t send_copyfrom_args; /* Optional; default FALSE */ svn_tristate_t ignore_ancestry; /* Optional; default FALSE */ /* Default to unknown. Old clients won't send depth, but we'll handle that by converting recurse if necessary. */ svn_depth_t depth = svn_depth_unknown; svn_boolean_t is_checkout; /* Parse the arguments. */ SVN_ERR(svn_ra_svn__parse_tuple(params, "(?r)cb?w3?3", &rev, &target, &recurse, &depth_word, &send_copyfrom_args, &ignore_ancestry)); SVN_ERR(svn_relpath_canonicalize_safe(&canonical_target, NULL, target, pool, pool)); target = canonical_target; if (depth_word) depth = svn_depth_from_word(depth_word); else depth = SVN_DEPTH_INFINITY_OR_FILES(recurse); full_path = svn_fspath__join(b->repository->fs_path->data, target, pool); /* Check authorization and authenticate the user if necessary. */ SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, full_path, FALSE)); if (!SVN_IS_VALID_REVNUM(rev)) SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool)); SVN_ERR(accept_report(&is_checkout, NULL, conn, pool, b, rev, target, NULL, TRUE, depth, (send_copyfrom_args == svn_tristate_true), (ignore_ancestry == svn_tristate_true))); if (is_checkout) { SVN_ERR(log_command(b, conn, pool, "%s", svn_log__checkout(full_path, rev, depth, pool))); } else { SVN_ERR(log_command(b, conn, pool, "%s", svn_log__update(full_path, rev, depth, (send_copyfrom_args == svn_tristate_true), pool))); } return SVN_NO_ERROR; } static svn_error_t * switch_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { server_baton_t *b = baton; svn_revnum_t rev; const char *target, *depth_word; const char *switch_url, *switch_path, *canonical_url, *canonical_target; svn_boolean_t recurse; /* Default to unknown. Old clients won't send depth, but we'll handle that by converting recurse if necessary. */ svn_depth_t depth = svn_depth_unknown; svn_tristate_t send_copyfrom_args; /* Optional; default FALSE */ svn_tristate_t ignore_ancestry; /* Optional; default TRUE */ /* Parse the arguments. */ SVN_ERR(svn_ra_svn__parse_tuple(params, "(?r)cbc?w?33", &rev, &target, &recurse, &switch_url, &depth_word, &send_copyfrom_args, &ignore_ancestry)); SVN_ERR(svn_relpath_canonicalize_safe(&canonical_target, NULL, target, pool, pool)); target = canonical_target; SVN_ERR(svn_uri_canonicalize_safe(&canonical_url, NULL, switch_url, pool, pool)); switch_url = canonical_url; if (depth_word) depth = svn_depth_from_word(depth_word); else depth = SVN_DEPTH_INFINITY_OR_FILES(recurse); SVN_ERR(trivial_auth_request(conn, pool, b)); if (!SVN_IS_VALID_REVNUM(rev)) SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool)); SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repository->repos_url, pool), svn_path_uri_decode(switch_url, pool), &switch_path)); { const char *full_path = svn_fspath__join(b->repository->fs_path->data, target, pool); SVN_ERR(log_command(b, conn, pool, "%s", svn_log__switch(full_path, switch_path, rev, depth, pool))); } return accept_report(NULL, NULL, conn, pool, b, rev, target, switch_path, TRUE, depth, (send_copyfrom_args == svn_tristate_true), (ignore_ancestry != svn_tristate_false)); } static svn_error_t * status(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { server_baton_t *b = baton; svn_revnum_t rev; const char *target, *depth_word, *canonical_target; svn_boolean_t recurse; /* Default to unknown. Old clients won't send depth, but we'll handle that by converting recurse if necessary. */ svn_depth_t depth = svn_depth_unknown; /* Parse the arguments. */ SVN_ERR(svn_ra_svn__parse_tuple(params, "cb?(?r)?w", &target, &recurse, &rev, &depth_word)); SVN_ERR(svn_relpath_canonicalize_safe(&canonical_target, NULL, target, pool, pool)); target = canonical_target; if (depth_word) depth = svn_depth_from_word(depth_word); else depth = SVN_DEPTH_INFINITY_OR_EMPTY(recurse); SVN_ERR(trivial_auth_request(conn, pool, b)); if (!SVN_IS_VALID_REVNUM(rev)) SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool)); { const char *full_path = svn_fspath__join(b->repository->fs_path->data, target, pool); SVN_ERR(log_command(b, conn, pool, "%s", svn_log__status(full_path, rev, depth, pool))); } return accept_report(NULL, NULL, conn, pool, b, rev, target, NULL, FALSE, depth, FALSE, FALSE); } static svn_error_t * diff(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { server_baton_t *b = baton; svn_revnum_t rev; const char *target, *versus_url, *versus_path, *depth_word, *canonical_url; const char *canonical_target; svn_boolean_t recurse, ignore_ancestry; svn_boolean_t text_deltas; /* Default to unknown. Old clients won't send depth, but we'll handle that by converting recurse if necessary. */ svn_depth_t depth = svn_depth_unknown; /* Parse the arguments. */ if (params->nelts == 5) { /* Clients before 1.4 don't send the text_deltas boolean or depth. */ SVN_ERR(svn_ra_svn__parse_tuple(params, "(?r)cbbc", &rev, &target, &recurse, &ignore_ancestry, &versus_url)); text_deltas = TRUE; depth_word = NULL; } else { SVN_ERR(svn_ra_svn__parse_tuple(params, "(?r)cbbcb?w", &rev, &target, &recurse, &ignore_ancestry, &versus_url, &text_deltas, &depth_word)); } SVN_ERR(svn_relpath_canonicalize_safe(&canonical_target, NULL, target, pool, pool)); target = canonical_target; SVN_ERR(svn_uri_canonicalize_safe(&canonical_url, NULL, versus_url, pool, pool)); versus_url = canonical_url; if (depth_word) depth = svn_depth_from_word(depth_word); else depth = SVN_DEPTH_INFINITY_OR_FILES(recurse); SVN_ERR(trivial_auth_request(conn, pool, b)); if (!SVN_IS_VALID_REVNUM(rev)) SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool)); SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repository->repos_url, pool), svn_path_uri_decode(versus_url, pool), &versus_path)); { const char *full_path = svn_fspath__join(b->repository->fs_path->data, target, pool); svn_revnum_t from_rev; SVN_ERR(accept_report(NULL, &from_rev, conn, pool, b, rev, target, versus_path, text_deltas, depth, FALSE, ignore_ancestry)); SVN_ERR(log_command(b, conn, pool, "%s", svn_log__diff(full_path, from_rev, versus_path, rev, depth, ignore_ancestry, pool))); } return SVN_NO_ERROR; } /* Baton type to be used with mergeinfo_receiver. */ typedef struct mergeinfo_receiver_baton_t { /* Send the response over this connection. */ svn_ra_svn_conn_t *conn; /* Start path of the query; report paths relative to this one. */ const char *fs_path; /* Did we already send the opening sequence? */ svn_boolean_t starting_tuple_sent; } mergeinfo_receiver_baton_t; /* Utility method sending the start of the "get m/i" response once over BATON->CONN. */ static svn_error_t * send_mergeinfo_starting_tuple(mergeinfo_receiver_baton_t *baton, apr_pool_t *scratch_pool) { if (baton->starting_tuple_sent) return SVN_NO_ERROR; SVN_ERR(svn_ra_svn__write_tuple(baton->conn, scratch_pool, "w((!", "success")); baton->starting_tuple_sent = TRUE; return SVN_NO_ERROR; } /* Implements svn_repos_mergeinfo_receiver_t, sending the MERGEINFO * out over the connection in the mergeinfo_receiver_baton_t * BATON. */ static svn_error_t * mergeinfo_receiver(const char *path, svn_mergeinfo_t mergeinfo, void *baton, apr_pool_t *scratch_pool) { mergeinfo_receiver_baton_t *b = baton; svn_string_t *mergeinfo_string; /* Delay starting the response until we checked that the initial request went through. We are at that point now b/c we've got the first results in. */ SVN_ERR(send_mergeinfo_starting_tuple(b, scratch_pool)); /* Adjust the path info and send the m/i. */ path = svn_fspath__skip_ancestor(b->fs_path, path); SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, mergeinfo, scratch_pool)); SVN_ERR(svn_ra_svn__write_tuple(b->conn, scratch_pool, "cs", path, mergeinfo_string)); return SVN_NO_ERROR; } /* Regardless of whether a client's capabilities indicate an understanding of this command (by way of SVN_RA_SVN_CAP_MERGEINFO), we provide a response. ASSUMPTION: When performing a 'merge' with two URLs at different revisions, the client will call this command more than once. */ static svn_error_t * get_mergeinfo(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { server_baton_t *b = baton; svn_revnum_t rev; svn_ra_svn__list_t *paths; apr_array_header_t *canonical_paths; int i; const char *inherit_word; svn_mergeinfo_inheritance_t inherit; svn_boolean_t include_descendants; authz_baton_t ab; mergeinfo_receiver_baton_t mergeinfo_baton; ab.server = b; ab.conn = conn; mergeinfo_baton.conn = conn; mergeinfo_baton.fs_path = b->repository->fs_path->data; mergeinfo_baton.starting_tuple_sent = FALSE; SVN_ERR(svn_ra_svn__parse_tuple(params, "l(?r)wb", &paths, &rev, &inherit_word, &include_descendants)); inherit = svn_inheritance_from_word(inherit_word); /* Canonicalize the paths which mergeinfo has been requested for. */ canonical_paths = apr_array_make(pool, paths->nelts, sizeof(const char *)); for (i = 0; i < paths->nelts; i++) { svn_ra_svn__item_t *item = &SVN_RA_SVN__LIST_ITEM(paths, i); const char *full_path, *canonical_path; if (item->kind != SVN_RA_SVN_STRING) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Path is not a string")); SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, item->u.string.data, pool, pool)); full_path = svn_fspath__join(b->repository->fs_path->data, canonical_path, pool); APR_ARRAY_PUSH(canonical_paths, const char *) = full_path; } SVN_ERR(log_command(b, conn, pool, "%s", svn_log__get_mergeinfo(canonical_paths, inherit, include_descendants, pool))); SVN_ERR(trivial_auth_request(conn, pool, b)); SVN_CMD_ERR(svn_repos_fs_get_mergeinfo2(b->repository->repos, canonical_paths, rev, inherit, include_descendants, authz_check_access_cb_func(b), &ab, mergeinfo_receiver, &mergeinfo_baton, pool)); /* We might not have sent anything => ensure to begin the response in any case. */ SVN_ERR(send_mergeinfo_starting_tuple(&mergeinfo_baton, pool)); SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); return SVN_NO_ERROR; } /* Send a changed paths list entry to the client. This implements svn_repos_path_change_receiver_t. */ static svn_error_t * path_change_receiver(void *baton, svn_repos_path_change_t *change, apr_pool_t *scratch_pool) { const char symbol[] = "MADR"; log_baton_t *b = baton; svn_ra_svn_conn_t *conn = b->conn; /* Sanitize and convert change kind to ra-svn level action. Pushing that conversion down into libsvn_ra_svn would add yet another API dependency there. */ char action = ( change->change_kind < svn_fs_path_change_modify || change->change_kind > svn_fs_path_change_replace) ? 0 : symbol[change->change_kind]; /* Open lists once: LOG_ENTRY and LOG_ENTRY->CHANGED_PATHS. */ if (!b->started) { SVN_ERR(svn_ra_svn__start_list(conn, scratch_pool)); SVN_ERR(svn_ra_svn__start_list(conn, scratch_pool)); b->started = TRUE; } /* Serialize CHANGE. */ SVN_ERR(svn_ra_svn__write_data_log_changed_path( conn, scratch_pool, &change->path, action, change->copyfrom_path, change->copyfrom_rev, change->node_kind, change->text_mod, change->prop_mod)); return SVN_NO_ERROR; } /* Send a the meta data and the revpros for LOG_ENTRY to the client. This implements svn_log_entry_receiver_t. */ static svn_error_t * revision_receiver(void *baton, svn_repos_log_entry_t *log_entry, apr_pool_t *scratch_pool) { log_baton_t *b = baton; svn_ra_svn_conn_t *conn = b->conn; svn_boolean_t invalid_revnum = FALSE; const svn_string_t *author, *date, *message; unsigned revprop_count; if (log_entry->revision == SVN_INVALID_REVNUM) { /* If the stack depth is zero, we've seen the last revision, so don't send it, just return. */ if (b->stack_depth == 0) return SVN_NO_ERROR; /* Because the svn protocol won't let us send an invalid revnum, we have to fudge here and send an additional flag. */ log_entry->revision = 0; invalid_revnum = TRUE; b->stack_depth--; } svn_compat_log_revprops_out_string(&author, &date, &message, log_entry->revprops); /* Revprops list filtering is somewhat expensive. Avoid doing that for the 90% case where only the standard revprops have been requested and delivered. */ if (author && date && message && apr_hash_count(log_entry->revprops) == 3) { revprop_count = 0; } else { svn_compat_log_revprops_clear(log_entry->revprops); if (log_entry->revprops) revprop_count = apr_hash_count(log_entry->revprops); else revprop_count = 0; } /* Open lists once: LOG_ENTRY and LOG_ENTRY->CHANGED_PATHS. */ if (!b->started) { SVN_ERR(svn_ra_svn__start_list(conn, scratch_pool)); SVN_ERR(svn_ra_svn__start_list(conn, scratch_pool)); } /* Close LOG_ENTRY->CHANGED_PATHS. */ SVN_ERR(svn_ra_svn__end_list(conn, scratch_pool)); b->started = FALSE; /* send LOG_ENTRY main members */ SVN_ERR(svn_ra_svn__write_data_log_entry(conn, scratch_pool, log_entry->revision, author, date, message, log_entry->has_children, invalid_revnum, revprop_count)); /* send LOG_ENTRY->REVPROPS */ SVN_ERR(svn_ra_svn__start_list(conn, scratch_pool)); if (revprop_count) SVN_ERR(svn_ra_svn__write_proplist(conn, scratch_pool, log_entry->revprops)); SVN_ERR(svn_ra_svn__end_list(conn, scratch_pool)); /* send LOG_ENTRY members that were added in later SVN releases */ SVN_ERR(svn_ra_svn__write_boolean(conn, scratch_pool, log_entry->subtractive_merge)); SVN_ERR(svn_ra_svn__end_list(conn, scratch_pool)); if (log_entry->has_children) b->stack_depth++; return SVN_NO_ERROR; } static svn_error_t * log_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { svn_error_t *err, *write_err; server_baton_t *b = baton; svn_revnum_t start_rev, end_rev; const char *full_path, *canonical_path; svn_boolean_t send_changed_paths, strict_node, include_merged_revisions; apr_array_header_t *full_paths, *revprops; svn_ra_svn__list_t *paths, *revprop_items; char *revprop_word; svn_ra_svn__item_t *elt; int i; apr_uint64_t limit, include_merged_revs_param; log_baton_t lb; authz_baton_t ab; ab.server = b; ab.conn = conn; SVN_ERR(svn_ra_svn__parse_tuple(params, "l(?r)(?r)bb?n?Bwl", &paths, &start_rev, &end_rev, &send_changed_paths, &strict_node, &limit, &include_merged_revs_param, &revprop_word, &revprop_items)); if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) include_merged_revisions = FALSE; else include_merged_revisions = (svn_boolean_t) include_merged_revs_param; if (revprop_word == NULL) /* pre-1.5 client */ revprops = svn_compat_log_revprops_in(pool); else if (strcmp(revprop_word, "all-revprops") == 0) revprops = NULL; else if (strcmp(revprop_word, "revprops") == 0) { SVN_ERR_ASSERT(revprop_items); revprops = apr_array_make(pool, revprop_items->nelts, sizeof(char *)); for (i = 0; i < revprop_items->nelts; i++) { elt = &SVN_RA_SVN__LIST_ITEM(revprop_items, i); if (elt->kind != SVN_RA_SVN_STRING) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Log revprop entry not a string")); APR_ARRAY_PUSH(revprops, const char *) = elt->u.string.data; } } else return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Unknown revprop word '%s' in log command"), revprop_word); /* If we got an unspecified number then the user didn't send us anything, so we assume no limit. If it's larger than INT_MAX then someone is messing with us, since we know the svn client libraries will never send us anything that big, so play it safe and default to no limit. */ if (limit == SVN_RA_SVN_UNSPECIFIED_NUMBER || limit > INT_MAX) limit = 0; full_paths = apr_array_make(pool, paths->nelts, sizeof(const char *)); for (i = 0; i < paths->nelts; i++) { elt = &SVN_RA_SVN__LIST_ITEM(paths, i); if (elt->kind != SVN_RA_SVN_STRING) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Log path entry not a string")); SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, elt->u.string.data, pool, pool)); full_path = svn_fspath__join(b->repository->fs_path->data, canonical_path, pool); APR_ARRAY_PUSH(full_paths, const char *) = full_path; } SVN_ERR(trivial_auth_request(conn, pool, b)); SVN_ERR(log_command(b, conn, pool, "%s", svn_log__log(full_paths, start_rev, end_rev, (int) limit, send_changed_paths, strict_node, include_merged_revisions, revprops, pool))); /* Get logs. (Can't report errors back to the client at this point.) */ lb.fs_path = b->repository->fs_path->data; lb.conn = conn; lb.stack_depth = 0; lb.started = FALSE; err = svn_repos_get_logs5(b->repository->repos, full_paths, start_rev, end_rev, (int) limit, strict_node, include_merged_revisions, revprops, authz_check_access_cb_func(b), &ab, send_changed_paths ? path_change_receiver : NULL, send_changed_paths ? &lb : NULL, revision_receiver, &lb, pool); write_err = svn_ra_svn__write_word(conn, pool, "done"); if (write_err) { svn_error_clear(err); return write_err; } SVN_CMD_ERR(err); SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); return SVN_NO_ERROR; } static svn_error_t * check_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { server_baton_t *b = baton; svn_revnum_t rev; const char *path, *full_path, *canonical_path; svn_fs_root_t *root; svn_node_kind_t kind; SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)", &path, &rev)); SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, pool, pool));; full_path = svn_fspath__join(b->repository->fs_path->data, canonical_path, pool); /* Check authorizations */ SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, full_path, FALSE)); if (!SVN_IS_VALID_REVNUM(rev)) SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool)); SVN_ERR(log_command(b, conn, pool, "check-path %s@%d", svn_path_uri_encode(full_path, pool), rev)); SVN_CMD_ERR(svn_fs_revision_root(&root, b->repository->fs, rev, pool)); SVN_CMD_ERR(svn_fs_check_path(&kind, root, full_path, pool)); SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "w", svn_node_kind_to_word(kind))); return SVN_NO_ERROR; } static svn_error_t * stat_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { server_baton_t *b = baton; svn_revnum_t rev; const char *path, *full_path, *cdate, *canonical_path; svn_fs_root_t *root; svn_dirent_t *dirent; SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)", &path, &rev)); SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, pool, pool)); full_path = svn_fspath__join(b->repository->fs_path->data, canonical_path, pool); /* Check authorizations */ SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, full_path, FALSE)); if (!SVN_IS_VALID_REVNUM(rev)) SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool)); SVN_ERR(log_command(b, conn, pool, "stat %s@%d", svn_path_uri_encode(full_path, pool), rev)); SVN_CMD_ERR(svn_fs_revision_root(&root, b->repository->fs, rev, pool)); SVN_CMD_ERR(svn_repos_stat(&dirent, root, full_path, pool)); /* Need to return the equivalent of "(?l)", since that's what the client is reading. */ if (dirent == NULL) { SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "()")); return SVN_NO_ERROR; } cdate = (dirent->time == (time_t) -1) ? NULL : svn_time_to_cstring(dirent->time, pool); SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "((wnbr(?c)(?c)))", svn_node_kind_to_word(dirent->kind), (apr_uint64_t) dirent->size, dirent->has_props, dirent->created_rev, cdate, dirent->last_author)); return SVN_NO_ERROR; } static svn_error_t * get_locations(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { svn_error_t *err, *write_err; server_baton_t *b = baton; svn_revnum_t revision; apr_array_header_t *location_revisions; svn_ra_svn__list_t *loc_revs_proto; svn_ra_svn__item_t *elt; int i; const char *relative_path, *canonical_path; svn_revnum_t peg_revision; apr_hash_t *fs_locations; const char *abs_path; authz_baton_t ab; ab.server = b; ab.conn = conn; /* Parse the arguments. */ SVN_ERR(svn_ra_svn__parse_tuple(params, "crl", &relative_path, &peg_revision, &loc_revs_proto)); SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, relative_path, pool, pool)); relative_path = canonical_path; abs_path = svn_fspath__join(b->repository->fs_path->data, relative_path, pool); location_revisions = apr_array_make(pool, loc_revs_proto->nelts, sizeof(svn_revnum_t)); for (i = 0; i < loc_revs_proto->nelts; i++) { elt = &SVN_RA_SVN__LIST_ITEM(loc_revs_proto, i); if (elt->kind != SVN_RA_SVN_NUMBER) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, "Get-locations location revisions entry " "not a revision number"); revision = (svn_revnum_t)(elt->u.number); APR_ARRAY_PUSH(location_revisions, svn_revnum_t) = revision; } SVN_ERR(trivial_auth_request(conn, pool, b)); SVN_ERR(log_command(b, conn, pool, "%s", svn_log__get_locations(abs_path, peg_revision, location_revisions, pool))); /* All the parameters are fine - let's perform the query against the * repository. */ /* We store both err and write_err here, so the client will get * the "done" even if there was an error in fetching the results. */ err = svn_repos_trace_node_locations(b->repository->fs, &fs_locations, abs_path, peg_revision, location_revisions, authz_check_access_cb_func(b), &ab, pool); /* Now, write the results to the connection. */ if (!err) { if (fs_locations) { apr_hash_index_t *iter; for (iter = apr_hash_first(pool, fs_locations); iter; iter = apr_hash_next(iter)) { const svn_revnum_t *iter_key = apr_hash_this_key(iter); const char *iter_value = apr_hash_this_val(iter); SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "rc", *iter_key, iter_value)); } } } write_err = svn_ra_svn__write_word(conn, pool, "done"); if (write_err) { svn_error_clear(err); return write_err; } SVN_CMD_ERR(err); SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); return SVN_NO_ERROR; } static svn_error_t *gls_receiver(svn_location_segment_t *segment, void *baton, apr_pool_t *pool) { svn_ra_svn_conn_t *conn = baton; return svn_ra_svn__write_tuple(conn, pool, "rr(?c)", segment->range_start, segment->range_end, segment->path); } static svn_error_t * get_location_segments(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { svn_error_t *err, *write_err; server_baton_t *b = baton; svn_revnum_t peg_revision, start_rev, end_rev; const char *relative_path, *canonical_path; const char *abs_path; authz_baton_t ab; ab.server = b; ab.conn = conn; /* Parse the arguments. */ SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)(?r)(?r)", &relative_path, &peg_revision, &start_rev, &end_rev)); SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, relative_path, pool, pool)); relative_path = canonical_path; abs_path = svn_fspath__join(b->repository->fs_path->data, relative_path, pool); SVN_ERR(trivial_auth_request(conn, pool, b)); SVN_ERR(log_command(baton, conn, pool, "%s", svn_log__get_location_segments(abs_path, peg_revision, start_rev, end_rev, pool))); /* No START_REV or PEG_REVISION? We'll use HEAD. */ if (!SVN_IS_VALID_REVNUM(start_rev) || !SVN_IS_VALID_REVNUM(peg_revision)) { svn_revnum_t youngest; err = svn_fs_youngest_rev(&youngest, b->repository->fs, pool); if (err) { err = svn_error_compose_create( svn_ra_svn__write_word(conn, pool, "done"), err); return log_fail_and_flush(err, b, conn, pool); } if (!SVN_IS_VALID_REVNUM(start_rev)) start_rev = youngest; if (!SVN_IS_VALID_REVNUM(peg_revision)) peg_revision = youngest; } /* No END_REV? We'll use 0. */ if (!SVN_IS_VALID_REVNUM(end_rev)) end_rev = 0; if (end_rev > start_rev) { err = svn_ra_svn__write_word(conn, pool, "done"); err = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, err, "Get-location-segments end revision must not be " "younger than start revision"); return log_fail_and_flush(err, b, conn, pool); } if (start_rev > peg_revision) { err = svn_ra_svn__write_word(conn, pool, "done"); err = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, err, "Get-location-segments start revision must not " "be younger than peg revision"); return log_fail_and_flush(err, b, conn, pool); } /* All the parameters are fine - let's perform the query against the * repository. */ /* We store both err and write_err here, so the client will get * the "done" even if there was an error in fetching the results. */ err = svn_repos_node_location_segments(b->repository->repos, abs_path, peg_revision, start_rev, end_rev, gls_receiver, (void *)conn, authz_check_access_cb_func(b), &ab, pool); write_err = svn_ra_svn__write_word(conn, pool, "done"); if (write_err) { return svn_error_compose_create(write_err, err); } SVN_CMD_ERR(err); SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); return SVN_NO_ERROR; } /* This implements svn_write_fn_t. Write LEN bytes starting at DATA to the client as a string. */ static svn_error_t *svndiff_handler(void *baton, const char *data, apr_size_t *len) { file_revs_baton_t *b = baton; svn_string_t str; str.data = data; str.len = *len; return svn_ra_svn__write_string(b->conn, b->pool, &str); } /* This implements svn_close_fn_t. Mark the end of the data by writing an empty string to the client. */ static svn_error_t *svndiff_close_handler(void *baton) { file_revs_baton_t *b = baton; SVN_ERR(svn_ra_svn__write_cstring(b->conn, b->pool, "")); return SVN_NO_ERROR; } /* This implements the svn_repos_file_rev_handler_t interface. */ static svn_error_t *file_rev_handler(void *baton, const char *path, svn_revnum_t rev, apr_hash_t *rev_props, svn_boolean_t merged_revision, svn_txdelta_window_handler_t *d_handler, void **d_baton, apr_array_header_t *prop_diffs, apr_pool_t *pool) { file_revs_baton_t *frb = baton; svn_stream_t *stream; SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "cr(!", path, rev)); SVN_ERR(svn_ra_svn__write_proplist(frb->conn, pool, rev_props)); SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "!)(!")); SVN_ERR(write_prop_diffs(frb->conn, pool, prop_diffs)); SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "!)b", merged_revision)); /* Store the pool for the delta stream. */ frb->pool = pool; /* Prepare for the delta or just write an empty string. */ if (d_handler) { stream = svn_stream_create(baton, pool); svn_stream_set_write(stream, svndiff_handler); svn_stream_set_close(stream, svndiff_close_handler); svn_txdelta_to_svndiff3(d_handler, d_baton, stream, svn_ra_svn__svndiff_version(frb->conn), svn_ra_svn_compression_level(frb->conn), pool); } else SVN_ERR(svn_ra_svn__write_cstring(frb->conn, pool, "")); return SVN_NO_ERROR; } static svn_error_t * get_file_revs(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { server_baton_t *b = baton; svn_error_t *err, *write_err; file_revs_baton_t frb; svn_revnum_t start_rev, end_rev; const char *path; const char *full_path; const char *canonical_path; apr_uint64_t include_merged_revs_param; svn_boolean_t include_merged_revisions; authz_baton_t ab; ab.server = b; ab.conn = conn; /* Parse arguments. */ SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)(?r)?B", &path, &start_rev, &end_rev, &include_merged_revs_param)); SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, pool, pool)); path = canonical_path; SVN_ERR(trivial_auth_request(conn, pool, b)); full_path = svn_fspath__join(b->repository->fs_path->data, path, pool); if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) include_merged_revisions = FALSE; else include_merged_revisions = (svn_boolean_t) include_merged_revs_param; SVN_ERR(log_command(b, conn, pool, "%s", svn_log__get_file_revs(full_path, start_rev, end_rev, include_merged_revisions, pool))); frb.conn = conn; frb.pool = NULL; err = svn_repos_get_file_revs2(b->repository->repos, full_path, start_rev, end_rev, include_merged_revisions, authz_check_access_cb_func(b), &ab, file_rev_handler, &frb, pool); write_err = svn_ra_svn__write_word(conn, pool, "done"); if (write_err) { svn_error_clear(err); return write_err; } SVN_CMD_ERR(err); SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); return SVN_NO_ERROR; } static svn_error_t * lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { server_baton_t *b = baton; const char *path; const char *comment; const char *full_path; const char *canonical_path; svn_boolean_t steal_lock; svn_revnum_t current_rev; svn_lock_t *l; SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?c)b(?r)", &path, &comment, &steal_lock, ¤t_rev)); SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, pool, pool));; full_path = svn_fspath__join(b->repository->fs_path->data, canonical_path, pool); SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, full_path, TRUE)); SVN_ERR(log_command(b, conn, pool, "%s", svn_log__lock_one_path(full_path, steal_lock, pool))); SVN_CMD_ERR(svn_repos_fs_lock(&l, b->repository->repos, full_path, NULL, comment, 0, 0, /* No expiration time. */ current_rev, steal_lock, pool)); SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(!", "success")); SVN_ERR(write_lock(conn, pool, l)); SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)")); return SVN_NO_ERROR; } struct lock_result_t { const svn_lock_t *lock; svn_error_t *err; }; struct lock_many_baton_t { apr_hash_t *results; apr_pool_t *pool; }; /* Implements svn_fs_lock_callback_t. */ static svn_error_t * lock_many_cb(void *baton, const char *path, const svn_lock_t *fs_lock, svn_error_t *fs_err, apr_pool_t *pool) { struct lock_many_baton_t *b = baton; struct lock_result_t *result = apr_palloc(b->pool, sizeof(struct lock_result_t)); result->lock = fs_lock; result->err = svn_error_dup(fs_err); svn_hash_sets(b->results, apr_pstrdup(b->pool, path), result); return SVN_NO_ERROR; } static void clear_lock_result_hash(apr_hash_t *results, apr_pool_t *scratch_pool) { apr_hash_index_t *hi; for (hi = apr_hash_first(scratch_pool, results); hi; hi = apr_hash_next(hi)) { struct lock_result_t *result = apr_hash_this_val(hi); svn_error_clear(result->err); } } static svn_error_t * lock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { server_baton_t *b = baton; svn_ra_svn__list_t *path_revs; const char *comment; svn_boolean_t steal_lock; int i; apr_pool_t *subpool; svn_error_t *err, *write_err = SVN_NO_ERROR; apr_hash_t *targets = apr_hash_make(pool); apr_hash_t *authz_results = apr_hash_make(pool); apr_hash_index_t *hi; struct lock_many_baton_t lmb; SVN_ERR(svn_ra_svn__parse_tuple(params, "(?c)bl", &comment, &steal_lock, &path_revs)); subpool = svn_pool_create(pool); /* Because we can only send a single auth reply per request, we send a reply before parsing the lock commands. This means an authz access denial will abort the processing of the locks and return an error. */ SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, TRUE)); /* Parse the lock requests from PATH_REVS into TARGETS. */ for (i = 0; i < path_revs->nelts; ++i) { const char *path, *full_path, *canonical_path; svn_revnum_t current_rev; svn_ra_svn__item_t *item = &SVN_RA_SVN__LIST_ITEM(path_revs, i); svn_fs_lock_target_t *target; svn_pool_clear(subpool); if (item->kind != SVN_RA_SVN_LIST) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, "Lock requests should be list of lists"); SVN_ERR(svn_ra_svn__parse_tuple(&item->u.list, "c(?r)", &path, ¤t_rev)); SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, subpool, subpool)); full_path = svn_fspath__join(b->repository->fs_path->data, canonical_path, pool); target = svn_fs_lock_target_create(NULL, current_rev, pool); /* Any duplicate paths, once canonicalized, get collapsed into a single path that is processed once. The result is then returned multiple times. */ svn_hash_sets(targets, full_path, target); } SVN_ERR(log_command(b, conn, subpool, "%s", svn_log__lock(targets, steal_lock, subpool))); /* Check authz. Note: From here on we need to make sure any errors in authz_results, or results, are cleared before returning from this function. */ for (hi = apr_hash_first(pool, targets); hi; hi = apr_hash_next(hi)) { const char *full_path = apr_hash_this_key(hi); svn_pool_clear(subpool); if (! lookup_access(subpool, b, svn_authz_write, full_path, TRUE)) { struct lock_result_t *result = apr_palloc(pool, sizeof(struct lock_result_t)); result->lock = NULL; result->err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, NULL, b); svn_hash_sets(authz_results, full_path, result); svn_hash_sets(targets, full_path, NULL); } } lmb.results = apr_hash_make(pool); lmb.pool = pool; err = svn_repos_fs_lock_many(b->repository->repos, targets, comment, FALSE, 0, /* No expiration time. */ steal_lock, lock_many_cb, &lmb, pool, subpool); /* Return results in the same order as the paths were supplied. */ for (i = 0; i < path_revs->nelts; ++i) { const char *path, *full_path, *canonical_path; svn_revnum_t current_rev; svn_ra_svn__item_t *item = &SVN_RA_SVN__LIST_ITEM(path_revs, i); struct lock_result_t *result; svn_pool_clear(subpool); write_err = svn_ra_svn__parse_tuple(&item->u.list, "c(?r)", &path, ¤t_rev); if (write_err) break; SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, subpool, subpool)); full_path = svn_fspath__join(b->repository->fs_path->data, canonical_path, subpool); result = svn_hash_gets(lmb.results, full_path); if (!result) result = svn_hash_gets(authz_results, full_path); if (!result) { /* No result? Something really odd happened, create a placeholder error so that any other results can be reported in the correct order. */ result = apr_palloc(pool, sizeof(struct lock_result_t)); result->err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED, 0, _("No result for '%s'."), path); svn_hash_sets(lmb.results, full_path, result); } if (result->err) write_err = svn_ra_svn__write_cmd_failure(conn, subpool, result->err); else { write_err = svn_ra_svn__write_tuple(conn, subpool, "w!", "success"); if (!write_err) write_err = write_lock(conn, subpool, result->lock); if (!write_err) write_err = svn_ra_svn__write_tuple(conn, subpool, "!"); } if (write_err) break; } clear_lock_result_hash(authz_results, subpool); clear_lock_result_hash(lmb.results, subpool); svn_pool_destroy(subpool); if (!write_err) write_err = svn_ra_svn__write_word(conn, pool, "done"); if (!write_err) SVN_CMD_ERR(err); svn_error_clear(err); SVN_ERR(write_err); SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); return SVN_NO_ERROR; } static svn_error_t * unlock(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { server_baton_t *b = baton; const char *path, *token, *full_path, *canonical_path; svn_boolean_t break_lock; SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?c)b", &path, &token, &break_lock)); SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, pool, pool)); full_path = svn_fspath__join(b->repository->fs_path->data, canonical_path, pool); /* Username required unless break_lock was specified. */ SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, full_path, ! break_lock)); SVN_ERR(log_command(b, conn, pool, "%s", svn_log__unlock_one_path(full_path, break_lock, pool))); SVN_CMD_ERR(svn_repos_fs_unlock(b->repository->repos, full_path, token, break_lock, pool)); SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); return SVN_NO_ERROR; } static svn_error_t * unlock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { server_baton_t *b = baton; svn_boolean_t break_lock; svn_ra_svn__list_t *unlock_tokens; int i; apr_pool_t *subpool; svn_error_t *err = SVN_NO_ERROR, *write_err = SVN_NO_ERROR; apr_hash_t *targets = apr_hash_make(pool); apr_hash_t *authz_results = apr_hash_make(pool); apr_hash_index_t *hi; struct lock_many_baton_t lmb; SVN_ERR(svn_ra_svn__parse_tuple(params, "bl", &break_lock, &unlock_tokens)); /* Username required unless break_lock was specified. */ SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, ! break_lock)); subpool = svn_pool_create(pool); /* Parse the unlock requests from PATH_REVS into TARGETS. */ for (i = 0; i < unlock_tokens->nelts; i++) { svn_ra_svn__item_t *item = &SVN_RA_SVN__LIST_ITEM(unlock_tokens, i); const char *path, *full_path, *token, *canonical_path; svn_pool_clear(subpool); if (item->kind != SVN_RA_SVN_LIST) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, "Unlock request should be a list of lists"); SVN_ERR(svn_ra_svn__parse_tuple(&item->u.list, "c(?c)", &path, &token)); if (!token) token = ""; SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, subpool, subpool)); full_path = svn_fspath__join(b->repository->fs_path->data, canonical_path, pool); /* Any duplicate paths, once canonicalized, get collapsed into a single path that is processed once. The result is then returned multiple times. */ svn_hash_sets(targets, full_path, token); } SVN_ERR(log_command(b, conn, subpool, "%s", svn_log__unlock(targets, break_lock, subpool))); /* Check authz. Note: From here on we need to make sure any errors in authz_results, or results, are cleared before returning from this function. */ for (hi = apr_hash_first(pool, targets); hi; hi = apr_hash_next(hi)) { const char *full_path = apr_hash_this_key(hi); svn_pool_clear(subpool); if (! lookup_access(subpool, b, svn_authz_write, full_path, ! break_lock)) { struct lock_result_t *result = apr_palloc(pool, sizeof(struct lock_result_t)); result->lock = NULL; result->err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, NULL, b); svn_hash_sets(authz_results, full_path, result); svn_hash_sets(targets, full_path, NULL); } } lmb.results = apr_hash_make(pool); lmb.pool = pool; err = svn_repos_fs_unlock_many(b->repository->repos, targets, break_lock, lock_many_cb, &lmb, pool, subpool); /* Return results in the same order as the paths were supplied. */ for (i = 0; i < unlock_tokens->nelts; ++i) { const char *path, *token, *full_path, *canonical_path; svn_ra_svn__item_t *item = &SVN_RA_SVN__LIST_ITEM(unlock_tokens, i); struct lock_result_t *result; svn_pool_clear(subpool); write_err = svn_ra_svn__parse_tuple(&item->u.list, "c(?c)", &path, &token); if (write_err) break; SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, subpool, subpool)); full_path = svn_fspath__join(b->repository->fs_path->data, canonical_path, pool); result = svn_hash_gets(lmb.results, full_path); if (!result) result = svn_hash_gets(authz_results, full_path); if (!result) { /* No result? Something really odd happened, create a placeholder error so that any other results can be reported in the correct order. */ result = apr_palloc(pool, sizeof(struct lock_result_t)); result->err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED, 0, _("No result for '%s'."), path); svn_hash_sets(lmb.results, full_path, result); } if (result->err) write_err = svn_ra_svn__write_cmd_failure(conn, pool, result->err); else write_err = svn_ra_svn__write_tuple(conn, subpool, "w(c)", "success", path); if (write_err) break; } clear_lock_result_hash(authz_results, subpool); clear_lock_result_hash(lmb.results, subpool); svn_pool_destroy(subpool); if (!write_err) write_err = svn_ra_svn__write_word(conn, pool, "done"); if (! write_err) SVN_CMD_ERR(err); svn_error_clear(err); SVN_ERR(write_err); SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); return SVN_NO_ERROR; } static svn_error_t * get_lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { server_baton_t *b = baton; const char *path; const char *full_path; const char *canonical_path; svn_lock_t *l; SVN_ERR(svn_ra_svn__parse_tuple(params, "c", &path)); SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, pool, pool)); full_path = svn_fspath__join(b->repository->fs_path->data, canonical_path, pool); SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, full_path, FALSE)); SVN_ERR(log_command(b, conn, pool, "get-lock %s", svn_path_uri_encode(full_path, pool))); SVN_CMD_ERR(svn_fs_get_lock(&l, b->repository->fs, full_path, pool)); SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success")); if (l) SVN_ERR(write_lock(conn, pool, l)); SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); return SVN_NO_ERROR; } static svn_error_t * get_locks(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { server_baton_t *b = baton; const char *path; const char *full_path; const char *canonical_path; const char *depth_word; svn_depth_t depth; apr_hash_t *locks; apr_hash_index_t *hi; svn_error_t *err; authz_baton_t ab; ab.server = b; ab.conn = conn; SVN_ERR(svn_ra_svn__parse_tuple(params, "c?(?w)", &path, &depth_word)); depth = depth_word ? svn_depth_from_word(depth_word) : svn_depth_infinity; if ((depth != svn_depth_empty) && (depth != svn_depth_files) && (depth != svn_depth_immediates) && (depth != svn_depth_infinity)) { err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, "Invalid 'depth' specified in get-locks request"); return log_fail_and_flush(err, b, conn, pool); } SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, pool, pool)); full_path = svn_fspath__join(b->repository->fs_path->data, canonical_path, pool); SVN_ERR(trivial_auth_request(conn, pool, b)); SVN_ERR(log_command(b, conn, pool, "get-locks %s", svn_path_uri_encode(full_path, pool))); SVN_CMD_ERR(svn_repos_fs_get_locks2(&locks, b->repository->repos, full_path, depth, authz_check_access_cb_func(b), &ab, pool)); SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success")); for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi)) { svn_lock_t *l = apr_hash_this_val(hi); SVN_ERR(write_lock(conn, pool, l)); } SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); return SVN_NO_ERROR; } static svn_error_t *replay_one_revision(svn_ra_svn_conn_t *conn, server_baton_t *b, svn_revnum_t rev, svn_revnum_t low_water_mark, svn_boolean_t send_deltas, apr_pool_t *pool) { const svn_delta_editor_t *editor; void *edit_baton; svn_fs_root_t *root; svn_error_t *err; authz_baton_t ab; ab.server = b; ab.conn = conn; SVN_ERR(log_command(b, conn, pool, svn_log__replay(b->repository->fs_path->data, rev, pool))); svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL); err = svn_fs_revision_root(&root, b->repository->fs, rev, pool); if (! err) err = svn_repos_replay2(root, b->repository->fs_path->data, low_water_mark, send_deltas, editor, edit_baton, authz_check_access_cb_func(b), &ab, pool); if (err) svn_error_clear(editor->abort_edit(edit_baton, pool)); SVN_CMD_ERR(err); return svn_ra_svn__write_cmd_finish_replay(conn, pool); } static svn_error_t * replay(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { svn_revnum_t rev, low_water_mark; svn_boolean_t send_deltas; server_baton_t *b = baton; SVN_ERR(svn_ra_svn__parse_tuple(params, "rrb", &rev, &low_water_mark, &send_deltas)); SVN_ERR(trivial_auth_request(conn, pool, b)); SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark, send_deltas, pool)); SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); return SVN_NO_ERROR; } static svn_error_t * replay_range(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { svn_revnum_t start_rev, end_rev, rev, low_water_mark; svn_boolean_t send_deltas; server_baton_t *b = baton; apr_pool_t *iterpool; authz_baton_t ab; ab.server = b; ab.conn = conn; SVN_ERR(svn_ra_svn__parse_tuple(params, "rrrb", &start_rev, &end_rev, &low_water_mark, &send_deltas)); SVN_ERR(trivial_auth_request(conn, pool, b)); iterpool = svn_pool_create(pool); for (rev = start_rev; rev <= end_rev; rev++) { apr_hash_t *props; svn_pool_clear(iterpool); SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repository->repos, rev, authz_check_access_cb_func(b), &ab, iterpool)); SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "w(!", "revprops")); SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, props)); SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!)")); SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark, send_deltas, iterpool)); } svn_pool_destroy(iterpool); SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); return SVN_NO_ERROR; } static svn_error_t * get_deleted_rev(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { server_baton_t *b = baton; const char *path, *full_path, *canonical_path; svn_revnum_t peg_revision; svn_revnum_t end_revision; svn_revnum_t revision_deleted; SVN_ERR(svn_ra_svn__parse_tuple(params, "crr", &path, &peg_revision, &end_revision)); SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, pool, pool)); full_path = svn_fspath__join(b->repository->fs_path->data, canonical_path, pool); SVN_ERR(log_command(b, conn, pool, "get-deleted-rev")); SVN_ERR(trivial_auth_request(conn, pool, b)); SVN_CMD_ERR(svn_repos_deleted_rev(b->repository->fs, full_path, peg_revision, end_revision, &revision_deleted, pool)); /* The protocol does not allow for a reply of SVN_INVALID_REVNUM directly. Instead, return SVN_ERR_ENTRY_MISSING_REVISION. A new enough client knows that this means the answer to the query is SVN_INVALID_REVNUM. (An older client reports this as an error.) */ if (revision_deleted == SVN_INVALID_REVNUM) SVN_CMD_ERR(svn_error_createf(SVN_ERR_ENTRY_MISSING_REVISION, NULL, "svn protocol command 'get-deleted-rev': " "path '%s' was not deleted in r%ld-%ld; " "NOTE: newer clients handle this case " "and do not report it as an error", full_path, peg_revision, end_revision)); SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", revision_deleted)); return SVN_NO_ERROR; } static svn_error_t * get_inherited_props(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { server_baton_t *b = baton; const char *path, *full_path, *canonical_path; svn_revnum_t rev; svn_fs_root_t *root; apr_array_header_t *inherited_props; int i; apr_pool_t *iterpool = svn_pool_create(pool); authz_baton_t ab; svn_node_kind_t node_kind; ab.server = b; ab.conn = conn; /* Parse arguments. */ SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)", &path, &rev)); SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, iterpool, iterpool)); full_path = svn_fspath__join(b->repository->fs_path->data, canonical_path, pool); /* Check authorizations */ SVN_ERR(must_have_access(conn, iterpool, b, svn_authz_read, full_path, FALSE)); SVN_ERR(log_command(b, conn, pool, "%s", svn_log__get_inherited_props(full_path, rev, iterpool))); /* Fetch the properties and a stream for the contents. */ SVN_CMD_ERR(svn_fs_revision_root(&root, b->repository->fs, rev, iterpool)); SVN_CMD_ERR(svn_fs_check_path(&node_kind, root, full_path, pool)); if (node_kind == svn_node_none) { SVN_CMD_ERR(svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, _("'%s' path not found"), full_path)); } SVN_CMD_ERR(get_props(NULL, &inherited_props, &ab, root, full_path, pool)); /* Send successful command response with revision and props. */ SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "w(!", "success")); SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(?!")); for (i = 0; i < inherited_props->nelts; i++) { svn_prop_inherited_item_t *iprop = APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *); svn_pool_clear(iterpool); SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!", iprop->path_or_url)); SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash)); SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!", iprop->path_or_url)); } SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))")); svn_pool_destroy(iterpool); return SVN_NO_ERROR; } /* Baton type to be used with list_receiver. */ typedef struct list_receiver_baton_t { /* Send the data through this connection. */ svn_ra_svn_conn_t *conn; /* Send the field selected by these flags. */ apr_uint32_t dirent_fields; } list_receiver_baton_t; /* Implements svn_repos_dirent_receiver_t, sending DIRENT and PATH to the * client. BATON must be a list_receiver_baton_t. */ static svn_error_t * list_receiver(const char *path, svn_dirent_t *dirent, void *baton, apr_pool_t *pool) { list_receiver_baton_t *b = baton; return svn_error_trace(svn_ra_svn__write_dirent(b->conn, pool, path, dirent, b->dirent_fields)); } static svn_error_t * list(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_ra_svn__list_t *params, void *baton) { server_baton_t *b = baton; const char *path, *full_path, *canonical_path; svn_revnum_t rev; svn_depth_t depth; apr_array_header_t *patterns = NULL; svn_fs_root_t *root; const char *depth_word; svn_boolean_t path_info_only; svn_ra_svn__list_t *dirent_fields_list = NULL; svn_ra_svn__list_t *patterns_list = NULL; int i; list_receiver_baton_t rb; svn_error_t *err, *write_err; authz_baton_t ab; ab.server = b; ab.conn = conn; /* Read the command parameters. */ SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)w?l?l", &path, &rev, &depth_word, &dirent_fields_list, &patterns_list)); rb.conn = conn; SVN_ERR(parse_dirent_fields(&rb.dirent_fields, dirent_fields_list)); depth = svn_depth_from_word(depth_word); SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, pool, pool)); full_path = svn_fspath__join(b->repository->fs_path->data, canonical_path, pool); /* Read the patterns list. */ if (patterns_list) { patterns = apr_array_make(pool, 0, sizeof(const char *)); for (i = 0; i < patterns_list->nelts; ++i) { svn_ra_svn__item_t *elt = &SVN_RA_SVN__LIST_ITEM(patterns_list, i); if (elt->kind != SVN_RA_SVN_STRING) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, "Pattern field not a string"); APR_ARRAY_PUSH(patterns, const char *) = elt->u.string.data; } } /* Check authorizations */ SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, full_path, FALSE)); if (!SVN_IS_VALID_REVNUM(rev)) SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool)); SVN_ERR(log_command(b, conn, pool, "%s", svn_log__list(full_path, rev, patterns, depth, rb.dirent_fields, pool))); /* Fetch the root of the appropriate revision. */ SVN_CMD_ERR(svn_fs_revision_root(&root, b->repository->fs, rev, pool)); /* Fetch the directory entries if requested and send them immediately. */ path_info_only = (rb.dirent_fields & ~SVN_DIRENT_KIND) == 0; err = svn_repos_list(root, full_path, patterns, depth, path_info_only, authz_check_access_cb_func(b), &ab, list_receiver, &rb, NULL, NULL, pool); /* Finish response. */ write_err = svn_ra_svn__write_word(conn, pool, "done"); if (write_err) { svn_error_clear(err); return write_err; } SVN_CMD_ERR(err); return svn_error_trace(svn_ra_svn__write_cmd_response(conn, pool, "")); } static const svn_ra_svn__cmd_entry_t main_commands[] = { { "reparent", reparent }, { "get-latest-rev", get_latest_rev }, { "get-dated-rev", get_dated_rev }, { "change-rev-prop", change_rev_prop }, { "change-rev-prop2",change_rev_prop2 }, { "rev-proplist", rev_proplist }, { "rev-prop", rev_prop }, { "commit", commit }, { "get-file", get_file }, { "get-dir", get_dir }, { "update", update }, { "switch", switch_cmd }, { "status", status }, { "diff", diff }, { "get-mergeinfo", get_mergeinfo }, { "log", log_cmd }, { "check-path", check_path }, { "stat", stat_cmd }, { "get-locations", get_locations }, { "get-location-segments", get_location_segments }, { "get-file-revs", get_file_revs }, { "lock", lock }, { "lock-many", lock_many }, { "unlock", unlock }, { "unlock-many", unlock_many }, { "get-lock", get_lock }, { "get-locks", get_locks }, { "replay", replay }, { "replay-range", replay_range }, { "get-deleted-rev", get_deleted_rev }, { "get-iprops", get_inherited_props }, { "list", list }, { NULL } }; /* Skip past the scheme part of a URL, including the tunnel specification * if present. Return NULL if the scheme part is invalid for ra_svn. */ static const char *skip_scheme_part(const char *url) { if (strncmp(url, "svn", 3) != 0) return NULL; url += 3; if (*url == '+') url += strcspn(url, ":"); if (strncmp(url, "://", 3) != 0) return NULL; return url + 3; } /* Check that PATH is a valid repository path, meaning it doesn't contain any '..' path segments. NOTE: This is similar to svn_path_is_backpath_present, but that function assumes the path separator is '/'. This function also checks for segments delimited by the local path separator. */ static svn_boolean_t repos_path_valid(const char *path) { const char *s = path; while (*s) { /* Scan for the end of the segment. */ while (*path && *path != '/' && *path != SVN_PATH_LOCAL_SEPARATOR) ++path; /* Check for '..'. */ #ifdef WIN32 /* On Windows, don't allow sequences of more than one character consisting of just dots and spaces. Win32 functions treat paths such as ".. " and "......." inconsistently. Make sure no one can escape out of the root. */ if (path - s >= 2 && strspn(s, ". ") == (size_t)(path - s)) return FALSE; #else /* ! WIN32 */ if (path - s == 2 && s[0] == '.' && s[1] == '.') return FALSE; #endif /* Skip all separators. */ while (*path && (*path == '/' || *path == SVN_PATH_LOCAL_SEPARATOR)) ++path; s = path; } return TRUE; } /* Look for the repository given by URL, using ROOT as the virtual * repository root. If we find one, fill in the repos, fs, repos_url, * and fs_path fields of REPOSITORY. VHOST and READ_ONLY flags are the * same as in the server baton. * * CONFIG_POOL shall be used to load config objects. * * Use SCRATCH_POOL for temporary allocations. * */ static svn_error_t * find_repos(const char *url, const char *root, svn_boolean_t vhost, svn_boolean_t read_only, svn_config_t *cfg, repository_t *repository, svn_repos__config_pool_t *config_pool, apr_hash_t *fs_config, svn_repos_authz_warning_func_t authz_warning_func, void *authz_warning_baton, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { const char *path, *full_path, *fs_path, *hooks_env, *canonical_path; const char *canonical_root; svn_stringbuf_t *url_buf; svn_boolean_t sasl_requested; /* Skip past the scheme and authority part. */ path = skip_scheme_part(url); if (path == NULL) return svn_error_createf(SVN_ERR_BAD_URL, NULL, "Non-svn URL passed to svn server: '%s'", url); if (! vhost) { path = strchr(path, '/'); if (path == NULL) path = ""; } SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, scratch_pool, scratch_pool)); path = svn_path_uri_decode(canonical_path, scratch_pool); /* Ensure that it isn't possible to escape the root by disallowing '..' segments. */ if (!repos_path_valid(path)) return svn_error_create(SVN_ERR_BAD_FILENAME, NULL, "Couldn't determine repository path"); /* Join the server-configured root with the client path. */ SVN_ERR(svn_dirent_canonicalize_safe(&canonical_root, NULL, root, scratch_pool, scratch_pool)); full_path = svn_dirent_join(canonical_root, path, scratch_pool); /* Search for a repository in the full path. */ repository->repos_root = svn_repos_find_root_path(full_path, result_pool); if (!repository->repos_root) return svn_error_createf(SVN_ERR_RA_SVN_REPOS_NOT_FOUND, NULL, "No repository found in '%s'", url); /* Open the repository and fill in b with the resulting information. */ SVN_ERR(svn_repos_open3(&repository->repos, repository->repos_root, fs_config, result_pool, scratch_pool)); SVN_ERR(svn_repos_remember_client_capabilities(repository->repos, repository->capabilities)); repository->fs = svn_repos_fs(repository->repos); fs_path = full_path + strlen(repository->repos_root); repository->fs_path = svn_stringbuf_create(*fs_path ? fs_path : "/", result_pool); url_buf = svn_stringbuf_create(url, result_pool); svn_path_remove_components(url_buf, svn_path_component_count(repository->fs_path->data)); repository->repos_url = url_buf->data; repository->authz_repos_name = svn_dirent_is_child(canonical_root, repository->repos_root, result_pool); if (repository->authz_repos_name == NULL) repository->repos_name = svn_dirent_basename(repository->repos_root, result_pool); else repository->repos_name = repository->authz_repos_name; repository->repos_name = svn_path_uri_encode(repository->repos_name, result_pool); /* If the svnserve configuration has not been loaded then load it from the * repository. */ if (NULL == cfg) { repository->base = svn_repos_conf_dir(repository->repos, result_pool); SVN_ERR(svn_repos__config_pool_get(&cfg, config_pool, svn_repos_svnserve_conf (repository->repos, result_pool), FALSE, repository->repos, result_pool)); } SVN_ERR(load_pwdb_config(repository, cfg, config_pool, result_pool)); SVN_ERR(load_authz_config(repository, repository->repos_root, cfg, authz_warning_func, authz_warning_baton, result_pool, scratch_pool)); /* Should we use Cyrus SASL? */ SVN_ERR(svn_config_get_bool(cfg, &sasl_requested, SVN_CONFIG_SECTION_SASL, SVN_CONFIG_OPTION_USE_SASL, FALSE)); if (sasl_requested) { #ifdef SVN_HAVE_SASL const char *val; repository->use_sasl = sasl_requested; svn_config_get(cfg, &val, SVN_CONFIG_SECTION_SASL, SVN_CONFIG_OPTION_MIN_SSF, "0"); SVN_ERR(svn_cstring_atoui(&repository->min_ssf, val)); svn_config_get(cfg, &val, SVN_CONFIG_SECTION_SASL, SVN_CONFIG_OPTION_MAX_SSF, "256"); SVN_ERR(svn_cstring_atoui(&repository->max_ssf, val)); #else /* !SVN_HAVE_SASL */ return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL, _("SASL requested but not compiled in; " "set '%s' to 'false' or recompile " "svnserve with SASL support"), SVN_CONFIG_OPTION_USE_SASL); #endif /* SVN_HAVE_SASL */ } else { repository->use_sasl = FALSE; } /* Use the repository UUID as the default realm. */ SVN_ERR(svn_fs_get_uuid(repository->fs, &repository->realm, scratch_pool)); svn_config_get(cfg, &repository->realm, SVN_CONFIG_SECTION_GENERAL, SVN_CONFIG_OPTION_REALM, repository->realm); repository->realm = apr_pstrdup(result_pool, repository->realm); /* Make sure it's possible for the client to authenticate. Note that this doesn't take into account any authz configuration read above, because we can't know about access it grants until paths are given by the client. */ set_access(repository, cfg, read_only); /* Configure hook script environment variables. */ svn_config_get(cfg, &hooks_env, SVN_CONFIG_SECTION_GENERAL, SVN_CONFIG_OPTION_HOOKS_ENV, NULL); if (hooks_env) hooks_env = svn_dirent_internal_style(hooks_env, scratch_pool); SVN_ERR(svn_repos_hooks_setenv(repository->repos, hooks_env, scratch_pool)); repository->hooks_env = apr_pstrdup(result_pool, hooks_env); return SVN_NO_ERROR; } /* Compute the authentication name EXTERNAL should be able to get, if any. */ static const char *get_tunnel_user(serve_params_t *params, apr_pool_t *pool) { /* Only offer EXTERNAL for connections tunneled over a login agent. */ if (!params->tunnel) return NULL; /* If a tunnel user was provided on the command line, use that. */ if (params->tunnel_user) return params->tunnel_user; return svn_user_get_name(pool); } static void fs_warning_func(void *baton, svn_error_t *err) { fs_warning_baton_t *b = baton; log_error(err, b->server); } /* Return the normalized repository-relative path for the given PATH * (may be a URL, full path or relative path) and fs contained in the * server baton BATON. Allocate the result in POOL. */ static const char * get_normalized_repo_rel_path(void *baton, const char *path, apr_pool_t *pool) { server_baton_t *sb = baton; if (svn_path_is_url(path)) { /* This is a copyfrom URL. */ path = svn_uri_skip_ancestor(sb->repository->repos_url, path, pool); path = svn_fspath__canonicalize(path, pool); } else { /* This is a base-relative path. */ if ((path)[0] != '/') /* Get an absolute path for use in the FS. */ path = svn_fspath__join(sb->repository->fs_path->data, path, pool); } return path; } /* Get the revision root for REVISION in fs given by server baton BATON * and return it in *FS_ROOT. Use HEAD if REVISION is SVN_INVALID_REVNUM. * Use POOL for allocations. */ static svn_error_t * get_revision_root(svn_fs_root_t **fs_root, void *baton, svn_revnum_t revision, apr_pool_t *pool) { server_baton_t *sb = baton; if (!SVN_IS_VALID_REVNUM(revision)) SVN_ERR(svn_fs_youngest_rev(&revision, sb->repository->fs, pool)); SVN_ERR(svn_fs_revision_root(fs_root, sb->repository->fs, revision, pool)); return SVN_NO_ERROR; } static svn_error_t * fetch_props_func(apr_hash_t **props, void *baton, const char *path, svn_revnum_t base_revision, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { svn_fs_root_t *fs_root; svn_error_t *err; path = get_normalized_repo_rel_path(baton, path, scratch_pool); SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool)); err = svn_fs_node_proplist(props, fs_root, path, result_pool); if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) { svn_error_clear(err); *props = apr_hash_make(result_pool); return SVN_NO_ERROR; } else if (err) return svn_error_trace(err); return SVN_NO_ERROR; } static svn_error_t * fetch_kind_func(svn_node_kind_t *kind, void *baton, const char *path, svn_revnum_t base_revision, apr_pool_t *scratch_pool) { svn_fs_root_t *fs_root; path = get_normalized_repo_rel_path(baton, path, scratch_pool); SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool)); SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool)); return SVN_NO_ERROR; } static svn_error_t * fetch_base_func(const char **filename, void *baton, const char *path, svn_revnum_t base_revision, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { svn_stream_t *contents; svn_stream_t *file_stream; const char *tmp_filename; svn_fs_root_t *fs_root; svn_error_t *err; path = get_normalized_repo_rel_path(baton, path, scratch_pool); SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool)); err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool); if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) { svn_error_clear(err); *filename = NULL; return SVN_NO_ERROR; } else if (err) return svn_error_trace(err); SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL, svn_io_file_del_on_pool_cleanup, scratch_pool, scratch_pool)); SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool)); *filename = apr_pstrdup(result_pool, tmp_filename); return SVN_NO_ERROR; } client_info_t * get_client_info(svn_ra_svn_conn_t *conn, serve_params_t *params, apr_pool_t *pool) { client_info_t *client_info = apr_pcalloc(pool, sizeof(*client_info)); client_info->tunnel = params->tunnel; client_info->tunnel_user = get_tunnel_user(params, pool); client_info->user = NULL; client_info->authz_user = NULL; client_info->remote_host = svn_ra_svn_conn_remote_host(conn); return client_info; } static void handle_authz_warning(void *baton, const svn_error_t *err, apr_pool_t *scratch_pool) { server_baton_t *const server_baton = baton; log_warning(err, server_baton); SVN_UNUSED(scratch_pool); } /* Construct the server baton for CONN using PARAMS and return it in *BATON. * It's lifetime is the same as that of CONN. SCRATCH_POOL */ static svn_error_t * construct_server_baton(server_baton_t **baton, svn_ra_svn_conn_t *conn, serve_params_t *params, apr_pool_t *scratch_pool) { svn_error_t *err; apr_uint64_t ver; const char *client_url, *ra_client_string, *client_string, *canonical_url; svn_ra_svn__list_t *caplist; apr_pool_t *conn_pool = svn_ra_svn__get_pool(conn); server_baton_t *b = apr_pcalloc(conn_pool, sizeof(*b)); fs_warning_baton_t *warn_baton; svn_stringbuf_t *cap_log = svn_stringbuf_create_empty(scratch_pool); b->repository = apr_pcalloc(conn_pool, sizeof(*b->repository)); b->repository->username_case = params->username_case; b->repository->base = params->base; b->repository->pwdb = NULL; b->repository->authzdb = NULL; b->repository->realm = NULL; b->repository->use_sasl = FALSE; b->read_only = params->read_only; b->pool = conn_pool; b->vhost = params->vhost; b->logger = params->logger; b->client_info = get_client_info(conn, params, conn_pool); /* Send greeting. We don't support version 1 any more, so we can * send an empty mechlist. */ if (params->compression_level > 0) SVN_ERR(svn_ra_svn__write_cmd_response(conn, scratch_pool, "nn()(wwwwwwwwwwwww)", (apr_uint64_t) 2, (apr_uint64_t) 2, SVN_RA_SVN_CAP_EDIT_PIPELINE, SVN_RA_SVN_CAP_SVNDIFF1, SVN_RA_SVN_CAP_SVNDIFF2_ACCEPTED, SVN_RA_SVN_CAP_ABSENT_ENTRIES, SVN_RA_SVN_CAP_COMMIT_REVPROPS, SVN_RA_SVN_CAP_DEPTH, SVN_RA_SVN_CAP_LOG_REVPROPS, SVN_RA_SVN_CAP_ATOMIC_REVPROPS, SVN_RA_SVN_CAP_PARTIAL_REPLAY, SVN_RA_SVN_CAP_INHERITED_PROPS, SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS, SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE, SVN_RA_SVN_CAP_LIST )); else SVN_ERR(svn_ra_svn__write_cmd_response(conn, scratch_pool, "nn()(wwwwwwwwwww)", (apr_uint64_t) 2, (apr_uint64_t) 2, SVN_RA_SVN_CAP_EDIT_PIPELINE, SVN_RA_SVN_CAP_ABSENT_ENTRIES, SVN_RA_SVN_CAP_COMMIT_REVPROPS, SVN_RA_SVN_CAP_DEPTH, SVN_RA_SVN_CAP_LOG_REVPROPS, SVN_RA_SVN_CAP_ATOMIC_REVPROPS, SVN_RA_SVN_CAP_PARTIAL_REPLAY, SVN_RA_SVN_CAP_INHERITED_PROPS, SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS, SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE, SVN_RA_SVN_CAP_LIST )); /* Read client response, which we assume to be in version 2 format: * version, capability list, and client URL; then we do an auth * request. */ SVN_ERR(svn_ra_svn__read_tuple(conn, scratch_pool, "nlc?c(?c)", &ver, &caplist, &client_url, &ra_client_string, &client_string)); if (ver != 2) return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL, "Unsupported ra_svn protocol version" " %"APR_UINT64_T_FMT " (supported versions: [2])", ver); SVN_ERR(svn_uri_canonicalize_safe(&canonical_url, NULL, client_url, conn_pool, scratch_pool)); client_url = canonical_url; SVN_ERR(svn_ra_svn__set_capabilities(conn, caplist)); /* All released versions of Subversion support edit-pipeline, * so we do not accept connections from clients that do not. */ if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE)) return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL, "Missing edit-pipeline capability"); /* find_repos needs the capabilities as a list of words (eventually they get handed to the start-commit hook). While we could add a new interface to re-retrieve them from conn and convert the result to a list, it's simpler to just convert caplist by hand here, since we already have it and turning 'svn_ra_svn__item_t's into 'const char *'s is pretty easy. We only record capabilities we care about. The client may report more (because it doesn't know what the server cares about). */ { int i; svn_ra_svn__item_t *item; b->repository->capabilities = apr_array_make(conn_pool, 1, sizeof(const char *)); for (i = 0; i < caplist->nelts; i++) { static const svn_string_t str_cap_mergeinfo = SVN__STATIC_STRING(SVN_RA_SVN_CAP_MERGEINFO); item = &SVN_RA_SVN__LIST_ITEM(caplist, i); /* ra_svn_set_capabilities() already type-checked for us */ if (svn_string_compare(&item->u.word, &str_cap_mergeinfo)) { APR_ARRAY_PUSH(b->repository->capabilities, const char *) = SVN_RA_CAPABILITY_MERGEINFO; } /* Save for operational log. */ if (cap_log->len > 0) svn_stringbuf_appendcstr(cap_log, " "); svn_stringbuf_appendcstr(cap_log, item->u.word.data); } } /* (*b) has the logger, repository and client_info set, so it can be used as the authz_warning_baton that eventyally gets passed to log_warning(). */ err = handle_config_error(find_repos(client_url, params->root, b->vhost, b->read_only, params->cfg, b->repository, params->config_pool, params->fs_config, handle_authz_warning, b, conn_pool, scratch_pool), b); if (!err) { if (b->repository->anon_access == NO_ACCESS && (b->repository->auth_access == NO_ACCESS || (!b->client_info->tunnel_user && !b->repository->pwdb && !b->repository->use_sasl))) err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, "No access allowed to this repository", b); } if (!err) { SVN_ERR(auth_request(conn, scratch_pool, b, READ_ACCESS, FALSE)); if (current_access(b) == NO_ACCESS) err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, "Not authorized for access", b); } if (err) { /* Report these errors to the client before closing the connection. */ err = svn_error_compose_create(err, svn_ra_svn__write_cmd_failure(conn, scratch_pool, err)); err = svn_error_compose_create(err, svn_ra_svn__flush(conn, scratch_pool)); return err; } SVN_ERR(svn_fs_get_uuid(b->repository->fs, &b->repository->uuid, conn_pool)); /* We can't claim mergeinfo capability until we know whether the repository supports mergeinfo (i.e., is not a 1.4 repository), but we don't get the repository url from the client until after we've already sent the initial list of server capabilities. So we list repository capabilities here, in our first response after the client has sent the url. */ { svn_boolean_t supports_mergeinfo; SVN_ERR(svn_repos_has_capability(b->repository->repos, &supports_mergeinfo, SVN_REPOS_CAPABILITY_MERGEINFO, scratch_pool)); SVN_ERR(svn_ra_svn__write_tuple(conn, scratch_pool, "w(cc(!", "success", b->repository->uuid, b->repository->repos_url)); if (supports_mergeinfo) SVN_ERR(svn_ra_svn__write_word(conn, scratch_pool, SVN_RA_SVN_CAP_MERGEINFO)); SVN_ERR(svn_ra_svn__write_tuple(conn, scratch_pool, "!))")); SVN_ERR(svn_ra_svn__flush(conn, scratch_pool)); } /* Log the open. */ if (ra_client_string == NULL || ra_client_string[0] == '\0') ra_client_string = "-"; else ra_client_string = svn_path_uri_encode(ra_client_string, scratch_pool); if (client_string == NULL || client_string[0] == '\0') client_string = "-"; else client_string = svn_path_uri_encode(client_string, scratch_pool); SVN_ERR(log_command(b, conn, scratch_pool, "open %" APR_UINT64_T_FMT " cap=(%s) %s %s %s", ver, cap_log->data, svn_path_uri_encode(b->repository->fs_path->data, scratch_pool), ra_client_string, client_string)); warn_baton = apr_pcalloc(conn_pool, sizeof(*warn_baton)); warn_baton->server = b; warn_baton->conn = conn; svn_fs_set_warning_func(b->repository->fs, fs_warning_func, warn_baton); /* Set up editor shims. */ { svn_delta_shim_callbacks_t *callbacks = svn_delta_shim_callbacks_default(conn_pool); callbacks->fetch_base_func = fetch_base_func; callbacks->fetch_props_func = fetch_props_func; callbacks->fetch_kind_func = fetch_kind_func; callbacks->fetch_baton = b; SVN_ERR(svn_ra_svn__set_shim_callbacks(conn, callbacks)); } *baton = b; return SVN_NO_ERROR; } svn_error_t * serve_interruptable(svn_boolean_t *terminate_p, connection_t *connection, svn_boolean_t (* is_busy)(connection_t *), apr_pool_t *pool) { svn_boolean_t terminate = FALSE; svn_error_t *err = NULL; const svn_ra_svn__cmd_entry_t *command; apr_pool_t *iterpool = svn_pool_create(pool); /* Prepare command parser. */ apr_hash_t *cmd_hash = apr_hash_make(pool); for (command = main_commands; command->cmdname; command++) svn_hash_sets(cmd_hash, command->cmdname, command); /* Auto-initialize connection */ if (! connection->conn) { apr_status_t ar; /* Enable TCP keep-alives on the socket so we time out when * the connection breaks due to network-layer problems. * If the peer has dropped the connection due to a network partition * or a crash, or if the peer no longer considers the connection * valid because we are behind a NAT and our public IP has changed, * it will respond to the keep-alive probe with a RST instead of an * acknowledgment segment, which will cause svn to abort the session * even while it is currently blocked waiting for data from the peer. */ ar = apr_socket_opt_set(connection->usock, APR_SO_KEEPALIVE, 1); if (ar) { /* It's not a fatal error if we cannot enable keep-alives. */ } /* create the connection, configure ports etc. */ connection->conn = svn_ra_svn_create_conn5(connection->usock, NULL, NULL, connection->params->compression_level, connection->params->zero_copy_limit, connection->params->error_check_interval, connection->params->max_request_size, connection->params->max_response_size, connection->pool); /* Construct server baton and open the repository for the first time. */ err = construct_server_baton(&connection->baton, connection->conn, connection->params, pool); } /* If we can't access the repo for some reason, end this connection. */ if (err) terminate = TRUE; /* Process incoming commands. */ while (!terminate && !err) { svn_pool_clear(iterpool); if (is_busy && is_busy(connection)) { svn_boolean_t has_command; /* If the server is busy, execute just one command and only if * there is one currently waiting in our receive buffers. */ err = svn_ra_svn__has_command(&has_command, &terminate, connection->conn, iterpool); if (!err && has_command) err = svn_ra_svn__handle_command(&terminate, cmd_hash, connection->baton, connection->conn, FALSE, iterpool); break; } else { /* The server is not busy, thus let's serve whichever command * comes in next and whenever it comes in. This requires the * busy() callback test to return TRUE while there are still some * resources left. */ err = svn_ra_svn__handle_command(&terminate, cmd_hash, connection->baton, connection->conn, FALSE, iterpool); } } /* error or normal end of session. Close the connection */ svn_pool_destroy(iterpool); if (terminate_p) *terminate_p = terminate; return svn_error_trace(err); } svn_error_t *serve(svn_ra_svn_conn_t *conn, serve_params_t *params, apr_pool_t *pool) { server_baton_t *baton = NULL; SVN_ERR(construct_server_baton(&baton, conn, params, pool)); return svn_ra_svn__handle_commands2(conn, pool, main_commands, baton, FALSE); }