1/* 2 * serve.c : Functions for serving the Subversion protocol 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24 25 26 27#include <limits.h> /* for UINT_MAX */ 28#include <stdarg.h> 29 30#define APR_WANT_STRFUNC 31#include <apr_want.h> 32#include <apr_general.h> 33#include <apr_lib.h> 34#include <apr_strings.h> 35 36#include "svn_compat.h" 37#include "svn_private_config.h" /* For SVN_PATH_LOCAL_SEPARATOR */ 38#include "svn_hash.h" 39#include "svn_types.h" 40#include "svn_string.h" 41#include "svn_pools.h" 42#include "svn_error.h" 43#include "svn_ra.h" /* for SVN_RA_CAPABILITY_* */ 44#include "svn_ra_svn.h" 45#include "svn_repos.h" 46#include "svn_dirent_uri.h" 47#include "svn_path.h" 48#include "svn_time.h" 49#include "svn_config.h" 50#include "svn_props.h" 51#include "svn_mergeinfo.h" 52#include "svn_user.h" 53 54#include "private/svn_log.h" 55#include "private/svn_mergeinfo_private.h" 56#include "private/svn_ra_svn_private.h" 57#include "private/svn_fspath.h" 58 59#ifdef HAVE_UNISTD_H 60#include <unistd.h> /* For getpid() */ 61#endif 62 63#include "server.h" 64#include "logger.h" 65 66typedef struct commit_callback_baton_t { 67 apr_pool_t *pool; 68 svn_revnum_t *new_rev; 69 const char **date; 70 const char **author; 71 const char **post_commit_err; 72} commit_callback_baton_t; 73 74typedef struct report_driver_baton_t { 75 server_baton_t *sb; 76 const char *repos_url; /* Decoded repository URL. */ 77 void *report_baton; 78 svn_error_t *err; 79 /* so update() can distinguish checkout from update in logging */ 80 int entry_counter; 81 svn_boolean_t only_empty_entries; 82 /* for diff() logging */ 83 svn_revnum_t *from_rev; 84} report_driver_baton_t; 85 86typedef struct log_baton_t { 87 const char *fs_path; 88 svn_ra_svn_conn_t *conn; 89 int stack_depth; 90 91 /* Set to TRUE when at least one changed path has been sent. */ 92 svn_boolean_t started; 93} log_baton_t; 94 95typedef struct file_revs_baton_t { 96 svn_ra_svn_conn_t *conn; 97 apr_pool_t *pool; /* Pool provided in the handler call. */ 98} file_revs_baton_t; 99 100typedef struct fs_warning_baton_t { 101 server_baton_t *server; 102 svn_ra_svn_conn_t *conn; 103} fs_warning_baton_t; 104 105typedef struct authz_baton_t { 106 server_baton_t *server; 107 svn_ra_svn_conn_t *conn; 108} authz_baton_t; 109 110/* Log an error. */ 111static void 112log_error(const svn_error_t *err, server_baton_t *server) 113{ 114 logger__log_error(server->logger, err, server->repository, 115 server->client_info); 116} 117 118/* Log a warning. */ 119static void 120log_warning(const svn_error_t *err, server_baton_t *server) 121{ 122 logger__log_warning(server->logger, err, server->repository, 123 server->client_info); 124} 125 126/* svn_error_create() a new error, log_server_error() it, and 127 return it. */ 128static svn_error_t * 129error_create_and_log(apr_status_t apr_err, svn_error_t *child, 130 const char *message, server_baton_t *server) 131{ 132 svn_error_t *err = svn_error_create(apr_err, child, message); 133 log_error(err, server); 134 return err; 135} 136 137/* Log a failure ERR, transmit ERR back to the client (as part of a 138 "failure" notification), consume ERR, and flush the connection. */ 139static svn_error_t * 140log_fail_and_flush(svn_error_t *err, server_baton_t *server, 141 svn_ra_svn_conn_t *conn, apr_pool_t *pool) 142{ 143 svn_error_t *io_err; 144 145 log_error(err, server); 146 io_err = svn_ra_svn__write_cmd_failure(conn, pool, err); 147 svn_error_clear(err); 148 SVN_ERR(io_err); 149 return svn_ra_svn__flush(conn, pool); 150} 151 152/* Log a client command. */ 153static svn_error_t *log_command(server_baton_t *b, 154 svn_ra_svn_conn_t *conn, 155 apr_pool_t *pool, 156 const char *fmt, ...) 157{ 158 const char *remote_host, *timestr, *log, *line; 159 va_list ap; 160 apr_size_t nbytes; 161 162 if (b->logger == NULL) 163 return SVN_NO_ERROR; 164 165 remote_host = svn_ra_svn_conn_remote_host(conn); 166 timestr = svn_time_to_cstring(apr_time_now(), pool); 167 168 va_start(ap, fmt); 169 log = apr_pvsprintf(pool, fmt, ap); 170 va_end(ap); 171 172 line = apr_psprintf(pool, "%" APR_PID_T_FMT 173 " %s %s %s %s %s" APR_EOL_STR, 174 getpid(), timestr, 175 (remote_host ? remote_host : "-"), 176 (b->client_info->user ? b->client_info->user : "-"), 177 b->repository->repos_name, log); 178 nbytes = strlen(line); 179 180 return logger__write(b->logger, line, nbytes); 181} 182 183/* Log an authz failure */ 184static svn_error_t * 185log_authz_denied(const char *path, 186 svn_repos_authz_access_t required, 187 server_baton_t *b, 188 apr_pool_t *pool) 189{ 190 const char *timestr, *remote_host, *line; 191 192 if (!b->logger) 193 return SVN_NO_ERROR; 194 195 if (!b->client_info || !b->client_info->user) 196 return SVN_NO_ERROR; 197 198 timestr = svn_time_to_cstring(apr_time_now(), pool); 199 remote_host = b->client_info->remote_host; 200 201 line = apr_psprintf(pool, "%" APR_PID_T_FMT 202 " %s %s %s %s Authorization Failed %s%s %s" APR_EOL_STR, 203 getpid(), timestr, 204 (remote_host ? remote_host : "-"), 205 b->client_info->user, 206 b->repository->repos_name, 207 (required & svn_authz_recursive ? "recursive " : ""), 208 (required & svn_authz_write ? "write" : "read"), 209 (path && path[0] ? path : "/")); 210 211 return logger__write(b->logger, line, strlen(line)); 212} 213 214/* If CFG specifies a path to the password DB, read that DB through 215 * CONFIG_POOL and store it in REPOSITORY->PWDB. 216 */ 217static svn_error_t * 218load_pwdb_config(repository_t *repository, 219 svn_config_t *cfg, 220 svn_repos__config_pool_t *config_pool, 221 apr_pool_t *pool) 222{ 223 const char *pwdb_path; 224 svn_error_t *err; 225 226 svn_config_get(cfg, &pwdb_path, 227 SVN_CONFIG_SECTION_GENERAL, 228 SVN_CONFIG_OPTION_PASSWORD_DB, NULL); 229 230 repository->pwdb = NULL; 231 if (pwdb_path) 232 { 233 pwdb_path = svn_dirent_internal_style(pwdb_path, pool); 234 pwdb_path = svn_dirent_join(repository->base, pwdb_path, pool); 235 236 err = svn_repos__config_pool_get(&repository->pwdb, config_pool, 237 pwdb_path, TRUE, 238 repository->repos, pool); 239 if (err) 240 { 241 /* Because it may be possible to read the pwdb file with some 242 access methods and not others, ignore errors reading the pwdb 243 file and just don't present password authentication as an 244 option. Also, some authentications (e.g. --tunnel) can 245 proceed without it anyway. 246 247 ### Not entirely sure why SVN_ERR_BAD_FILENAME is checked 248 ### for here. That seems to have been introduced in r856914, 249 ### and only in r870942 was the APR_EACCES check introduced. */ 250 if (err->apr_err != SVN_ERR_BAD_FILENAME 251 && ! APR_STATUS_IS_EACCES(err->apr_err)) 252 { 253 return svn_error_create(SVN_ERR_AUTHN_FAILED, err, NULL); 254 } 255 else 256 /* Ignore SVN_ERR_BAD_FILENAME and APR_EACCES and proceed. */ 257 svn_error_clear(err); 258 } 259 } 260 261 return SVN_NO_ERROR; 262} 263 264/* Canonicalize *ACCESS_FILE based on the type of argument. Results are 265 * placed in *ACCESS_FILE. REPOSITORY is used to convert relative paths to 266 * absolute paths rooted at the server root. REPOS_ROOT is used to calculate 267 * an absolute URL for repos-relative URLs. */ 268static svn_error_t * 269canonicalize_access_file(const char **access_file, repository_t *repository, 270 const char *repos_root, apr_pool_t *pool) 271{ 272 if (svn_path_is_url(*access_file)) 273 { 274 const char *canonical_url; 275 SVN_ERR(svn_uri_canonicalize_safe(&canonical_url, NULL, *access_file, 276 pool, pool)); 277 *access_file = canonical_url; 278 } 279 else if (svn_path_is_repos_relative_url(*access_file)) 280 { 281 const char *repos_root_url; 282 const char *canonical_url; 283 284 SVN_ERR(svn_uri_get_file_url_from_dirent(&repos_root_url, repos_root, 285 pool)); 286 SVN_ERR(svn_path_resolve_repos_relative_url(access_file, *access_file, 287 repos_root_url, pool)); 288 SVN_ERR(svn_uri_canonicalize_safe(&canonical_url, NULL, *access_file, 289 pool, pool)); 290 *access_file = canonical_url; 291 } 292 else 293 { 294 *access_file = svn_dirent_internal_style(*access_file, pool); 295 *access_file = svn_dirent_join(repository->base, *access_file, pool); 296 } 297 298 return SVN_NO_ERROR; 299} 300 301/* Load the authz database for the listening server based on the entries 302 in the SERVER struct. 303 304 SERVER and CONN must not be NULL. The real errors will be logged with 305 SERVER and CONN but return generic errors to the client. */ 306static svn_error_t * 307load_authz_config(repository_t *repository, 308 const char *repos_root, 309 svn_config_t *cfg, 310 svn_repos_authz_warning_func_t warning_func, 311 void *warning_baton, 312 apr_pool_t *result_pool, 313 apr_pool_t *scratch_pool) 314{ 315 const char *authzdb_path; 316 const char *groupsdb_path; 317 svn_error_t *err; 318 319 /* Read authz configuration. */ 320 svn_config_get(cfg, &authzdb_path, SVN_CONFIG_SECTION_GENERAL, 321 SVN_CONFIG_OPTION_AUTHZ_DB, NULL); 322 323 svn_config_get(cfg, &groupsdb_path, SVN_CONFIG_SECTION_GENERAL, 324 SVN_CONFIG_OPTION_GROUPS_DB, NULL); 325 326 if (authzdb_path) 327 { 328 const char *case_force_val; 329 330 /* Canonicalize and add the base onto the authzdb_path (if needed). */ 331 err = canonicalize_access_file(&authzdb_path, repository, 332 repos_root, scratch_pool); 333 334 /* Same for the groupsdb_path if it is present. */ 335 if (groupsdb_path && !err) 336 err = canonicalize_access_file(&groupsdb_path, repository, 337 repos_root, scratch_pool); 338 339 if (!err) 340 err = svn_repos_authz_read4(&repository->authzdb, authzdb_path, 341 groupsdb_path, TRUE, repository->repos, 342 warning_func, warning_baton, 343 result_pool, scratch_pool); 344 345 if (err) 346 return svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, err, NULL); 347 348 /* Are we going to be case-normalizing usernames when we consult 349 * this authz file? */ 350 svn_config_get(cfg, &case_force_val, 351 SVN_CONFIG_SECTION_GENERAL, 352 SVN_CONFIG_OPTION_FORCE_USERNAME_CASE, NULL); 353 if (case_force_val) 354 { 355 if (strcmp(case_force_val, "upper") == 0) 356 repository->username_case = CASE_FORCE_UPPER; 357 else if (strcmp(case_force_val, "lower") == 0) 358 repository->username_case = CASE_FORCE_LOWER; 359 else 360 repository->username_case = CASE_ASIS; 361 } 362 } 363 else 364 { 365 repository->authzdb = NULL; 366 repository->username_case = CASE_ASIS; 367 } 368 369 return SVN_NO_ERROR; 370} 371 372/* If ERROR is a AUTH* error as returned by load_pwdb_config or 373 * load_authz_config, write it to SERVER's log file. 374 * Return a sanitized version of ERROR. 375 */ 376static svn_error_t * 377handle_config_error(svn_error_t *error, 378 server_baton_t *server) 379{ 380 if ( error 381 && ( error->apr_err == SVN_ERR_AUTHZ_INVALID_CONFIG 382 || error->apr_err == SVN_ERR_AUTHN_FAILED)) 383 { 384 apr_status_t apr_err = error->apr_err; 385 log_error(error, server); 386 387 /* Now that we've logged the error, clear it and return a 388 * nice, generic error to the user: 389 * https://issues.apache.org/jira/browse/SVN-2271 */ 390 svn_error_clear(error); 391 return svn_error_create(apr_err, NULL, NULL); 392 } 393 394 return error; 395} 396 397/* Set *FS_PATH to the portion of URL that is the path within the 398 repository, if URL is inside REPOS_URL (if URL is not inside 399 REPOS_URL, then error, with the effect on *FS_PATH undefined). 400 401 If the resultant fs path would be the empty string (i.e., URL and 402 REPOS_URL are the same), then set *FS_PATH to "/". 403 404 Assume that REPOS_URL and URL are already URI-decoded. */ 405static svn_error_t *get_fs_path(const char *repos_url, const char *url, 406 const char **fs_path) 407{ 408 apr_size_t len; 409 410 len = strlen(repos_url); 411 if (strncmp(url, repos_url, len) != 0) 412 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 413 "'%s' is not the same repository as '%s'", 414 url, repos_url); 415 *fs_path = url + len; 416 if (! **fs_path) 417 *fs_path = "/"; 418 419 return SVN_NO_ERROR; 420} 421 422/* --- AUTHENTICATION AND AUTHORIZATION FUNCTIONS --- */ 423 424/* Convert TEXT to upper case if TO_UPPERCASE is TRUE, else 425 converts it to lower case. */ 426static void convert_case(char *text, svn_boolean_t to_uppercase) 427{ 428 char *c = text; 429 while (*c) 430 { 431 *c = (char)(to_uppercase ? apr_toupper(*c) : apr_tolower(*c)); 432 ++c; 433 } 434} 435 436/* Set *ALLOWED to TRUE if PATH is accessible in the REQUIRED mode to 437 the user described in BATON according to the authz rules in BATON. 438 Use POOL for temporary allocations only. If no authz rules are 439 present in BATON, grant access by default. */ 440static svn_error_t *authz_check_access(svn_boolean_t *allowed, 441 const char *path, 442 svn_repos_authz_access_t required, 443 server_baton_t *b, 444 apr_pool_t *pool) 445{ 446 repository_t *repository = b->repository; 447 client_info_t *client_info = b->client_info; 448 449 /* If authz cannot be performed, grant access. This is NOT the same 450 as the default policy when authz is performed on a path with no 451 rules. In the latter case, the default is to deny access, and is 452 set by svn_repos_authz_check_access. */ 453 if (!repository->authzdb) 454 { 455 *allowed = TRUE; 456 return SVN_NO_ERROR; 457 } 458 459 /* If the authz request is for the empty path (ie. ""), replace it 460 with the root path. This happens because of stripping done at 461 various levels in svnserve that remove the leading / on an 462 absolute path. Passing such a malformed path to the authz 463 routines throws them into an infinite loop and makes them miss 464 ACLs. */ 465 if (path && *path != '/') 466 path = svn_fspath__canonicalize(path, pool); 467 468 /* If we have a username, and we've not yet used it + any username 469 case normalization that might be requested to determine "the 470 username we used for authz purposes", do so now. */ 471 if (client_info->user && (! client_info->authz_user)) 472 { 473 char *authz_user = apr_pstrdup(b->pool, client_info->user); 474 if (repository->username_case == CASE_FORCE_UPPER) 475 convert_case(authz_user, TRUE); 476 else if (repository->username_case == CASE_FORCE_LOWER) 477 convert_case(authz_user, FALSE); 478 479 client_info->authz_user = authz_user; 480 } 481 482 SVN_ERR(svn_repos_authz_check_access(repository->authzdb, 483 repository->authz_repos_name, 484 path, client_info->authz_user, 485 required, allowed, pool)); 486 if (!*allowed) 487 SVN_ERR(log_authz_denied(path, required, b, pool)); 488 489 return SVN_NO_ERROR; 490} 491 492/* Set *ALLOWED to TRUE if PATH is readable by the user described in 493 * BATON. Use POOL for temporary allocations only. ROOT is not used. 494 * Implements the svn_repos_authz_func_t interface. 495 */ 496static svn_error_t *authz_check_access_cb(svn_boolean_t *allowed, 497 svn_fs_root_t *root, 498 const char *path, 499 void *baton, 500 apr_pool_t *pool) 501{ 502 authz_baton_t *sb = baton; 503 504 return authz_check_access(allowed, path, svn_authz_read, 505 sb->server, pool); 506} 507 508/* If authz is enabled in the specified BATON, return a read authorization 509 function. Otherwise, return NULL. */ 510static svn_repos_authz_func_t authz_check_access_cb_func(server_baton_t *baton) 511{ 512 if (baton->repository->authzdb) 513 return authz_check_access_cb; 514 return NULL; 515} 516 517/* Set *ALLOWED to TRUE if the REQUIRED access to PATH is granted, 518 * according to the state in BATON. Use POOL for temporary 519 * allocations only. ROOT is not used. Implements the 520 * svn_repos_authz_callback_t interface. 521 */ 522static svn_error_t *authz_commit_cb(svn_repos_authz_access_t required, 523 svn_boolean_t *allowed, 524 svn_fs_root_t *root, 525 const char *path, 526 void *baton, 527 apr_pool_t *pool) 528{ 529 authz_baton_t *sb = baton; 530 531 return authz_check_access(allowed, path, required, sb->server, pool); 532} 533 534/* Return the access level specified for OPTION in CFG. If no such 535 * setting exists, use DEF. If READ_ONLY is set, unconditionally disable 536 * write access. 537 */ 538static enum access_type 539get_access(svn_config_t *cfg, 540 const char *option, 541 const char *def, 542 svn_boolean_t read_only) 543{ 544 enum access_type result; 545 const char *val; 546 547 svn_config_get(cfg, &val, SVN_CONFIG_SECTION_GENERAL, option, def); 548 result = (strcmp(val, "write") == 0 ? WRITE_ACCESS : 549 strcmp(val, "read") == 0 ? READ_ACCESS : NO_ACCESS); 550 551 return result == WRITE_ACCESS && read_only ? READ_ACCESS : result; 552} 553 554/* Set the *_ACCESS members in REPOSITORY according to the settings in 555 * CFG. If READ_ONLY is set, unconditionally disable write access. 556 */ 557static void 558set_access(repository_t *repository, 559 svn_config_t *cfg, 560 svn_boolean_t read_only) 561{ 562 repository->auth_access = get_access(cfg, SVN_CONFIG_OPTION_AUTH_ACCESS, 563 "write", read_only); 564 repository->anon_access = get_access(cfg, SVN_CONFIG_OPTION_ANON_ACCESS, 565 "read", read_only); 566} 567 568/* Return the access level for the user in B. 569 */ 570static enum access_type 571current_access(server_baton_t *b) 572{ 573 return b->client_info->user ? b->repository->auth_access 574 : b->repository->anon_access; 575} 576 577/* Send authentication mechs for ACCESS_TYPE to the client. If NEEDS_USERNAME 578 is true, don't send anonymous mech even if that would give the desired 579 access. */ 580static svn_error_t *send_mechs(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 581 server_baton_t *b, enum access_type required, 582 svn_boolean_t needs_username) 583{ 584 if (!needs_username && b->repository->anon_access >= required) 585 SVN_ERR(svn_ra_svn__write_word(conn, pool, "ANONYMOUS")); 586 if (b->client_info->tunnel_user && b->repository->auth_access >= required) 587 SVN_ERR(svn_ra_svn__write_word(conn, pool, "EXTERNAL")); 588 if (b->repository->pwdb && b->repository->auth_access >= required) 589 SVN_ERR(svn_ra_svn__write_word(conn, pool, "CRAM-MD5")); 590 return SVN_NO_ERROR; 591} 592 593/* Context for cleanup handler. */ 594struct cleanup_fs_access_baton 595{ 596 svn_fs_t *fs; 597 apr_pool_t *pool; 598}; 599 600/* Pool cleanup handler. Make sure fs's access_t points to NULL when 601 the command pool is destroyed. */ 602static apr_status_t cleanup_fs_access(void *data) 603{ 604 svn_error_t *serr; 605 struct cleanup_fs_access_baton *baton = data; 606 607 serr = svn_fs_set_access(baton->fs, NULL); 608 if (serr) 609 { 610 apr_status_t apr_err = serr->apr_err; 611 svn_error_clear(serr); 612 return apr_err; 613 } 614 615 return APR_SUCCESS; 616} 617 618 619/* Create an svn_fs_access_t in POOL for USER and associate it with 620 B's filesystem. Also, register a cleanup handler with POOL which 621 de-associates the svn_fs_access_t from B's filesystem. */ 622static svn_error_t * 623create_fs_access(server_baton_t *b, apr_pool_t *pool) 624{ 625 svn_fs_access_t *fs_access; 626 struct cleanup_fs_access_baton *cleanup_baton; 627 628 if (!b->client_info->user) 629 return SVN_NO_ERROR; 630 631 SVN_ERR(svn_fs_create_access(&fs_access, b->client_info->user, pool)); 632 SVN_ERR(svn_fs_set_access(b->repository->fs, fs_access)); 633 634 cleanup_baton = apr_pcalloc(pool, sizeof(*cleanup_baton)); 635 cleanup_baton->pool = pool; 636 cleanup_baton->fs = b->repository->fs; 637 apr_pool_cleanup_register(pool, cleanup_baton, cleanup_fs_access, 638 apr_pool_cleanup_null); 639 640 return SVN_NO_ERROR; 641} 642 643/* Authenticate, once the client has chosen a mechanism and possibly 644 * sent an initial mechanism token. On success, set *success to true 645 * and b->user to the authenticated username (or NULL for anonymous). 646 * On authentication failure, report failure to the client and set 647 * *success to FALSE. On communications failure, return an error. 648 * If NEEDS_USERNAME is TRUE, don't allow anonymous authentication. */ 649static svn_error_t *auth(svn_boolean_t *success, 650 svn_ra_svn_conn_t *conn, 651 const char *mech, const char *mecharg, 652 server_baton_t *b, enum access_type required, 653 svn_boolean_t needs_username, 654 apr_pool_t *scratch_pool) 655{ 656 const char *user; 657 *success = FALSE; 658 659 if (b->repository->auth_access >= required 660 && b->client_info->tunnel_user && strcmp(mech, "EXTERNAL") == 0) 661 { 662 if (*mecharg && strcmp(mecharg, b->client_info->tunnel_user) != 0) 663 return svn_ra_svn__write_tuple(conn, scratch_pool, "w(c)", "failure", 664 "Requested username does not match"); 665 b->client_info->user = b->client_info->tunnel_user; 666 SVN_ERR(svn_ra_svn__write_tuple(conn, scratch_pool, "w()", "success")); 667 *success = TRUE; 668 return SVN_NO_ERROR; 669 } 670 671 if (b->repository->anon_access >= required 672 && strcmp(mech, "ANONYMOUS") == 0 && ! needs_username) 673 { 674 SVN_ERR(svn_ra_svn__write_tuple(conn, scratch_pool, "w()", "success")); 675 *success = TRUE; 676 return SVN_NO_ERROR; 677 } 678 679 if (b->repository->auth_access >= required 680 && b->repository->pwdb && strcmp(mech, "CRAM-MD5") == 0) 681 { 682 SVN_ERR(svn_ra_svn_cram_server(conn, scratch_pool, b->repository->pwdb, 683 &user, success)); 684 b->client_info->user = apr_pstrdup(b->pool, user); 685 return SVN_NO_ERROR; 686 } 687 688 return svn_ra_svn__write_tuple(conn, scratch_pool, "w(c)", "failure", 689 "Must authenticate with listed mechanism"); 690} 691 692/* Perform an authentication request using the built-in SASL implementation. */ 693static svn_error_t * 694internal_auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 695 server_baton_t *b, enum access_type required, 696 svn_boolean_t needs_username) 697{ 698 svn_boolean_t success; 699 const char *mech, *mecharg; 700 apr_pool_t *iterpool; 701 702 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success")); 703 SVN_ERR(send_mechs(conn, pool, b, required, needs_username)); 704 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)c)", b->repository->realm)); 705 706 iterpool = svn_pool_create(pool); 707 do 708 { 709 svn_pool_clear(iterpool); 710 711 SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "w(?c)", &mech, &mecharg)); 712 if (!*mech) 713 break; 714 SVN_ERR(auth(&success, conn, mech, mecharg, b, required, 715 needs_username, iterpool)); 716 } 717 while (!success); 718 svn_pool_destroy(iterpool); 719 720 return SVN_NO_ERROR; 721} 722 723/* Perform an authentication request in order to get an access level of 724 * REQUIRED or higher. Since the client may escape the authentication 725 * exchange, the caller should check current_access(b) to see if 726 * authentication succeeded. */ 727static svn_error_t *auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 728 server_baton_t *b, enum access_type required, 729 svn_boolean_t needs_username) 730{ 731#ifdef SVN_HAVE_SASL 732 if (b->repository->use_sasl) 733 return cyrus_auth_request(conn, pool, b, required, needs_username); 734#endif 735 736 return internal_auth_request(conn, pool, b, required, needs_username); 737} 738 739/* Send a trivial auth notification on CONN which lists no mechanisms, 740 * indicating that authentication is unnecessary. Usually called in 741 * response to invocation of a svnserve command. 742 */ 743static svn_error_t *trivial_auth_request(svn_ra_svn_conn_t *conn, 744 apr_pool_t *pool, server_baton_t *b) 745{ 746 return svn_ra_svn__write_cmd_response(conn, pool, "()c", ""); 747} 748 749/* Ensure that the client has the REQUIRED access by checking the 750 * access directives (both blanket and per-directory) in BATON. If 751 * PATH is NULL, then only the blanket access configuration will 752 * impact the result. 753 * 754 * If NEEDS_USERNAME is TRUE, then a lookup is only successful if the 755 * user described in BATON is authenticated and, well, has a username 756 * assigned to him. 757 * 758 * Use POOL for temporary allocations only. 759 */ 760static svn_boolean_t lookup_access(apr_pool_t *pool, 761 server_baton_t *baton, 762 svn_repos_authz_access_t required, 763 const char *path, 764 svn_boolean_t needs_username) 765{ 766 enum access_type req = (required & svn_authz_write) ? 767 WRITE_ACCESS : READ_ACCESS; 768 svn_boolean_t authorized; 769 svn_error_t *err; 770 771 /* Get authz's opinion on the access. */ 772 err = authz_check_access(&authorized, path, required, baton, pool); 773 774 /* If an error made lookup fail, deny access. */ 775 if (err) 776 { 777 log_error(err, baton); 778 svn_error_clear(err); 779 return FALSE; 780 } 781 782 /* If the required access is blanket-granted AND granted by authz 783 AND we already have a username if one is required, then the 784 lookup has succeeded. */ 785 if (current_access(baton) >= req 786 && authorized 787 && (! needs_username || baton->client_info->user)) 788 return TRUE; 789 790 return FALSE; 791} 792 793/* Check that the client has the REQUIRED access by consulting the 794 * authentication and authorization states stored in BATON. If the 795 * client does not have the required access credentials, attempt to 796 * authenticate the client to get that access, using CONN for 797 * communication. 798 * 799 * This function is supposed to be called to handle the authentication 800 * half of a standard svn protocol reply. If an error is returned, it 801 * probably means that the server can terminate the client connection 802 * with an apologetic error, as it implies an authentication failure. 803 * 804 * PATH and NEEDS_USERNAME are passed along to lookup_access, their 805 * behaviour is documented there. 806 */ 807static svn_error_t *must_have_access(svn_ra_svn_conn_t *conn, 808 apr_pool_t *pool, 809 server_baton_t *b, 810 svn_repos_authz_access_t required, 811 const char *path, 812 svn_boolean_t needs_username) 813{ 814 enum access_type req = (required & svn_authz_write) ? 815 WRITE_ACCESS : READ_ACCESS; 816 817 /* See whether the user already has the required access. If so, 818 nothing needs to be done. Create the FS access and send a 819 trivial auth request. */ 820 if (lookup_access(pool, b, required, path, needs_username)) 821 { 822 SVN_ERR(create_fs_access(b, pool)); 823 return trivial_auth_request(conn, pool, b); 824 } 825 826 /* If the required blanket access can be obtained by authenticating, 827 try that. Unfortunately, we can't tell until after 828 authentication whether authz will work or not. We force 829 requiring a username because we need one to be able to check 830 authz configuration again with a different user credentials than 831 the first time round. */ 832 if (b->client_info->user == NULL 833 && b->repository->auth_access >= req 834 && (b->client_info->tunnel_user || b->repository->pwdb 835 || b->repository->use_sasl)) 836 SVN_ERR(auth_request(conn, pool, b, req, TRUE)); 837 838 /* Now that an authentication has been done get the new take of 839 authz on the request. */ 840 if (! lookup_access(pool, b, required, path, needs_username)) 841 return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR, 842 error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, 843 NULL, NULL, b), 844 NULL); 845 846 /* Else, access is granted, and there is much rejoicing. */ 847 SVN_ERR(create_fs_access(b, pool)); 848 849 return SVN_NO_ERROR; 850} 851 852/* --- REPORTER COMMAND SET --- */ 853 854/* To allow for pipelining, reporter commands have no reponses. If we 855 * get an error, we ignore all subsequent reporter commands and return 856 * the error finish_report, to be handled by the calling command. 857 */ 858 859static svn_error_t *set_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 860 svn_ra_svn__list_t *params, void *baton) 861{ 862 report_driver_baton_t *b = baton; 863 const char *path, *lock_token, *depth_word, *canonical_relpath; 864 svn_revnum_t rev; 865 /* Default to infinity, for old clients that don't send depth. */ 866 svn_depth_t depth = svn_depth_infinity; 867 svn_boolean_t start_empty; 868 869 SVN_ERR(svn_ra_svn__parse_tuple(params, "crb?(?c)?w", 870 &path, &rev, &start_empty, &lock_token, 871 &depth_word)); 872 if (depth_word) 873 depth = svn_depth_from_word(depth_word); 874 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_relpath, NULL, path, 875 pool, pool)); 876 path = canonical_relpath; 877 if (b->from_rev && strcmp(path, "") == 0) 878 *b->from_rev = rev; 879 if (!b->err) 880 b->err = svn_repos_set_path3(b->report_baton, path, rev, depth, 881 start_empty, lock_token, pool); 882 b->entry_counter++; 883 if (!start_empty) 884 b->only_empty_entries = FALSE; 885 return SVN_NO_ERROR; 886} 887 888static svn_error_t *delete_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 889 svn_ra_svn__list_t *params, void *baton) 890{ 891 report_driver_baton_t *b = baton; 892 const char *path, *canonical_relpath; 893 894 SVN_ERR(svn_ra_svn__parse_tuple(params, "c", &path)); 895 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_relpath, NULL, path, 896 pool, pool)); 897 path = canonical_relpath; 898 if (!b->err) 899 b->err = svn_repos_delete_path(b->report_baton, path, pool); 900 return SVN_NO_ERROR; 901} 902 903static svn_error_t *link_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 904 svn_ra_svn__list_t *params, void *baton) 905{ 906 report_driver_baton_t *b = baton; 907 const char *path, *url, *lock_token, *fs_path, *depth_word, *canonical_url; 908 const char *canonical_path; 909 svn_revnum_t rev; 910 svn_boolean_t start_empty; 911 /* Default to infinity, for old clients that don't send depth. */ 912 svn_depth_t depth = svn_depth_infinity; 913 914 SVN_ERR(svn_ra_svn__parse_tuple(params, "ccrb?(?c)?w", 915 &path, &url, &rev, &start_empty, 916 &lock_token, &depth_word)); 917 918 /* ### WHAT?! The link path is an absolute URL?! Didn't see that 919 coming... -- cmpilato */ 920 921 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, 922 pool, pool)); 923 path = canonical_path; 924 SVN_ERR(svn_uri_canonicalize_safe(&canonical_url, NULL, url, pool, pool)); 925 url = canonical_url; 926 if (depth_word) 927 depth = svn_depth_from_word(depth_word); 928 if (!b->err) 929 b->err = get_fs_path(svn_path_uri_decode(b->repos_url, pool), 930 svn_path_uri_decode(url, pool), 931 &fs_path); 932 if (!b->err) 933 b->err = svn_repos_link_path3(b->report_baton, path, fs_path, rev, 934 depth, start_empty, lock_token, pool); 935 b->entry_counter++; 936 return SVN_NO_ERROR; 937} 938 939static svn_error_t *finish_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 940 svn_ra_svn__list_t *params, void *baton) 941{ 942 report_driver_baton_t *b = baton; 943 944 /* No arguments to parse. */ 945 SVN_ERR(trivial_auth_request(conn, pool, b->sb)); 946 if (!b->err) 947 b->err = svn_repos_finish_report(b->report_baton, pool); 948 return SVN_NO_ERROR; 949} 950 951static svn_error_t * 952abort_report(svn_ra_svn_conn_t *conn, 953 apr_pool_t *pool, 954 svn_ra_svn__list_t *params, 955 void *baton) 956{ 957 report_driver_baton_t *b = baton; 958 959 /* No arguments to parse. */ 960 svn_error_clear(svn_repos_abort_report(b->report_baton, pool)); 961 return SVN_NO_ERROR; 962} 963 964static const svn_ra_svn__cmd_entry_t report_commands[] = { 965 { "set-path", set_path }, 966 { "delete-path", delete_path }, 967 { "link-path", link_path }, 968 { "finish-report", finish_report, NULL, TRUE }, 969 { "abort-report", abort_report, NULL, TRUE }, 970 { NULL } 971}; 972 973/* Accept a report from the client, drive the network editor with the 974 * result, and then write an empty command response. If there is a 975 * non-protocol failure, accept_report will abort the edit and return 976 * a command error to be reported by handle_commands(). 977 * 978 * If only_empty_entry is not NULL and the report contains only one 979 * item, and that item is empty, set *only_empty_entry to TRUE, else 980 * set it to FALSE. 981 * 982 * If from_rev is not NULL, set *from_rev to the revision number from 983 * the set-path on ""; if somehow set-path "" never happens, set 984 * *from_rev to SVN_INVALID_REVNUM. 985 */ 986static svn_error_t *accept_report(svn_boolean_t *only_empty_entry, 987 svn_revnum_t *from_rev, 988 svn_ra_svn_conn_t *conn, apr_pool_t *pool, 989 server_baton_t *b, svn_revnum_t rev, 990 const char *target, const char *tgt_path, 991 svn_boolean_t text_deltas, 992 svn_depth_t depth, 993 svn_boolean_t send_copyfrom_args, 994 svn_boolean_t ignore_ancestry) 995{ 996 const svn_delta_editor_t *editor; 997 void *edit_baton, *report_baton; 998 report_driver_baton_t rb; 999 svn_error_t *err; 1000 authz_baton_t ab; 1001 1002 ab.server = b; 1003 ab.conn = conn; 1004 1005 /* Make an svn_repos report baton. Tell it to drive the network editor 1006 * when the report is complete. */ 1007 svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL); 1008 SVN_CMD_ERR(svn_repos_begin_report3(&report_baton, rev, 1009 b->repository->repos, 1010 b->repository->fs_path->data, target, 1011 tgt_path, text_deltas, depth, 1012 ignore_ancestry, send_copyfrom_args, 1013 editor, edit_baton, 1014 authz_check_access_cb_func(b), 1015 &ab, svn_ra_svn_zero_copy_limit(conn), 1016 pool)); 1017 1018 rb.sb = b; 1019 rb.repos_url = svn_path_uri_decode(b->repository->repos_url, pool); 1020 rb.report_baton = report_baton; 1021 rb.err = NULL; 1022 rb.entry_counter = 0; 1023 rb.only_empty_entries = TRUE; 1024 rb.from_rev = from_rev; 1025 if (from_rev) 1026 *from_rev = SVN_INVALID_REVNUM; 1027 err = svn_ra_svn__handle_commands2(conn, pool, report_commands, &rb, TRUE); 1028 if (err) 1029 { 1030 /* Network or protocol error while handling commands. */ 1031 svn_error_clear(rb.err); 1032 return err; 1033 } 1034 else if (rb.err) 1035 { 1036 /* Some failure during the reporting or editing operations. */ 1037 SVN_CMD_ERR(rb.err); 1038 } 1039 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 1040 1041 if (only_empty_entry) 1042 *only_empty_entry = rb.entry_counter == 1 && rb.only_empty_entries; 1043 1044 return SVN_NO_ERROR; 1045} 1046 1047/* --- MAIN COMMAND SET --- */ 1048 1049/* Write out a list of property diffs. PROPDIFFS is an array of svn_prop_t 1050 * values. */ 1051static svn_error_t *write_prop_diffs(svn_ra_svn_conn_t *conn, 1052 apr_pool_t *pool, 1053 const apr_array_header_t *propdiffs) 1054{ 1055 int i; 1056 1057 for (i = 0; i < propdiffs->nelts; ++i) 1058 { 1059 const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t); 1060 1061 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "c(?s)", 1062 prop->name, prop->value)); 1063 } 1064 1065 return SVN_NO_ERROR; 1066} 1067 1068/* Write out a lock to the client. */ 1069static svn_error_t *write_lock(svn_ra_svn_conn_t *conn, 1070 apr_pool_t *pool, 1071 const svn_lock_t *lock) 1072{ 1073 const char *cdate, *edate; 1074 1075 cdate = svn_time_to_cstring(lock->creation_date, pool); 1076 edate = lock->expiration_date 1077 ? svn_time_to_cstring(lock->expiration_date, pool) : NULL; 1078 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "ccc(?c)c(?c)", lock->path, 1079 lock->token, lock->owner, lock->comment, 1080 cdate, edate)); 1081 1082 return SVN_NO_ERROR; 1083} 1084 1085/* ### This really belongs in libsvn_repos. */ 1086/* Get the explicit properties and/or inherited properties for a PATH in 1087 ROOT, with hardcoded committed-info values. */ 1088static svn_error_t * 1089get_props(apr_hash_t **props, 1090 apr_array_header_t **iprops, 1091 authz_baton_t *b, 1092 svn_fs_root_t *root, 1093 const char *path, 1094 apr_pool_t *pool) 1095{ 1096 /* Get the explicit properties. */ 1097 if (props) 1098 { 1099 svn_string_t *str; 1100 svn_revnum_t crev; 1101 const char *cdate, *cauthor, *uuid; 1102 1103 SVN_ERR(svn_fs_node_proplist(props, root, path, pool)); 1104 1105 /* Hardcode the values for the committed revision, date, and author. */ 1106 SVN_ERR(svn_repos_get_committed_info(&crev, &cdate, &cauthor, root, 1107 path, pool)); 1108 str = svn_string_createf(pool, "%ld", crev); 1109 svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_REV, str); 1110 str = (cdate) ? svn_string_create(cdate, pool) : NULL; 1111 svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_DATE, str); 1112 str = (cauthor) ? svn_string_create(cauthor, pool) : NULL; 1113 svn_hash_sets(*props, SVN_PROP_ENTRY_LAST_AUTHOR, str); 1114 1115 /* Hardcode the values for the UUID. */ 1116 SVN_ERR(svn_fs_get_uuid(svn_fs_root_fs(root), &uuid, pool)); 1117 str = (uuid) ? svn_string_create(uuid, pool) : NULL; 1118 svn_hash_sets(*props, SVN_PROP_ENTRY_UUID, str); 1119 } 1120 1121 /* Get any inherited properties the user is authorized to. */ 1122 if (iprops) 1123 { 1124 SVN_ERR(svn_repos_fs_get_inherited_props( 1125 iprops, root, path, NULL, 1126 authz_check_access_cb_func(b->server), 1127 b, pool, pool)); 1128 } 1129 1130 return SVN_NO_ERROR; 1131} 1132 1133/* Set BATON->FS_PATH for the repository URL found in PARAMS. */ 1134static svn_error_t * 1135reparent(svn_ra_svn_conn_t *conn, 1136 apr_pool_t *pool, 1137 svn_ra_svn__list_t *params, 1138 void *baton) 1139{ 1140 server_baton_t *b = baton; 1141 const char *url, *canonical_url; 1142 const char *fs_path; 1143 1144 SVN_ERR(svn_ra_svn__parse_tuple(params, "c", &url)); 1145 SVN_ERR(svn_uri_canonicalize_safe(&canonical_url, NULL, url, pool, pool)); 1146 url = canonical_url; 1147 SVN_ERR(trivial_auth_request(conn, pool, b)); 1148 SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repository->repos_url, pool), 1149 svn_path_uri_decode(url, pool), 1150 &fs_path)); 1151 SVN_ERR(log_command(b, conn, pool, "%s", svn_log__reparent(fs_path, pool))); 1152 svn_stringbuf_set(b->repository->fs_path, fs_path); 1153 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 1154 return SVN_NO_ERROR; 1155} 1156 1157static svn_error_t * 1158get_latest_rev(svn_ra_svn_conn_t *conn, 1159 apr_pool_t *pool, 1160 svn_ra_svn__list_t *params, 1161 void *baton) 1162{ 1163 server_baton_t *b = baton; 1164 svn_revnum_t rev; 1165 1166 SVN_ERR(log_command(b, conn, pool, "get-latest-rev")); 1167 1168 SVN_ERR(trivial_auth_request(conn, pool, b)); 1169 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool)); 1170 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", rev)); 1171 return SVN_NO_ERROR; 1172} 1173 1174static svn_error_t * 1175get_dated_rev(svn_ra_svn_conn_t *conn, 1176 apr_pool_t *pool, 1177 svn_ra_svn__list_t *params, 1178 void *baton) 1179{ 1180 server_baton_t *b = baton; 1181 svn_revnum_t rev; 1182 apr_time_t tm; 1183 const char *timestr; 1184 1185 SVN_ERR(svn_ra_svn__parse_tuple(params, "c", ×tr)); 1186 SVN_ERR(log_command(b, conn, pool, "get-dated-rev %s", timestr)); 1187 1188 SVN_ERR(trivial_auth_request(conn, pool, b)); 1189 SVN_CMD_ERR(svn_time_from_cstring(&tm, timestr, pool)); 1190 SVN_CMD_ERR(svn_repos_dated_revision(&rev, b->repository->repos, tm, pool)); 1191 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", rev)); 1192 return SVN_NO_ERROR; 1193} 1194 1195/* Common logic for change_rev_prop() and change_rev_prop2(). */ 1196static svn_error_t *do_change_rev_prop(svn_ra_svn_conn_t *conn, 1197 server_baton_t *b, 1198 svn_revnum_t rev, 1199 const char *name, 1200 const svn_string_t *const *old_value_p, 1201 const svn_string_t *value, 1202 apr_pool_t *pool) 1203{ 1204 authz_baton_t ab; 1205 1206 ab.server = b; 1207 ab.conn = conn; 1208 1209 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, FALSE)); 1210 SVN_ERR(log_command(b, conn, pool, "%s", 1211 svn_log__change_rev_prop(rev, name, pool))); 1212 SVN_CMD_ERR(svn_repos_fs_change_rev_prop4(b->repository->repos, rev, 1213 b->client_info->user, 1214 name, old_value_p, value, 1215 TRUE, TRUE, 1216 authz_check_access_cb_func(b), &ab, 1217 pool)); 1218 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 1219 1220 return SVN_NO_ERROR; 1221} 1222 1223static svn_error_t * 1224change_rev_prop2(svn_ra_svn_conn_t *conn, 1225 apr_pool_t *pool, 1226 svn_ra_svn__list_t *params, 1227 void *baton) 1228{ 1229 server_baton_t *b = baton; 1230 svn_revnum_t rev; 1231 const char *name; 1232 svn_string_t *value; 1233 const svn_string_t *const *old_value_p; 1234 svn_string_t *old_value; 1235 svn_boolean_t dont_care; 1236 1237 SVN_ERR(svn_ra_svn__parse_tuple(params, "rc(?s)(b?s)", 1238 &rev, &name, &value, 1239 &dont_care, &old_value)); 1240 1241 /* Argument parsing. */ 1242 if (dont_care) 1243 old_value_p = NULL; 1244 else 1245 old_value_p = (const svn_string_t *const *)&old_value; 1246 1247 /* Input validation. */ 1248 if (dont_care && old_value) 1249 { 1250 svn_error_t *err; 1251 err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, 1252 "'previous-value' and 'dont-care' cannot both be " 1253 "set in 'change-rev-prop2' request"); 1254 return log_fail_and_flush(err, b, conn, pool); 1255 } 1256 1257 /* Do it. */ 1258 SVN_ERR(do_change_rev_prop(conn, b, rev, name, old_value_p, value, pool)); 1259 1260 return SVN_NO_ERROR; 1261} 1262 1263static svn_error_t * 1264change_rev_prop(svn_ra_svn_conn_t *conn, 1265 apr_pool_t *pool, 1266 svn_ra_svn__list_t *params, 1267 void *baton) 1268{ 1269 server_baton_t *b = baton; 1270 svn_revnum_t rev; 1271 const char *name; 1272 svn_string_t *value; 1273 1274 /* Because the revprop value was at one time mandatory, the usual 1275 optional element pattern "(?s)" isn't used. */ 1276 SVN_ERR(svn_ra_svn__parse_tuple(params, "rc?s", &rev, &name, &value)); 1277 1278 SVN_ERR(do_change_rev_prop(conn, b, rev, name, NULL, value, pool)); 1279 1280 return SVN_NO_ERROR; 1281} 1282 1283static svn_error_t * 1284rev_proplist(svn_ra_svn_conn_t *conn, 1285 apr_pool_t *pool, 1286 svn_ra_svn__list_t *params, 1287 void *baton) 1288{ 1289 server_baton_t *b = baton; 1290 svn_revnum_t rev; 1291 apr_hash_t *props; 1292 authz_baton_t ab; 1293 1294 ab.server = b; 1295 ab.conn = conn; 1296 1297 SVN_ERR(svn_ra_svn__parse_tuple(params, "r", &rev)); 1298 SVN_ERR(log_command(b, conn, pool, "%s", svn_log__rev_proplist(rev, pool))); 1299 1300 SVN_ERR(trivial_auth_request(conn, pool, b)); 1301 SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repository->repos, 1302 rev, 1303 authz_check_access_cb_func(b), 1304 &ab, pool)); 1305 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success")); 1306 SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props)); 1307 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 1308 return SVN_NO_ERROR; 1309} 1310 1311static svn_error_t * 1312rev_prop(svn_ra_svn_conn_t *conn, 1313 apr_pool_t *pool, 1314 svn_ra_svn__list_t *params, 1315 void *baton) 1316{ 1317 server_baton_t *b = baton; 1318 svn_revnum_t rev; 1319 const char *name; 1320 svn_string_t *value; 1321 authz_baton_t ab; 1322 1323 ab.server = b; 1324 ab.conn = conn; 1325 1326 SVN_ERR(svn_ra_svn__parse_tuple(params, "rc", &rev, &name)); 1327 SVN_ERR(log_command(b, conn, pool, "%s", 1328 svn_log__rev_prop(rev, name, pool))); 1329 1330 SVN_ERR(trivial_auth_request(conn, pool, b)); 1331 SVN_CMD_ERR(svn_repos_fs_revision_prop(&value, b->repository->repos, rev, 1332 name, authz_check_access_cb_func(b), 1333 &ab, pool)); 1334 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "(?s)", value)); 1335 return SVN_NO_ERROR; 1336} 1337 1338static svn_error_t *commit_done(const svn_commit_info_t *commit_info, 1339 void *baton, apr_pool_t *pool) 1340{ 1341 commit_callback_baton_t *ccb = baton; 1342 1343 *ccb->new_rev = commit_info->revision; 1344 *ccb->date = commit_info->date 1345 ? apr_pstrdup(ccb->pool, commit_info->date): NULL; 1346 *ccb->author = commit_info->author 1347 ? apr_pstrdup(ccb->pool, commit_info->author) : NULL; 1348 *ccb->post_commit_err = commit_info->post_commit_err 1349 ? apr_pstrdup(ccb->pool, commit_info->post_commit_err) : NULL; 1350 return SVN_NO_ERROR; 1351} 1352 1353/* Add the LOCK_TOKENS (if any) to the filesystem access context, 1354 * checking path authorizations using the state in SB as we go. 1355 * LOCK_TOKENS is an array of svn_ra_svn__item_t structs. Return a 1356 * client error if LOCK_TOKENS is not a list of lists. If a lock 1357 * violates the authz configuration, return SVN_ERR_RA_NOT_AUTHORIZED 1358 * to the client. Use POOL for temporary allocations only. 1359 */ 1360static svn_error_t * 1361add_lock_tokens(const svn_ra_svn__list_t *lock_tokens, 1362 server_baton_t *sb, 1363 apr_pool_t *pool) 1364{ 1365 const char *canonical_path; 1366 int i; 1367 svn_fs_access_t *fs_access; 1368 1369 SVN_ERR(svn_fs_get_access(&fs_access, sb->repository->fs)); 1370 1371 /* If there is no access context, nowhere to add the tokens. */ 1372 if (! fs_access) 1373 return SVN_NO_ERROR; 1374 1375 for (i = 0; i < lock_tokens->nelts; ++i) 1376 { 1377 const char *path, *token, *full_path; 1378 svn_ra_svn__item_t *path_item, *token_item; 1379 svn_ra_svn__item_t *item = &SVN_RA_SVN__LIST_ITEM(lock_tokens, i); 1380 if (item->kind != SVN_RA_SVN_LIST) 1381 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1382 "Lock tokens aren't a list of lists"); 1383 1384 path_item = &SVN_RA_SVN__LIST_ITEM(&item->u.list, 0); 1385 if (path_item->kind != SVN_RA_SVN_STRING) 1386 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1387 "Lock path isn't a string"); 1388 1389 token_item = &SVN_RA_SVN__LIST_ITEM(&item->u.list, 1); 1390 if (token_item->kind != SVN_RA_SVN_STRING) 1391 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1392 "Lock token isn't a string"); 1393 1394 path = path_item->u.string.data; 1395 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, 1396 pool, pool)); 1397 full_path = svn_fspath__join(sb->repository->fs_path->data, 1398 canonical_path, pool); 1399 1400 if (! lookup_access(pool, sb, svn_authz_write, full_path, TRUE)) 1401 return error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, NULL, 1402 sb); 1403 1404 token = token_item->u.string.data; 1405 SVN_ERR(svn_fs_access_add_lock_token2(fs_access, path, token)); 1406 } 1407 1408 return SVN_NO_ERROR; 1409} 1410 1411/* Implements svn_fs_lock_callback_t. */ 1412static svn_error_t * 1413lock_cb(void *baton, 1414 const char *path, 1415 const svn_lock_t *lock, 1416 svn_error_t *fs_err, 1417 apr_pool_t *pool) 1418{ 1419 server_baton_t *sb = baton; 1420 1421 log_error(fs_err, sb); 1422 1423 return SVN_NO_ERROR; 1424} 1425 1426/* Unlock the paths with lock tokens in LOCK_TOKENS, ignoring any errors. 1427 LOCK_TOKENS contains svn_ra_svn__item_t elements, assumed to be lists. */ 1428static svn_error_t * 1429unlock_paths(const svn_ra_svn__list_t *lock_tokens, 1430 server_baton_t *sb, 1431 apr_pool_t *pool) 1432{ 1433 int i; 1434 apr_pool_t *subpool = svn_pool_create(pool); 1435 apr_hash_t *targets = apr_hash_make(subpool); 1436 const char *canonical_path; 1437 svn_error_t *err; 1438 1439 for (i = 0; i < lock_tokens->nelts; ++i) 1440 { 1441 svn_ra_svn__item_t *item, *path_item, *token_item; 1442 const char *path, *token, *full_path; 1443 1444 item = &SVN_RA_SVN__LIST_ITEM(lock_tokens, i); 1445 path_item = &SVN_RA_SVN__LIST_ITEM(&item->u.list, 0); 1446 token_item = &SVN_RA_SVN__LIST_ITEM(&item->u.list, 1); 1447 1448 path = path_item->u.string.data; 1449 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, 1450 subpool, subpool)); 1451 full_path = svn_fspath__join(sb->repository->fs_path->data, 1452 canonical_path, subpool); 1453 token = token_item->u.string.data; 1454 svn_hash_sets(targets, full_path, token); 1455 } 1456 1457 1458 /* The lock may have become defunct after the commit, so ignore such 1459 errors. */ 1460 err = svn_repos_fs_unlock_many(sb->repository->repos, targets, FALSE, 1461 lock_cb, sb, subpool, subpool); 1462 log_error(err, sb); 1463 svn_error_clear(err); 1464 1465 svn_pool_destroy(subpool); 1466 1467 return SVN_NO_ERROR; 1468} 1469 1470static svn_error_t * 1471commit(svn_ra_svn_conn_t *conn, 1472 apr_pool_t *pool, 1473 svn_ra_svn__list_t *params, 1474 void *baton) 1475{ 1476 server_baton_t *b = baton; 1477 const char *log_msg, 1478 *date = NULL, 1479 *author = NULL, 1480 *post_commit_err = NULL; 1481 svn_ra_svn__list_t *lock_tokens; 1482 svn_boolean_t keep_locks; 1483 svn_ra_svn__list_t *revprop_list; 1484 apr_hash_t *revprop_table; 1485 const svn_delta_editor_t *editor; 1486 void *edit_baton; 1487 svn_boolean_t aborted; 1488 commit_callback_baton_t ccb; 1489 svn_revnum_t new_rev; 1490 authz_baton_t ab; 1491 1492 ab.server = b; 1493 ab.conn = conn; 1494 1495 if (params->nelts == 1) 1496 { 1497 /* Clients before 1.2 don't send lock-tokens, keep-locks, 1498 and rev-props fields. */ 1499 SVN_ERR(svn_ra_svn__parse_tuple(params, "c", &log_msg)); 1500 lock_tokens = NULL; 1501 keep_locks = TRUE; 1502 revprop_list = NULL; 1503 } 1504 else 1505 { 1506 /* Clients before 1.5 don't send the rev-props field. */ 1507 SVN_ERR(svn_ra_svn__parse_tuple(params, "clb?l", &log_msg, 1508 &lock_tokens, &keep_locks, 1509 &revprop_list)); 1510 } 1511 1512 /* The handling for locks is a little problematic, because the 1513 protocol won't let us send several auth requests once one has 1514 succeeded. So we request write access and a username before 1515 adding tokens (if we have any), and subsequently fail if a lock 1516 violates authz. */ 1517 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, 1518 NULL, 1519 (lock_tokens && lock_tokens->nelts))); 1520 1521 /* Authorize the lock tokens and give them to the FS if we got 1522 any. */ 1523 if (lock_tokens && lock_tokens->nelts) 1524 SVN_CMD_ERR(add_lock_tokens(lock_tokens, b, pool)); 1525 1526 /* Ignore LOG_MSG, per the protocol. See ra_svn_commit(). */ 1527 if (revprop_list) 1528 SVN_ERR(svn_ra_svn__parse_proplist(revprop_list, pool, &revprop_table)); 1529 else 1530 { 1531 revprop_table = apr_hash_make(pool); 1532 svn_hash_sets(revprop_table, SVN_PROP_REVISION_LOG, 1533 svn_string_create(log_msg, pool)); 1534 } 1535 1536 /* Get author from the baton, making sure clients can't circumvent 1537 the authentication via the revision props. */ 1538 svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR, 1539 b->client_info->user 1540 ? svn_string_create(b->client_info->user, pool) 1541 : NULL); 1542 1543 ccb.pool = pool; 1544 ccb.new_rev = &new_rev; 1545 ccb.date = &date; 1546 ccb.author = &author; 1547 ccb.post_commit_err = &post_commit_err; 1548 /* ### Note that svn_repos_get_commit_editor5 actually wants a decoded URL. */ 1549 SVN_CMD_ERR(svn_repos_get_commit_editor5 1550 (&editor, &edit_baton, b->repository->repos, NULL, 1551 svn_path_uri_decode(b->repository->repos_url, pool), 1552 b->repository->fs_path->data, revprop_table, 1553 commit_done, &ccb, 1554 authz_commit_cb, &ab, pool)); 1555 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 1556 SVN_ERR(svn_ra_svn_drive_editor2(conn, pool, editor, edit_baton, 1557 &aborted, FALSE)); 1558 if (!aborted) 1559 { 1560 SVN_ERR(log_command(b, conn, pool, "%s", 1561 svn_log__commit(new_rev, pool))); 1562 SVN_ERR(trivial_auth_request(conn, pool, b)); 1563 1564 /* In tunnel mode, deltify before answering the client, because 1565 answering may cause the client to terminate the connection 1566 and thus kill the server. But otherwise, deltify after 1567 answering the client, to avoid user-visible delay. */ 1568 1569 if (b->client_info->tunnel) 1570 SVN_ERR(svn_fs_deltify_revision(b->repository->fs, new_rev, pool)); 1571 1572 /* Unlock the paths. */ 1573 if (! keep_locks && lock_tokens && lock_tokens->nelts) 1574 SVN_ERR(unlock_paths(lock_tokens, b, pool)); 1575 1576 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "r(?c)(?c)(?c)", 1577 new_rev, date, author, post_commit_err)); 1578 1579 if (! b->client_info->tunnel) 1580 SVN_ERR(svn_fs_deltify_revision(b->repository->fs, new_rev, pool)); 1581 } 1582 return SVN_NO_ERROR; 1583} 1584 1585static svn_error_t * 1586get_file(svn_ra_svn_conn_t *conn, 1587 apr_pool_t *pool, 1588 svn_ra_svn__list_t *params, 1589 void *baton) 1590{ 1591 server_baton_t *b = baton; 1592 const char *path, *full_path, *hex_digest, *canonical_path; 1593 svn_revnum_t rev; 1594 svn_fs_root_t *root; 1595 svn_stream_t *contents; 1596 apr_hash_t *props = NULL; 1597 apr_array_header_t *inherited_props; 1598 svn_string_t write_str; 1599 char buf[4096]; 1600 apr_size_t len; 1601 svn_boolean_t want_props, want_contents; 1602 apr_uint64_t wants_inherited_props; 1603 svn_checksum_t *checksum; 1604 svn_error_t *err, *write_err; 1605 int i; 1606 authz_baton_t ab; 1607 1608 ab.server = b; 1609 ab.conn = conn; 1610 1611 /* Parse arguments. */ 1612 SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)bb?B", &path, &rev, 1613 &want_props, &want_contents, 1614 &wants_inherited_props)); 1615 1616 if (wants_inherited_props == SVN_RA_SVN_UNSPECIFIED_NUMBER) 1617 wants_inherited_props = FALSE; 1618 1619 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, pool, 1620 pool)); 1621 full_path = svn_fspath__join(b->repository->fs_path->data, canonical_path, 1622 pool); 1623 1624 /* Check authorizations */ 1625 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, 1626 full_path, FALSE)); 1627 1628 if (!SVN_IS_VALID_REVNUM(rev)) 1629 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool)); 1630 1631 SVN_ERR(log_command(b, conn, pool, "%s", 1632 svn_log__get_file(full_path, rev, 1633 want_contents, want_props, pool))); 1634 1635 /* Fetch the properties and a stream for the contents. */ 1636 SVN_CMD_ERR(svn_fs_revision_root(&root, b->repository->fs, rev, pool)); 1637 SVN_CMD_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, root, 1638 full_path, TRUE, pool)); 1639 hex_digest = svn_checksum_to_cstring_display(checksum, pool); 1640 1641 /* Fetch the file's explicit and/or inherited properties if 1642 requested. Although the wants-iprops boolean was added to the 1643 protocol in 1.8 a standard 1.8 client never requests iprops. */ 1644 if (want_props || wants_inherited_props) 1645 SVN_CMD_ERR(get_props(want_props ? &props : NULL, 1646 wants_inherited_props ? &inherited_props : NULL, 1647 &ab, root, full_path, 1648 pool)); 1649 if (want_contents) 1650 SVN_CMD_ERR(svn_fs_file_contents(&contents, root, full_path, pool)); 1651 1652 /* Send successful command response with revision and props. */ 1653 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)r(!", "success", 1654 hex_digest, rev)); 1655 SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props)); 1656 1657 if (wants_inherited_props) 1658 { 1659 apr_pool_t *iterpool = svn_pool_create(pool); 1660 1661 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?!")); 1662 for (i = 0; i < inherited_props->nelts; i++) 1663 { 1664 svn_prop_inherited_item_t *iprop = 1665 APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *); 1666 1667 svn_pool_clear(iterpool); 1668 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!", 1669 iprop->path_or_url)); 1670 SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash)); 1671 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!", 1672 iprop->path_or_url)); 1673 } 1674 svn_pool_destroy(iterpool); 1675 } 1676 1677 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 1678 1679 /* Now send the file's contents. */ 1680 if (want_contents) 1681 { 1682 err = SVN_NO_ERROR; 1683 while (1) 1684 { 1685 len = sizeof(buf); 1686 err = svn_stream_read_full(contents, buf, &len); 1687 if (err) 1688 break; 1689 if (len > 0) 1690 { 1691 write_str.data = buf; 1692 write_str.len = len; 1693 SVN_ERR(svn_ra_svn__write_string(conn, pool, &write_str)); 1694 } 1695 if (len < sizeof(buf)) 1696 { 1697 err = svn_stream_close(contents); 1698 break; 1699 } 1700 } 1701 write_err = svn_ra_svn__write_cstring(conn, pool, ""); 1702 if (write_err) 1703 { 1704 svn_error_clear(err); 1705 return write_err; 1706 } 1707 SVN_CMD_ERR(err); 1708 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 1709 } 1710 1711 return SVN_NO_ERROR; 1712} 1713 1714/* Translate all the words in DIRENT_FIELDS_LIST into the flags in 1715 * DIRENT_FIELDS_P. If DIRENT_FIELDS_LIST is NULL, set all flags. */ 1716static svn_error_t * 1717parse_dirent_fields(apr_uint32_t *dirent_fields_p, 1718 svn_ra_svn__list_t *dirent_fields_list) 1719{ 1720 static const svn_string_t str_kind 1721 = SVN__STATIC_STRING(SVN_RA_SVN_DIRENT_KIND); 1722 static const svn_string_t str_size 1723 = SVN__STATIC_STRING(SVN_RA_SVN_DIRENT_SIZE); 1724 static const svn_string_t str_has_props 1725 = SVN__STATIC_STRING(SVN_RA_SVN_DIRENT_HAS_PROPS); 1726 static const svn_string_t str_created_rev 1727 = SVN__STATIC_STRING(SVN_RA_SVN_DIRENT_CREATED_REV); 1728 static const svn_string_t str_time 1729 = SVN__STATIC_STRING(SVN_RA_SVN_DIRENT_TIME); 1730 static const svn_string_t str_last_author 1731 = SVN__STATIC_STRING(SVN_RA_SVN_DIRENT_LAST_AUTHOR); 1732 1733 apr_uint32_t dirent_fields; 1734 1735 if (! dirent_fields_list) 1736 { 1737 dirent_fields = SVN_DIRENT_ALL; 1738 } 1739 else 1740 { 1741 int i; 1742 dirent_fields = 0; 1743 1744 for (i = 0; i < dirent_fields_list->nelts; ++i) 1745 { 1746 svn_ra_svn__item_t *elt 1747 = &SVN_RA_SVN__LIST_ITEM(dirent_fields_list, i); 1748 1749 if (elt->kind != SVN_RA_SVN_WORD) 1750 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1751 "Dirent field not a word"); 1752 1753 if (svn_string_compare(&str_kind, &elt->u.word)) 1754 dirent_fields |= SVN_DIRENT_KIND; 1755 else if (svn_string_compare(&str_size, &elt->u.word)) 1756 dirent_fields |= SVN_DIRENT_SIZE; 1757 else if (svn_string_compare(&str_has_props, &elt->u.word)) 1758 dirent_fields |= SVN_DIRENT_HAS_PROPS; 1759 else if (svn_string_compare(&str_created_rev, &elt->u.word)) 1760 dirent_fields |= SVN_DIRENT_CREATED_REV; 1761 else if (svn_string_compare(&str_time, &elt->u.word)) 1762 dirent_fields |= SVN_DIRENT_TIME; 1763 else if (svn_string_compare(&str_last_author, &elt->u.word)) 1764 dirent_fields |= SVN_DIRENT_LAST_AUTHOR; 1765 } 1766 } 1767 1768 *dirent_fields_p = dirent_fields; 1769 return SVN_NO_ERROR; 1770} 1771 1772static svn_error_t * 1773get_dir(svn_ra_svn_conn_t *conn, 1774 apr_pool_t *pool, 1775 svn_ra_svn__list_t *params, 1776 void *baton) 1777{ 1778 server_baton_t *b = baton; 1779 const char *path, *full_path, *canonical_path; 1780 svn_revnum_t rev; 1781 apr_hash_t *entries, *props = NULL; 1782 apr_array_header_t *inherited_props; 1783 apr_hash_index_t *hi; 1784 svn_fs_root_t *root; 1785 apr_pool_t *subpool; 1786 svn_boolean_t want_props, want_contents; 1787 apr_uint64_t wants_inherited_props; 1788 apr_uint32_t dirent_fields; 1789 svn_ra_svn__list_t *dirent_fields_list = NULL; 1790 int i; 1791 authz_baton_t ab; 1792 1793 ab.server = b; 1794 ab.conn = conn; 1795 1796 SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)bb?l?B", &path, &rev, 1797 &want_props, &want_contents, 1798 &dirent_fields_list, 1799 &wants_inherited_props)); 1800 1801 if (wants_inherited_props == SVN_RA_SVN_UNSPECIFIED_NUMBER) 1802 wants_inherited_props = FALSE; 1803 1804 SVN_ERR(parse_dirent_fields(&dirent_fields, dirent_fields_list)); 1805 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, 1806 pool, pool)); 1807 full_path = svn_fspath__join(b->repository->fs_path->data, canonical_path, 1808 pool); 1809 1810 /* Check authorizations */ 1811 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, 1812 full_path, FALSE)); 1813 1814 if (!SVN_IS_VALID_REVNUM(rev)) 1815 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool)); 1816 1817 SVN_ERR(log_command(b, conn, pool, "%s", 1818 svn_log__get_dir(full_path, rev, 1819 want_contents, want_props, 1820 dirent_fields, pool))); 1821 1822 /* Fetch the root of the appropriate revision. */ 1823 SVN_CMD_ERR(svn_fs_revision_root(&root, b->repository->fs, rev, pool)); 1824 1825 /* Fetch the directory's explicit and/or inherited properties if 1826 requested. Although the wants-iprops boolean was added to the 1827 protocol in 1.8 a standard 1.8 client never requests iprops. */ 1828 if (want_props || wants_inherited_props) 1829 SVN_CMD_ERR(get_props(want_props ? &props : NULL, 1830 wants_inherited_props ? &inherited_props : NULL, 1831 &ab, root, full_path, 1832 pool)); 1833 1834 /* Fetch the directories' entries before starting the response, to allow 1835 proper error handling in cases like when FULL_PATH doesn't exist */ 1836 if (want_contents) 1837 SVN_CMD_ERR(svn_fs_dir_entries(&entries, root, full_path, pool)); 1838 1839 /* Begin response ... */ 1840 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(r(!", "success", rev)); 1841 SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props)); 1842 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(!")); 1843 1844 /* Fetch the directory entries if requested and send them immediately. */ 1845 if (want_contents) 1846 { 1847 /* Use epoch for a placeholder for a missing date. */ 1848 const char *missing_date = svn_time_to_cstring(0, pool); 1849 1850 /* Transform the hash table's FS entries into dirents. This probably 1851 * belongs in libsvn_repos. */ 1852 subpool = svn_pool_create(pool); 1853 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) 1854 { 1855 const char *name = apr_hash_this_key(hi); 1856 svn_fs_dirent_t *fsent = apr_hash_this_val(hi); 1857 const char *file_path; 1858 1859 /* The fields in the entry tuple. */ 1860 svn_node_kind_t entry_kind = svn_node_none; 1861 svn_filesize_t entry_size = 0; 1862 svn_boolean_t has_props = FALSE; 1863 /* If 'created rev' was not requested, send 0. We can't use 1864 * SVN_INVALID_REVNUM as the tuple field is not optional. 1865 * See the email thread on dev@, 2012-03-28, subject 1866 * "buildbot failure in ASF Buildbot on svn-slik-w2k3-x64-ra", 1867 * <http://svn.haxx.se/dev/archive-2012-03/0655.shtml>. */ 1868 svn_revnum_t created_rev = 0; 1869 const char *cdate = NULL; 1870 const char *last_author = NULL; 1871 1872 svn_pool_clear(subpool); 1873 1874 file_path = svn_fspath__join(full_path, name, subpool); 1875 if (! lookup_access(subpool, b, svn_authz_read, file_path, FALSE)) 1876 continue; 1877 1878 if (dirent_fields & SVN_DIRENT_KIND) 1879 entry_kind = fsent->kind; 1880 1881 if (dirent_fields & SVN_DIRENT_SIZE) 1882 if (fsent->kind != svn_node_dir) 1883 SVN_CMD_ERR(svn_fs_file_length(&entry_size, root, file_path, 1884 subpool)); 1885 1886 if (dirent_fields & SVN_DIRENT_HAS_PROPS) 1887 { 1888 /* has_props */ 1889 SVN_CMD_ERR(svn_fs_node_has_props(&has_props, root, file_path, 1890 subpool)); 1891 } 1892 1893 if ((dirent_fields & SVN_DIRENT_LAST_AUTHOR) 1894 || (dirent_fields & SVN_DIRENT_TIME) 1895 || (dirent_fields & SVN_DIRENT_CREATED_REV)) 1896 { 1897 /* created_rev, last_author, time */ 1898 SVN_CMD_ERR(svn_repos_get_committed_info(&created_rev, 1899 &cdate, 1900 &last_author, 1901 root, 1902 file_path, 1903 subpool)); 1904 } 1905 1906 /* The client does not properly handle a missing CDATE. For 1907 interoperability purposes, we must fill in some junk. 1908 1909 See libsvn_ra_svn/client.c:ra_svn_get_dir() */ 1910 if (cdate == NULL) 1911 cdate = missing_date; 1912 1913 /* Send the entry. */ 1914 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "cwnbr(?c)(?c)", name, 1915 svn_node_kind_to_word(entry_kind), 1916 (apr_uint64_t) entry_size, 1917 has_props, created_rev, 1918 cdate, last_author)); 1919 } 1920 svn_pool_destroy(subpool); 1921 } 1922 1923 if (wants_inherited_props) 1924 { 1925 apr_pool_t *iterpool = svn_pool_create(pool); 1926 1927 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?!")); 1928 for (i = 0; i < inherited_props->nelts; i++) 1929 { 1930 svn_prop_inherited_item_t *iprop = 1931 APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *); 1932 1933 svn_pool_clear(iterpool); 1934 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!", 1935 iprop->path_or_url)); 1936 SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash)); 1937 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!", 1938 iprop->path_or_url)); 1939 } 1940 svn_pool_destroy(iterpool); 1941 } 1942 1943 /* Finish response. */ 1944 return svn_ra_svn__write_tuple(conn, pool, "!))"); 1945} 1946 1947static svn_error_t * 1948update(svn_ra_svn_conn_t *conn, 1949 apr_pool_t *pool, 1950 svn_ra_svn__list_t *params, 1951 void *baton) 1952{ 1953 server_baton_t *b = baton; 1954 svn_revnum_t rev; 1955 const char *target, *full_path, *depth_word, *canonical_target; 1956 svn_boolean_t recurse; 1957 svn_tristate_t send_copyfrom_args; /* Optional; default FALSE */ 1958 svn_tristate_t ignore_ancestry; /* Optional; default FALSE */ 1959 /* Default to unknown. Old clients won't send depth, but we'll 1960 handle that by converting recurse if necessary. */ 1961 svn_depth_t depth = svn_depth_unknown; 1962 svn_boolean_t is_checkout; 1963 1964 /* Parse the arguments. */ 1965 SVN_ERR(svn_ra_svn__parse_tuple(params, "(?r)cb?w3?3", &rev, &target, 1966 &recurse, &depth_word, 1967 &send_copyfrom_args, &ignore_ancestry)); 1968 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_target, NULL, target, 1969 pool, pool)); 1970 target = canonical_target; 1971 1972 if (depth_word) 1973 depth = svn_depth_from_word(depth_word); 1974 else 1975 depth = SVN_DEPTH_INFINITY_OR_FILES(recurse); 1976 1977 full_path = svn_fspath__join(b->repository->fs_path->data, target, pool); 1978 /* Check authorization and authenticate the user if necessary. */ 1979 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, full_path, FALSE)); 1980 1981 if (!SVN_IS_VALID_REVNUM(rev)) 1982 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool)); 1983 1984 SVN_ERR(accept_report(&is_checkout, NULL, 1985 conn, pool, b, rev, target, NULL, TRUE, 1986 depth, 1987 (send_copyfrom_args == svn_tristate_true), 1988 (ignore_ancestry == svn_tristate_true))); 1989 if (is_checkout) 1990 { 1991 SVN_ERR(log_command(b, conn, pool, "%s", 1992 svn_log__checkout(full_path, rev, 1993 depth, pool))); 1994 } 1995 else 1996 { 1997 SVN_ERR(log_command(b, conn, pool, "%s", 1998 svn_log__update(full_path, rev, depth, 1999 (send_copyfrom_args 2000 == svn_tristate_true), 2001 pool))); 2002 } 2003 2004 return SVN_NO_ERROR; 2005} 2006 2007static svn_error_t * 2008switch_cmd(svn_ra_svn_conn_t *conn, 2009 apr_pool_t *pool, 2010 svn_ra_svn__list_t *params, 2011 void *baton) 2012{ 2013 server_baton_t *b = baton; 2014 svn_revnum_t rev; 2015 const char *target, *depth_word; 2016 const char *switch_url, *switch_path, *canonical_url, *canonical_target; 2017 svn_boolean_t recurse; 2018 /* Default to unknown. Old clients won't send depth, but we'll 2019 handle that by converting recurse if necessary. */ 2020 svn_depth_t depth = svn_depth_unknown; 2021 svn_tristate_t send_copyfrom_args; /* Optional; default FALSE */ 2022 svn_tristate_t ignore_ancestry; /* Optional; default TRUE */ 2023 2024 /* Parse the arguments. */ 2025 SVN_ERR(svn_ra_svn__parse_tuple(params, "(?r)cbc?w?33", &rev, &target, 2026 &recurse, &switch_url, &depth_word, 2027 &send_copyfrom_args, &ignore_ancestry)); 2028 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_target, NULL, target, 2029 pool, pool)); 2030 target = canonical_target; 2031 SVN_ERR(svn_uri_canonicalize_safe(&canonical_url, NULL, switch_url, pool, 2032 pool)); 2033 switch_url = canonical_url; 2034 if (depth_word) 2035 depth = svn_depth_from_word(depth_word); 2036 else 2037 depth = SVN_DEPTH_INFINITY_OR_FILES(recurse); 2038 2039 SVN_ERR(trivial_auth_request(conn, pool, b)); 2040 if (!SVN_IS_VALID_REVNUM(rev)) 2041 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool)); 2042 2043 SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repository->repos_url, 2044 pool), 2045 svn_path_uri_decode(switch_url, pool), 2046 &switch_path)); 2047 2048 { 2049 const char *full_path = svn_fspath__join(b->repository->fs_path->data, 2050 target, pool); 2051 SVN_ERR(log_command(b, conn, pool, "%s", 2052 svn_log__switch(full_path, switch_path, rev, 2053 depth, pool))); 2054 } 2055 2056 return accept_report(NULL, NULL, 2057 conn, pool, b, rev, target, switch_path, TRUE, 2058 depth, 2059 (send_copyfrom_args == svn_tristate_true), 2060 (ignore_ancestry != svn_tristate_false)); 2061} 2062 2063static svn_error_t * 2064status(svn_ra_svn_conn_t *conn, 2065 apr_pool_t *pool, 2066 svn_ra_svn__list_t *params, 2067 void *baton) 2068{ 2069 server_baton_t *b = baton; 2070 svn_revnum_t rev; 2071 const char *target, *depth_word, *canonical_target; 2072 svn_boolean_t recurse; 2073 /* Default to unknown. Old clients won't send depth, but we'll 2074 handle that by converting recurse if necessary. */ 2075 svn_depth_t depth = svn_depth_unknown; 2076 2077 /* Parse the arguments. */ 2078 SVN_ERR(svn_ra_svn__parse_tuple(params, "cb?(?r)?w", 2079 &target, &recurse, &rev, &depth_word)); 2080 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_target, NULL, target, 2081 pool, pool)); 2082 target = canonical_target; 2083 2084 if (depth_word) 2085 depth = svn_depth_from_word(depth_word); 2086 else 2087 depth = SVN_DEPTH_INFINITY_OR_EMPTY(recurse); 2088 2089 SVN_ERR(trivial_auth_request(conn, pool, b)); 2090 if (!SVN_IS_VALID_REVNUM(rev)) 2091 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool)); 2092 2093 { 2094 const char *full_path = svn_fspath__join(b->repository->fs_path->data, 2095 target, pool); 2096 SVN_ERR(log_command(b, conn, pool, "%s", 2097 svn_log__status(full_path, rev, depth, pool))); 2098 } 2099 2100 return accept_report(NULL, NULL, conn, pool, b, rev, target, NULL, FALSE, 2101 depth, FALSE, FALSE); 2102} 2103 2104static svn_error_t * 2105diff(svn_ra_svn_conn_t *conn, 2106 apr_pool_t *pool, 2107 svn_ra_svn__list_t *params, 2108 void *baton) 2109{ 2110 server_baton_t *b = baton; 2111 svn_revnum_t rev; 2112 const char *target, *versus_url, *versus_path, *depth_word, *canonical_url; 2113 const char *canonical_target; 2114 svn_boolean_t recurse, ignore_ancestry; 2115 svn_boolean_t text_deltas; 2116 /* Default to unknown. Old clients won't send depth, but we'll 2117 handle that by converting recurse if necessary. */ 2118 svn_depth_t depth = svn_depth_unknown; 2119 2120 /* Parse the arguments. */ 2121 if (params->nelts == 5) 2122 { 2123 /* Clients before 1.4 don't send the text_deltas boolean or depth. */ 2124 SVN_ERR(svn_ra_svn__parse_tuple(params, "(?r)cbbc", &rev, &target, 2125 &recurse, &ignore_ancestry, &versus_url)); 2126 text_deltas = TRUE; 2127 depth_word = NULL; 2128 } 2129 else 2130 { 2131 SVN_ERR(svn_ra_svn__parse_tuple(params, "(?r)cbbcb?w", 2132 &rev, &target, &recurse, 2133 &ignore_ancestry, &versus_url, 2134 &text_deltas, &depth_word)); 2135 } 2136 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_target, NULL, target, 2137 pool, pool)); 2138 target = canonical_target; 2139 SVN_ERR(svn_uri_canonicalize_safe(&canonical_url, NULL, versus_url, 2140 pool, pool)); 2141 versus_url = canonical_url; 2142 2143 if (depth_word) 2144 depth = svn_depth_from_word(depth_word); 2145 else 2146 depth = SVN_DEPTH_INFINITY_OR_FILES(recurse); 2147 2148 SVN_ERR(trivial_auth_request(conn, pool, b)); 2149 2150 if (!SVN_IS_VALID_REVNUM(rev)) 2151 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool)); 2152 SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repository->repos_url, 2153 pool), 2154 svn_path_uri_decode(versus_url, pool), 2155 &versus_path)); 2156 2157 { 2158 const char *full_path = svn_fspath__join(b->repository->fs_path->data, 2159 target, pool); 2160 svn_revnum_t from_rev; 2161 SVN_ERR(accept_report(NULL, &from_rev, 2162 conn, pool, b, rev, target, versus_path, 2163 text_deltas, depth, FALSE, ignore_ancestry)); 2164 SVN_ERR(log_command(b, conn, pool, "%s", 2165 svn_log__diff(full_path, from_rev, versus_path, 2166 rev, depth, ignore_ancestry, 2167 pool))); 2168 } 2169 return SVN_NO_ERROR; 2170} 2171 2172/* Baton type to be used with mergeinfo_receiver. */ 2173typedef struct mergeinfo_receiver_baton_t 2174{ 2175 /* Send the response over this connection. */ 2176 svn_ra_svn_conn_t *conn; 2177 2178 /* Start path of the query; report paths relative to this one. */ 2179 const char *fs_path; 2180 2181 /* Did we already send the opening sequence? */ 2182 svn_boolean_t starting_tuple_sent; 2183} mergeinfo_receiver_baton_t; 2184 2185/* Utility method sending the start of the "get m/i" response once 2186 over BATON->CONN. */ 2187static svn_error_t * 2188send_mergeinfo_starting_tuple(mergeinfo_receiver_baton_t *baton, 2189 apr_pool_t *scratch_pool) 2190{ 2191 if (baton->starting_tuple_sent) 2192 return SVN_NO_ERROR; 2193 2194 SVN_ERR(svn_ra_svn__write_tuple(baton->conn, scratch_pool, 2195 "w((!", "success")); 2196 baton->starting_tuple_sent = TRUE; 2197 2198 return SVN_NO_ERROR; 2199} 2200 2201/* Implements svn_repos_mergeinfo_receiver_t, sending the MERGEINFO 2202 * out over the connection in the mergeinfo_receiver_baton_t * BATON. */ 2203static svn_error_t * 2204mergeinfo_receiver(const char *path, 2205 svn_mergeinfo_t mergeinfo, 2206 void *baton, 2207 apr_pool_t *scratch_pool) 2208{ 2209 mergeinfo_receiver_baton_t *b = baton; 2210 svn_string_t *mergeinfo_string; 2211 2212 /* Delay starting the response until we checked that the initial 2213 request went through. We are at that point now b/c we've got 2214 the first results in. */ 2215 SVN_ERR(send_mergeinfo_starting_tuple(b, scratch_pool)); 2216 2217 /* Adjust the path info and send the m/i. */ 2218 path = svn_fspath__skip_ancestor(b->fs_path, path); 2219 SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, mergeinfo, 2220 scratch_pool)); 2221 SVN_ERR(svn_ra_svn__write_tuple(b->conn, scratch_pool, "cs", path, 2222 mergeinfo_string)); 2223 2224 return SVN_NO_ERROR; 2225} 2226 2227/* Regardless of whether a client's capabilities indicate an 2228 understanding of this command (by way of SVN_RA_SVN_CAP_MERGEINFO), 2229 we provide a response. 2230 2231 ASSUMPTION: When performing a 'merge' with two URLs at different 2232 revisions, the client will call this command more than once. */ 2233static svn_error_t * 2234get_mergeinfo(svn_ra_svn_conn_t *conn, 2235 apr_pool_t *pool, 2236 svn_ra_svn__list_t *params, 2237 void *baton) 2238{ 2239 server_baton_t *b = baton; 2240 svn_revnum_t rev; 2241 svn_ra_svn__list_t *paths; 2242 apr_array_header_t *canonical_paths; 2243 int i; 2244 const char *inherit_word; 2245 svn_mergeinfo_inheritance_t inherit; 2246 svn_boolean_t include_descendants; 2247 authz_baton_t ab; 2248 mergeinfo_receiver_baton_t mergeinfo_baton; 2249 2250 ab.server = b; 2251 ab.conn = conn; 2252 2253 mergeinfo_baton.conn = conn; 2254 mergeinfo_baton.fs_path = b->repository->fs_path->data; 2255 mergeinfo_baton.starting_tuple_sent = FALSE; 2256 2257 SVN_ERR(svn_ra_svn__parse_tuple(params, "l(?r)wb", &paths, &rev, 2258 &inherit_word, &include_descendants)); 2259 inherit = svn_inheritance_from_word(inherit_word); 2260 2261 /* Canonicalize the paths which mergeinfo has been requested for. */ 2262 canonical_paths = apr_array_make(pool, paths->nelts, sizeof(const char *)); 2263 for (i = 0; i < paths->nelts; i++) 2264 { 2265 svn_ra_svn__item_t *item = &SVN_RA_SVN__LIST_ITEM(paths, i); 2266 const char *full_path, *canonical_path; 2267 2268 if (item->kind != SVN_RA_SVN_STRING) 2269 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2270 _("Path is not a string")); 2271 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, 2272 item->u.string.data, pool, pool)); 2273 full_path = svn_fspath__join(b->repository->fs_path->data, 2274 canonical_path, pool); 2275 APR_ARRAY_PUSH(canonical_paths, const char *) = full_path; 2276 } 2277 2278 SVN_ERR(log_command(b, conn, pool, "%s", 2279 svn_log__get_mergeinfo(canonical_paths, inherit, 2280 include_descendants, 2281 pool))); 2282 2283 SVN_ERR(trivial_auth_request(conn, pool, b)); 2284 2285 SVN_CMD_ERR(svn_repos_fs_get_mergeinfo2(b->repository->repos, 2286 canonical_paths, rev, 2287 inherit, 2288 include_descendants, 2289 authz_check_access_cb_func(b), &ab, 2290 mergeinfo_receiver, 2291 &mergeinfo_baton, 2292 pool)); 2293 2294 /* We might not have sent anything 2295 => ensure to begin the response in any case. */ 2296 SVN_ERR(send_mergeinfo_starting_tuple(&mergeinfo_baton, pool)); 2297 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 2298 2299 return SVN_NO_ERROR; 2300} 2301 2302/* Send a changed paths list entry to the client. 2303 This implements svn_repos_path_change_receiver_t. */ 2304static svn_error_t * 2305path_change_receiver(void *baton, 2306 svn_repos_path_change_t *change, 2307 apr_pool_t *scratch_pool) 2308{ 2309 const char symbol[] = "MADR"; 2310 2311 log_baton_t *b = baton; 2312 svn_ra_svn_conn_t *conn = b->conn; 2313 2314 /* Sanitize and convert change kind to ra-svn level action. 2315 2316 Pushing that conversion down into libsvn_ra_svn would add yet another 2317 API dependency there. */ 2318 char action = ( change->change_kind < svn_fs_path_change_modify 2319 || change->change_kind > svn_fs_path_change_replace) 2320 ? 0 2321 : symbol[change->change_kind]; 2322 2323 /* Open lists once: LOG_ENTRY and LOG_ENTRY->CHANGED_PATHS. */ 2324 if (!b->started) 2325 { 2326 SVN_ERR(svn_ra_svn__start_list(conn, scratch_pool)); 2327 SVN_ERR(svn_ra_svn__start_list(conn, scratch_pool)); 2328 b->started = TRUE; 2329 } 2330 2331 /* Serialize CHANGE. */ 2332 SVN_ERR(svn_ra_svn__write_data_log_changed_path( 2333 conn, scratch_pool, 2334 &change->path, 2335 action, 2336 change->copyfrom_path, 2337 change->copyfrom_rev, 2338 change->node_kind, 2339 change->text_mod, 2340 change->prop_mod)); 2341 2342 return SVN_NO_ERROR; 2343} 2344 2345/* Send a the meta data and the revpros for LOG_ENTRY to the client. 2346 This implements svn_log_entry_receiver_t. */ 2347static svn_error_t * 2348revision_receiver(void *baton, 2349 svn_repos_log_entry_t *log_entry, 2350 apr_pool_t *scratch_pool) 2351{ 2352 log_baton_t *b = baton; 2353 svn_ra_svn_conn_t *conn = b->conn; 2354 svn_boolean_t invalid_revnum = FALSE; 2355 const svn_string_t *author, *date, *message; 2356 unsigned revprop_count; 2357 2358 if (log_entry->revision == SVN_INVALID_REVNUM) 2359 { 2360 /* If the stack depth is zero, we've seen the last revision, so don't 2361 send it, just return. */ 2362 if (b->stack_depth == 0) 2363 return SVN_NO_ERROR; 2364 2365 /* Because the svn protocol won't let us send an invalid revnum, we have 2366 to fudge here and send an additional flag. */ 2367 log_entry->revision = 0; 2368 invalid_revnum = TRUE; 2369 b->stack_depth--; 2370 } 2371 2372 svn_compat_log_revprops_out_string(&author, &date, &message, 2373 log_entry->revprops); 2374 2375 /* Revprops list filtering is somewhat expensive. 2376 Avoid doing that for the 90% case where only the standard revprops 2377 have been requested and delivered. */ 2378 if (author && date && message && apr_hash_count(log_entry->revprops) == 3) 2379 { 2380 revprop_count = 0; 2381 } 2382 else 2383 { 2384 svn_compat_log_revprops_clear(log_entry->revprops); 2385 if (log_entry->revprops) 2386 revprop_count = apr_hash_count(log_entry->revprops); 2387 else 2388 revprop_count = 0; 2389 } 2390 2391 /* Open lists once: LOG_ENTRY and LOG_ENTRY->CHANGED_PATHS. */ 2392 if (!b->started) 2393 { 2394 SVN_ERR(svn_ra_svn__start_list(conn, scratch_pool)); 2395 SVN_ERR(svn_ra_svn__start_list(conn, scratch_pool)); 2396 } 2397 2398 /* Close LOG_ENTRY->CHANGED_PATHS. */ 2399 SVN_ERR(svn_ra_svn__end_list(conn, scratch_pool)); 2400 b->started = FALSE; 2401 2402 /* send LOG_ENTRY main members */ 2403 SVN_ERR(svn_ra_svn__write_data_log_entry(conn, scratch_pool, 2404 log_entry->revision, 2405 author, date, message, 2406 log_entry->has_children, 2407 invalid_revnum, revprop_count)); 2408 2409 /* send LOG_ENTRY->REVPROPS */ 2410 SVN_ERR(svn_ra_svn__start_list(conn, scratch_pool)); 2411 if (revprop_count) 2412 SVN_ERR(svn_ra_svn__write_proplist(conn, scratch_pool, 2413 log_entry->revprops)); 2414 SVN_ERR(svn_ra_svn__end_list(conn, scratch_pool)); 2415 2416 /* send LOG_ENTRY members that were added in later SVN releases */ 2417 SVN_ERR(svn_ra_svn__write_boolean(conn, scratch_pool, 2418 log_entry->subtractive_merge)); 2419 SVN_ERR(svn_ra_svn__end_list(conn, scratch_pool)); 2420 2421 if (log_entry->has_children) 2422 b->stack_depth++; 2423 2424 return SVN_NO_ERROR; 2425} 2426 2427static svn_error_t * 2428log_cmd(svn_ra_svn_conn_t *conn, 2429 apr_pool_t *pool, 2430 svn_ra_svn__list_t *params, 2431 void *baton) 2432{ 2433 svn_error_t *err, *write_err; 2434 server_baton_t *b = baton; 2435 svn_revnum_t start_rev, end_rev; 2436 const char *full_path, *canonical_path; 2437 svn_boolean_t send_changed_paths, strict_node, include_merged_revisions; 2438 apr_array_header_t *full_paths, *revprops; 2439 svn_ra_svn__list_t *paths, *revprop_items; 2440 char *revprop_word; 2441 svn_ra_svn__item_t *elt; 2442 int i; 2443 apr_uint64_t limit, include_merged_revs_param; 2444 log_baton_t lb; 2445 authz_baton_t ab; 2446 2447 ab.server = b; 2448 ab.conn = conn; 2449 2450 SVN_ERR(svn_ra_svn__parse_tuple(params, "l(?r)(?r)bb?n?Bwl", &paths, 2451 &start_rev, &end_rev, &send_changed_paths, 2452 &strict_node, &limit, 2453 &include_merged_revs_param, 2454 &revprop_word, &revprop_items)); 2455 2456 if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) 2457 include_merged_revisions = FALSE; 2458 else 2459 include_merged_revisions = (svn_boolean_t) include_merged_revs_param; 2460 2461 if (revprop_word == NULL) 2462 /* pre-1.5 client */ 2463 revprops = svn_compat_log_revprops_in(pool); 2464 else if (strcmp(revprop_word, "all-revprops") == 0) 2465 revprops = NULL; 2466 else if (strcmp(revprop_word, "revprops") == 0) 2467 { 2468 SVN_ERR_ASSERT(revprop_items); 2469 2470 revprops = apr_array_make(pool, revprop_items->nelts, 2471 sizeof(char *)); 2472 for (i = 0; i < revprop_items->nelts; i++) 2473 { 2474 elt = &SVN_RA_SVN__LIST_ITEM(revprop_items, i); 2475 if (elt->kind != SVN_RA_SVN_STRING) 2476 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2477 _("Log revprop entry not a string")); 2478 APR_ARRAY_PUSH(revprops, const char *) = elt->u.string.data; 2479 } 2480 } 2481 else 2482 return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2483 _("Unknown revprop word '%s' in log command"), 2484 revprop_word); 2485 2486 /* If we got an unspecified number then the user didn't send us anything, 2487 so we assume no limit. If it's larger than INT_MAX then someone is 2488 messing with us, since we know the svn client libraries will never send 2489 us anything that big, so play it safe and default to no limit. */ 2490 if (limit == SVN_RA_SVN_UNSPECIFIED_NUMBER || limit > INT_MAX) 2491 limit = 0; 2492 2493 full_paths = apr_array_make(pool, paths->nelts, sizeof(const char *)); 2494 for (i = 0; i < paths->nelts; i++) 2495 { 2496 elt = &SVN_RA_SVN__LIST_ITEM(paths, i); 2497 if (elt->kind != SVN_RA_SVN_STRING) 2498 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2499 _("Log path entry not a string")); 2500 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, 2501 elt->u.string.data, pool, pool)); 2502 full_path = svn_fspath__join(b->repository->fs_path->data, 2503 canonical_path, pool); 2504 APR_ARRAY_PUSH(full_paths, const char *) = full_path; 2505 } 2506 SVN_ERR(trivial_auth_request(conn, pool, b)); 2507 2508 SVN_ERR(log_command(b, conn, pool, "%s", 2509 svn_log__log(full_paths, start_rev, end_rev, 2510 (int) limit, send_changed_paths, 2511 strict_node, include_merged_revisions, 2512 revprops, pool))); 2513 2514 /* Get logs. (Can't report errors back to the client at this point.) */ 2515 lb.fs_path = b->repository->fs_path->data; 2516 lb.conn = conn; 2517 lb.stack_depth = 0; 2518 lb.started = FALSE; 2519 err = svn_repos_get_logs5(b->repository->repos, full_paths, start_rev, 2520 end_rev, (int) limit, 2521 strict_node, include_merged_revisions, 2522 revprops, authz_check_access_cb_func(b), &ab, 2523 send_changed_paths ? path_change_receiver : NULL, 2524 send_changed_paths ? &lb : NULL, 2525 revision_receiver, &lb, pool); 2526 2527 write_err = svn_ra_svn__write_word(conn, pool, "done"); 2528 if (write_err) 2529 { 2530 svn_error_clear(err); 2531 return write_err; 2532 } 2533 SVN_CMD_ERR(err); 2534 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 2535 return SVN_NO_ERROR; 2536} 2537 2538static svn_error_t * 2539check_path(svn_ra_svn_conn_t *conn, 2540 apr_pool_t *pool, 2541 svn_ra_svn__list_t *params, 2542 void *baton) 2543{ 2544 server_baton_t *b = baton; 2545 svn_revnum_t rev; 2546 const char *path, *full_path, *canonical_path; 2547 svn_fs_root_t *root; 2548 svn_node_kind_t kind; 2549 2550 SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)", &path, &rev)); 2551 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, 2552 pool, pool));; 2553 full_path = svn_fspath__join(b->repository->fs_path->data, 2554 canonical_path, pool); 2555 2556 /* Check authorizations */ 2557 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, 2558 full_path, FALSE)); 2559 2560 if (!SVN_IS_VALID_REVNUM(rev)) 2561 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool)); 2562 2563 SVN_ERR(log_command(b, conn, pool, "check-path %s@%d", 2564 svn_path_uri_encode(full_path, pool), rev)); 2565 2566 SVN_CMD_ERR(svn_fs_revision_root(&root, b->repository->fs, rev, pool)); 2567 SVN_CMD_ERR(svn_fs_check_path(&kind, root, full_path, pool)); 2568 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "w", 2569 svn_node_kind_to_word(kind))); 2570 return SVN_NO_ERROR; 2571} 2572 2573static svn_error_t * 2574stat_cmd(svn_ra_svn_conn_t *conn, 2575 apr_pool_t *pool, 2576 svn_ra_svn__list_t *params, 2577 void *baton) 2578{ 2579 server_baton_t *b = baton; 2580 svn_revnum_t rev; 2581 const char *path, *full_path, *cdate, *canonical_path; 2582 svn_fs_root_t *root; 2583 svn_dirent_t *dirent; 2584 2585 SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)", &path, &rev)); 2586 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, pool, 2587 pool)); 2588 full_path = svn_fspath__join(b->repository->fs_path->data, canonical_path, 2589 pool); 2590 2591 /* Check authorizations */ 2592 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, 2593 full_path, FALSE)); 2594 2595 if (!SVN_IS_VALID_REVNUM(rev)) 2596 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool)); 2597 2598 SVN_ERR(log_command(b, conn, pool, "stat %s@%d", 2599 svn_path_uri_encode(full_path, pool), rev)); 2600 2601 SVN_CMD_ERR(svn_fs_revision_root(&root, b->repository->fs, rev, pool)); 2602 SVN_CMD_ERR(svn_repos_stat(&dirent, root, full_path, pool)); 2603 2604 /* Need to return the equivalent of "(?l)", since that's what the 2605 client is reading. */ 2606 2607 if (dirent == NULL) 2608 { 2609 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "()")); 2610 return SVN_NO_ERROR; 2611 } 2612 2613 cdate = (dirent->time == (time_t) -1) ? NULL 2614 : svn_time_to_cstring(dirent->time, pool); 2615 2616 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "((wnbr(?c)(?c)))", 2617 svn_node_kind_to_word(dirent->kind), 2618 (apr_uint64_t) dirent->size, 2619 dirent->has_props, dirent->created_rev, 2620 cdate, dirent->last_author)); 2621 2622 return SVN_NO_ERROR; 2623} 2624 2625static svn_error_t * 2626get_locations(svn_ra_svn_conn_t *conn, 2627 apr_pool_t *pool, 2628 svn_ra_svn__list_t *params, 2629 void *baton) 2630{ 2631 svn_error_t *err, *write_err; 2632 server_baton_t *b = baton; 2633 svn_revnum_t revision; 2634 apr_array_header_t *location_revisions; 2635 svn_ra_svn__list_t *loc_revs_proto; 2636 svn_ra_svn__item_t *elt; 2637 int i; 2638 const char *relative_path, *canonical_path; 2639 svn_revnum_t peg_revision; 2640 apr_hash_t *fs_locations; 2641 const char *abs_path; 2642 authz_baton_t ab; 2643 2644 ab.server = b; 2645 ab.conn = conn; 2646 2647 /* Parse the arguments. */ 2648 SVN_ERR(svn_ra_svn__parse_tuple(params, "crl", &relative_path, 2649 &peg_revision, 2650 &loc_revs_proto)); 2651 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, relative_path, 2652 pool, pool)); 2653 relative_path = canonical_path; 2654 2655 abs_path = svn_fspath__join(b->repository->fs_path->data, relative_path, 2656 pool); 2657 2658 location_revisions = apr_array_make(pool, loc_revs_proto->nelts, 2659 sizeof(svn_revnum_t)); 2660 for (i = 0; i < loc_revs_proto->nelts; i++) 2661 { 2662 elt = &SVN_RA_SVN__LIST_ITEM(loc_revs_proto, i); 2663 if (elt->kind != SVN_RA_SVN_NUMBER) 2664 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2665 "Get-locations location revisions entry " 2666 "not a revision number"); 2667 revision = (svn_revnum_t)(elt->u.number); 2668 APR_ARRAY_PUSH(location_revisions, svn_revnum_t) = revision; 2669 } 2670 SVN_ERR(trivial_auth_request(conn, pool, b)); 2671 SVN_ERR(log_command(b, conn, pool, "%s", 2672 svn_log__get_locations(abs_path, peg_revision, 2673 location_revisions, pool))); 2674 2675 /* All the parameters are fine - let's perform the query against the 2676 * repository. */ 2677 2678 /* We store both err and write_err here, so the client will get 2679 * the "done" even if there was an error in fetching the results. */ 2680 2681 err = svn_repos_trace_node_locations(b->repository->fs, &fs_locations, 2682 abs_path, peg_revision, 2683 location_revisions, 2684 authz_check_access_cb_func(b), &ab, 2685 pool); 2686 2687 /* Now, write the results to the connection. */ 2688 if (!err) 2689 { 2690 if (fs_locations) 2691 { 2692 apr_hash_index_t *iter; 2693 2694 for (iter = apr_hash_first(pool, fs_locations); iter; 2695 iter = apr_hash_next(iter)) 2696 { 2697 const svn_revnum_t *iter_key = apr_hash_this_key(iter); 2698 const char *iter_value = apr_hash_this_val(iter); 2699 2700 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "rc", 2701 *iter_key, iter_value)); 2702 } 2703 } 2704 } 2705 2706 write_err = svn_ra_svn__write_word(conn, pool, "done"); 2707 if (write_err) 2708 { 2709 svn_error_clear(err); 2710 return write_err; 2711 } 2712 SVN_CMD_ERR(err); 2713 2714 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 2715 2716 return SVN_NO_ERROR; 2717} 2718 2719static svn_error_t *gls_receiver(svn_location_segment_t *segment, 2720 void *baton, 2721 apr_pool_t *pool) 2722{ 2723 svn_ra_svn_conn_t *conn = baton; 2724 return svn_ra_svn__write_tuple(conn, pool, "rr(?c)", 2725 segment->range_start, 2726 segment->range_end, 2727 segment->path); 2728} 2729 2730static svn_error_t * 2731get_location_segments(svn_ra_svn_conn_t *conn, 2732 apr_pool_t *pool, 2733 svn_ra_svn__list_t *params, 2734 void *baton) 2735{ 2736 svn_error_t *err, *write_err; 2737 server_baton_t *b = baton; 2738 svn_revnum_t peg_revision, start_rev, end_rev; 2739 const char *relative_path, *canonical_path; 2740 const char *abs_path; 2741 authz_baton_t ab; 2742 2743 ab.server = b; 2744 ab.conn = conn; 2745 2746 /* Parse the arguments. */ 2747 SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)(?r)(?r)", 2748 &relative_path, &peg_revision, 2749 &start_rev, &end_rev)); 2750 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, relative_path, 2751 pool, pool)); 2752 relative_path = canonical_path; 2753 2754 abs_path = svn_fspath__join(b->repository->fs_path->data, relative_path, 2755 pool); 2756 2757 SVN_ERR(trivial_auth_request(conn, pool, b)); 2758 SVN_ERR(log_command(baton, conn, pool, "%s", 2759 svn_log__get_location_segments(abs_path, peg_revision, 2760 start_rev, end_rev, 2761 pool))); 2762 2763 /* No START_REV or PEG_REVISION? We'll use HEAD. */ 2764 if (!SVN_IS_VALID_REVNUM(start_rev) || !SVN_IS_VALID_REVNUM(peg_revision)) 2765 { 2766 svn_revnum_t youngest; 2767 2768 err = svn_fs_youngest_rev(&youngest, b->repository->fs, pool); 2769 2770 if (err) 2771 { 2772 err = svn_error_compose_create( 2773 svn_ra_svn__write_word(conn, pool, "done"), 2774 err); 2775 2776 return log_fail_and_flush(err, b, conn, pool); 2777 } 2778 2779 if (!SVN_IS_VALID_REVNUM(start_rev)) 2780 start_rev = youngest; 2781 if (!SVN_IS_VALID_REVNUM(peg_revision)) 2782 peg_revision = youngest; 2783 } 2784 2785 /* No END_REV? We'll use 0. */ 2786 if (!SVN_IS_VALID_REVNUM(end_rev)) 2787 end_rev = 0; 2788 2789 if (end_rev > start_rev) 2790 { 2791 err = svn_ra_svn__write_word(conn, pool, "done"); 2792 err = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, err, 2793 "Get-location-segments end revision must not be " 2794 "younger than start revision"); 2795 return log_fail_and_flush(err, b, conn, pool); 2796 } 2797 2798 if (start_rev > peg_revision) 2799 { 2800 err = svn_ra_svn__write_word(conn, pool, "done"); 2801 err = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, err, 2802 "Get-location-segments start revision must not " 2803 "be younger than peg revision"); 2804 return log_fail_and_flush(err, b, conn, pool); 2805 } 2806 2807 /* All the parameters are fine - let's perform the query against the 2808 * repository. */ 2809 2810 /* We store both err and write_err here, so the client will get 2811 * the "done" even if there was an error in fetching the results. */ 2812 2813 err = svn_repos_node_location_segments(b->repository->repos, abs_path, 2814 peg_revision, start_rev, end_rev, 2815 gls_receiver, (void *)conn, 2816 authz_check_access_cb_func(b), &ab, 2817 pool); 2818 write_err = svn_ra_svn__write_word(conn, pool, "done"); 2819 if (write_err) 2820 { 2821 return svn_error_compose_create(write_err, err); 2822 } 2823 SVN_CMD_ERR(err); 2824 2825 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 2826 2827 return SVN_NO_ERROR; 2828} 2829 2830/* This implements svn_write_fn_t. Write LEN bytes starting at DATA to the 2831 client as a string. */ 2832static svn_error_t *svndiff_handler(void *baton, const char *data, 2833 apr_size_t *len) 2834{ 2835 file_revs_baton_t *b = baton; 2836 svn_string_t str; 2837 2838 str.data = data; 2839 str.len = *len; 2840 return svn_ra_svn__write_string(b->conn, b->pool, &str); 2841} 2842 2843/* This implements svn_close_fn_t. Mark the end of the data by writing an 2844 empty string to the client. */ 2845static svn_error_t *svndiff_close_handler(void *baton) 2846{ 2847 file_revs_baton_t *b = baton; 2848 2849 SVN_ERR(svn_ra_svn__write_cstring(b->conn, b->pool, "")); 2850 return SVN_NO_ERROR; 2851} 2852 2853/* This implements the svn_repos_file_rev_handler_t interface. */ 2854static svn_error_t *file_rev_handler(void *baton, const char *path, 2855 svn_revnum_t rev, apr_hash_t *rev_props, 2856 svn_boolean_t merged_revision, 2857 svn_txdelta_window_handler_t *d_handler, 2858 void **d_baton, 2859 apr_array_header_t *prop_diffs, 2860 apr_pool_t *pool) 2861{ 2862 file_revs_baton_t *frb = baton; 2863 svn_stream_t *stream; 2864 2865 SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "cr(!", 2866 path, rev)); 2867 SVN_ERR(svn_ra_svn__write_proplist(frb->conn, pool, rev_props)); 2868 SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "!)(!")); 2869 SVN_ERR(write_prop_diffs(frb->conn, pool, prop_diffs)); 2870 SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "!)b", merged_revision)); 2871 2872 /* Store the pool for the delta stream. */ 2873 frb->pool = pool; 2874 2875 /* Prepare for the delta or just write an empty string. */ 2876 if (d_handler) 2877 { 2878 stream = svn_stream_create(baton, pool); 2879 svn_stream_set_write(stream, svndiff_handler); 2880 svn_stream_set_close(stream, svndiff_close_handler); 2881 2882 svn_txdelta_to_svndiff3(d_handler, d_baton, stream, 2883 svn_ra_svn__svndiff_version(frb->conn), 2884 svn_ra_svn_compression_level(frb->conn), pool); 2885 } 2886 else 2887 SVN_ERR(svn_ra_svn__write_cstring(frb->conn, pool, "")); 2888 2889 return SVN_NO_ERROR; 2890} 2891 2892static svn_error_t * 2893get_file_revs(svn_ra_svn_conn_t *conn, 2894 apr_pool_t *pool, 2895 svn_ra_svn__list_t *params, 2896 void *baton) 2897{ 2898 server_baton_t *b = baton; 2899 svn_error_t *err, *write_err; 2900 file_revs_baton_t frb; 2901 svn_revnum_t start_rev, end_rev; 2902 const char *path; 2903 const char *full_path; 2904 const char *canonical_path; 2905 apr_uint64_t include_merged_revs_param; 2906 svn_boolean_t include_merged_revisions; 2907 authz_baton_t ab; 2908 2909 ab.server = b; 2910 ab.conn = conn; 2911 2912 /* Parse arguments. */ 2913 SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)(?r)?B", 2914 &path, &start_rev, &end_rev, 2915 &include_merged_revs_param)); 2916 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, 2917 pool, pool)); 2918 path = canonical_path; 2919 SVN_ERR(trivial_auth_request(conn, pool, b)); 2920 full_path = svn_fspath__join(b->repository->fs_path->data, path, pool); 2921 2922 if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) 2923 include_merged_revisions = FALSE; 2924 else 2925 include_merged_revisions = (svn_boolean_t) include_merged_revs_param; 2926 2927 SVN_ERR(log_command(b, conn, pool, "%s", 2928 svn_log__get_file_revs(full_path, start_rev, end_rev, 2929 include_merged_revisions, 2930 pool))); 2931 2932 frb.conn = conn; 2933 frb.pool = NULL; 2934 2935 err = svn_repos_get_file_revs2(b->repository->repos, full_path, start_rev, 2936 end_rev, include_merged_revisions, 2937 authz_check_access_cb_func(b), &ab, 2938 file_rev_handler, &frb, pool); 2939 write_err = svn_ra_svn__write_word(conn, pool, "done"); 2940 if (write_err) 2941 { 2942 svn_error_clear(err); 2943 return write_err; 2944 } 2945 SVN_CMD_ERR(err); 2946 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 2947 2948 return SVN_NO_ERROR; 2949} 2950 2951static svn_error_t * 2952lock(svn_ra_svn_conn_t *conn, 2953 apr_pool_t *pool, 2954 svn_ra_svn__list_t *params, 2955 void *baton) 2956{ 2957 server_baton_t *b = baton; 2958 const char *path; 2959 const char *comment; 2960 const char *full_path; 2961 const char *canonical_path; 2962 svn_boolean_t steal_lock; 2963 svn_revnum_t current_rev; 2964 svn_lock_t *l; 2965 2966 SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?c)b(?r)", &path, &comment, 2967 &steal_lock, ¤t_rev)); 2968 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, 2969 pool, pool));; 2970 full_path = svn_fspath__join(b->repository->fs_path->data, 2971 canonical_path, pool); 2972 2973 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, 2974 full_path, TRUE)); 2975 SVN_ERR(log_command(b, conn, pool, "%s", 2976 svn_log__lock_one_path(full_path, steal_lock, pool))); 2977 2978 SVN_CMD_ERR(svn_repos_fs_lock(&l, b->repository->repos, full_path, NULL, 2979 comment, 0, 0, /* No expiration time. */ 2980 current_rev, steal_lock, pool)); 2981 2982 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(!", "success")); 2983 SVN_ERR(write_lock(conn, pool, l)); 2984 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)")); 2985 2986 return SVN_NO_ERROR; 2987} 2988 2989struct lock_result_t { 2990 const svn_lock_t *lock; 2991 svn_error_t *err; 2992}; 2993 2994struct lock_many_baton_t { 2995 apr_hash_t *results; 2996 apr_pool_t *pool; 2997}; 2998 2999/* Implements svn_fs_lock_callback_t. */ 3000static svn_error_t * 3001lock_many_cb(void *baton, 3002 const char *path, 3003 const svn_lock_t *fs_lock, 3004 svn_error_t *fs_err, 3005 apr_pool_t *pool) 3006{ 3007 struct lock_many_baton_t *b = baton; 3008 struct lock_result_t *result = apr_palloc(b->pool, 3009 sizeof(struct lock_result_t)); 3010 3011 result->lock = fs_lock; 3012 result->err = svn_error_dup(fs_err); 3013 svn_hash_sets(b->results, apr_pstrdup(b->pool, path), result); 3014 3015 return SVN_NO_ERROR; 3016} 3017 3018static void 3019clear_lock_result_hash(apr_hash_t *results, 3020 apr_pool_t *scratch_pool) 3021{ 3022 apr_hash_index_t *hi; 3023 3024 for (hi = apr_hash_first(scratch_pool, results); hi; hi = apr_hash_next(hi)) 3025 { 3026 struct lock_result_t *result = apr_hash_this_val(hi); 3027 svn_error_clear(result->err); 3028 } 3029} 3030 3031static svn_error_t * 3032lock_many(svn_ra_svn_conn_t *conn, 3033 apr_pool_t *pool, 3034 svn_ra_svn__list_t *params, 3035 void *baton) 3036{ 3037 server_baton_t *b = baton; 3038 svn_ra_svn__list_t *path_revs; 3039 const char *comment; 3040 svn_boolean_t steal_lock; 3041 int i; 3042 apr_pool_t *subpool; 3043 svn_error_t *err, *write_err = SVN_NO_ERROR; 3044 apr_hash_t *targets = apr_hash_make(pool); 3045 apr_hash_t *authz_results = apr_hash_make(pool); 3046 apr_hash_index_t *hi; 3047 struct lock_many_baton_t lmb; 3048 3049 SVN_ERR(svn_ra_svn__parse_tuple(params, "(?c)bl", &comment, &steal_lock, 3050 &path_revs)); 3051 3052 subpool = svn_pool_create(pool); 3053 3054 /* Because we can only send a single auth reply per request, we send 3055 a reply before parsing the lock commands. This means an authz 3056 access denial will abort the processing of the locks and return 3057 an error. */ 3058 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, TRUE)); 3059 3060 /* Parse the lock requests from PATH_REVS into TARGETS. */ 3061 for (i = 0; i < path_revs->nelts; ++i) 3062 { 3063 const char *path, *full_path, *canonical_path; 3064 svn_revnum_t current_rev; 3065 svn_ra_svn__item_t *item = &SVN_RA_SVN__LIST_ITEM(path_revs, i); 3066 svn_fs_lock_target_t *target; 3067 3068 svn_pool_clear(subpool); 3069 3070 if (item->kind != SVN_RA_SVN_LIST) 3071 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 3072 "Lock requests should be list of lists"); 3073 3074 SVN_ERR(svn_ra_svn__parse_tuple(&item->u.list, "c(?r)", &path, 3075 ¤t_rev)); 3076 3077 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, 3078 subpool, subpool)); 3079 full_path = svn_fspath__join(b->repository->fs_path->data, 3080 canonical_path, pool); 3081 target = svn_fs_lock_target_create(NULL, current_rev, pool); 3082 3083 /* Any duplicate paths, once canonicalized, get collapsed into a 3084 single path that is processed once. The result is then 3085 returned multiple times. */ 3086 svn_hash_sets(targets, full_path, target); 3087 } 3088 3089 SVN_ERR(log_command(b, conn, subpool, "%s", 3090 svn_log__lock(targets, steal_lock, subpool))); 3091 3092 /* Check authz. 3093 3094 Note: From here on we need to make sure any errors in authz_results, or 3095 results, are cleared before returning from this function. */ 3096 for (hi = apr_hash_first(pool, targets); hi; hi = apr_hash_next(hi)) 3097 { 3098 const char *full_path = apr_hash_this_key(hi); 3099 3100 svn_pool_clear(subpool); 3101 3102 if (! lookup_access(subpool, b, svn_authz_write, full_path, TRUE)) 3103 { 3104 struct lock_result_t *result 3105 = apr_palloc(pool, sizeof(struct lock_result_t)); 3106 3107 result->lock = NULL; 3108 result->err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, 3109 NULL, NULL, b); 3110 svn_hash_sets(authz_results, full_path, result); 3111 svn_hash_sets(targets, full_path, NULL); 3112 } 3113 } 3114 3115 lmb.results = apr_hash_make(pool); 3116 lmb.pool = pool; 3117 3118 err = svn_repos_fs_lock_many(b->repository->repos, targets, 3119 comment, FALSE, 3120 0, /* No expiration time. */ 3121 steal_lock, lock_many_cb, &lmb, 3122 pool, subpool); 3123 3124 /* Return results in the same order as the paths were supplied. */ 3125 for (i = 0; i < path_revs->nelts; ++i) 3126 { 3127 const char *path, *full_path, *canonical_path; 3128 svn_revnum_t current_rev; 3129 svn_ra_svn__item_t *item = &SVN_RA_SVN__LIST_ITEM(path_revs, i); 3130 struct lock_result_t *result; 3131 3132 svn_pool_clear(subpool); 3133 3134 write_err = svn_ra_svn__parse_tuple(&item->u.list, "c(?r)", 3135 &path, ¤t_rev); 3136 if (write_err) 3137 break; 3138 3139 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, 3140 subpool, subpool)); 3141 full_path = svn_fspath__join(b->repository->fs_path->data, 3142 canonical_path, subpool); 3143 3144 result = svn_hash_gets(lmb.results, full_path); 3145 if (!result) 3146 result = svn_hash_gets(authz_results, full_path); 3147 if (!result) 3148 { 3149 /* No result? Something really odd happened, create a 3150 placeholder error so that any other results can be 3151 reported in the correct order. */ 3152 result = apr_palloc(pool, sizeof(struct lock_result_t)); 3153 result->err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED, 0, 3154 _("No result for '%s'."), path); 3155 svn_hash_sets(lmb.results, full_path, result); 3156 } 3157 3158 if (result->err) 3159 write_err = svn_ra_svn__write_cmd_failure(conn, subpool, 3160 result->err); 3161 else 3162 { 3163 write_err = svn_ra_svn__write_tuple(conn, subpool, 3164 "w!", "success"); 3165 if (!write_err) 3166 write_err = write_lock(conn, subpool, result->lock); 3167 if (!write_err) 3168 write_err = svn_ra_svn__write_tuple(conn, subpool, "!"); 3169 } 3170 if (write_err) 3171 break; 3172 } 3173 3174 clear_lock_result_hash(authz_results, subpool); 3175 clear_lock_result_hash(lmb.results, subpool); 3176 3177 svn_pool_destroy(subpool); 3178 3179 if (!write_err) 3180 write_err = svn_ra_svn__write_word(conn, pool, "done"); 3181 if (!write_err) 3182 SVN_CMD_ERR(err); 3183 svn_error_clear(err); 3184 SVN_ERR(write_err); 3185 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 3186 3187 return SVN_NO_ERROR; 3188} 3189 3190static svn_error_t * 3191unlock(svn_ra_svn_conn_t *conn, 3192 apr_pool_t *pool, 3193 svn_ra_svn__list_t *params, 3194 void *baton) 3195{ 3196 server_baton_t *b = baton; 3197 const char *path, *token, *full_path, *canonical_path; 3198 svn_boolean_t break_lock; 3199 3200 SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?c)b", &path, &token, 3201 &break_lock)); 3202 3203 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, 3204 pool, pool)); 3205 full_path = svn_fspath__join(b->repository->fs_path->data, 3206 canonical_path, pool); 3207 3208 /* Username required unless break_lock was specified. */ 3209 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, 3210 full_path, ! break_lock)); 3211 SVN_ERR(log_command(b, conn, pool, "%s", 3212 svn_log__unlock_one_path(full_path, break_lock, pool))); 3213 3214 SVN_CMD_ERR(svn_repos_fs_unlock(b->repository->repos, full_path, token, 3215 break_lock, pool)); 3216 3217 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 3218 3219 return SVN_NO_ERROR; 3220} 3221 3222static svn_error_t * 3223unlock_many(svn_ra_svn_conn_t *conn, 3224 apr_pool_t *pool, 3225 svn_ra_svn__list_t *params, 3226 void *baton) 3227{ 3228 server_baton_t *b = baton; 3229 svn_boolean_t break_lock; 3230 svn_ra_svn__list_t *unlock_tokens; 3231 int i; 3232 apr_pool_t *subpool; 3233 svn_error_t *err = SVN_NO_ERROR, *write_err = SVN_NO_ERROR; 3234 apr_hash_t *targets = apr_hash_make(pool); 3235 apr_hash_t *authz_results = apr_hash_make(pool); 3236 apr_hash_index_t *hi; 3237 struct lock_many_baton_t lmb; 3238 3239 SVN_ERR(svn_ra_svn__parse_tuple(params, "bl", &break_lock, &unlock_tokens)); 3240 3241 /* Username required unless break_lock was specified. */ 3242 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, ! break_lock)); 3243 3244 subpool = svn_pool_create(pool); 3245 3246 /* Parse the unlock requests from PATH_REVS into TARGETS. */ 3247 for (i = 0; i < unlock_tokens->nelts; i++) 3248 { 3249 svn_ra_svn__item_t *item = &SVN_RA_SVN__LIST_ITEM(unlock_tokens, i); 3250 const char *path, *full_path, *token, *canonical_path; 3251 3252 svn_pool_clear(subpool); 3253 3254 if (item->kind != SVN_RA_SVN_LIST) 3255 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 3256 "Unlock request should be a list of lists"); 3257 3258 SVN_ERR(svn_ra_svn__parse_tuple(&item->u.list, "c(?c)", &path, 3259 &token)); 3260 if (!token) 3261 token = ""; 3262 3263 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, 3264 subpool, subpool)); 3265 full_path = svn_fspath__join(b->repository->fs_path->data, 3266 canonical_path, pool); 3267 3268 /* Any duplicate paths, once canonicalized, get collapsed into a 3269 single path that is processed once. The result is then 3270 returned multiple times. */ 3271 svn_hash_sets(targets, full_path, token); 3272 } 3273 3274 SVN_ERR(log_command(b, conn, subpool, "%s", 3275 svn_log__unlock(targets, break_lock, subpool))); 3276 3277 /* Check authz. 3278 3279 Note: From here on we need to make sure any errors in authz_results, or 3280 results, are cleared before returning from this function. */ 3281 for (hi = apr_hash_first(pool, targets); hi; hi = apr_hash_next(hi)) 3282 { 3283 const char *full_path = apr_hash_this_key(hi); 3284 3285 svn_pool_clear(subpool); 3286 3287 if (! lookup_access(subpool, b, svn_authz_write, full_path, 3288 ! break_lock)) 3289 { 3290 struct lock_result_t *result 3291 = apr_palloc(pool, sizeof(struct lock_result_t)); 3292 3293 result->lock = NULL; 3294 result->err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, 3295 NULL, NULL, b); 3296 svn_hash_sets(authz_results, full_path, result); 3297 svn_hash_sets(targets, full_path, NULL); 3298 } 3299 } 3300 3301 lmb.results = apr_hash_make(pool); 3302 lmb.pool = pool; 3303 3304 err = svn_repos_fs_unlock_many(b->repository->repos, targets, 3305 break_lock, lock_many_cb, &lmb, 3306 pool, subpool); 3307 3308 /* Return results in the same order as the paths were supplied. */ 3309 for (i = 0; i < unlock_tokens->nelts; ++i) 3310 { 3311 const char *path, *token, *full_path, *canonical_path; 3312 svn_ra_svn__item_t *item = &SVN_RA_SVN__LIST_ITEM(unlock_tokens, i); 3313 struct lock_result_t *result; 3314 3315 svn_pool_clear(subpool); 3316 3317 write_err = svn_ra_svn__parse_tuple(&item->u.list, "c(?c)", 3318 &path, &token); 3319 if (write_err) 3320 break; 3321 3322 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, 3323 subpool, subpool)); 3324 full_path = svn_fspath__join(b->repository->fs_path->data, 3325 canonical_path, pool); 3326 3327 result = svn_hash_gets(lmb.results, full_path); 3328 if (!result) 3329 result = svn_hash_gets(authz_results, full_path); 3330 if (!result) 3331 { 3332 /* No result? Something really odd happened, create a 3333 placeholder error so that any other results can be 3334 reported in the correct order. */ 3335 result = apr_palloc(pool, sizeof(struct lock_result_t)); 3336 result->err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED, 0, 3337 _("No result for '%s'."), path); 3338 svn_hash_sets(lmb.results, full_path, result); 3339 } 3340 3341 if (result->err) 3342 write_err = svn_ra_svn__write_cmd_failure(conn, pool, result->err); 3343 else 3344 write_err = svn_ra_svn__write_tuple(conn, subpool, "w(c)", "success", 3345 path); 3346 if (write_err) 3347 break; 3348 } 3349 3350 clear_lock_result_hash(authz_results, subpool); 3351 clear_lock_result_hash(lmb.results, subpool); 3352 3353 svn_pool_destroy(subpool); 3354 3355 if (!write_err) 3356 write_err = svn_ra_svn__write_word(conn, pool, "done"); 3357 if (! write_err) 3358 SVN_CMD_ERR(err); 3359 svn_error_clear(err); 3360 SVN_ERR(write_err); 3361 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 3362 3363 return SVN_NO_ERROR; 3364} 3365 3366static svn_error_t * 3367get_lock(svn_ra_svn_conn_t *conn, 3368 apr_pool_t *pool, 3369 svn_ra_svn__list_t *params, 3370 void *baton) 3371{ 3372 server_baton_t *b = baton; 3373 const char *path; 3374 const char *full_path; 3375 const char *canonical_path; 3376 svn_lock_t *l; 3377 3378 SVN_ERR(svn_ra_svn__parse_tuple(params, "c", &path)); 3379 3380 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, 3381 pool, pool)); 3382 full_path = svn_fspath__join(b->repository->fs_path->data, 3383 canonical_path, pool); 3384 3385 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, 3386 full_path, FALSE)); 3387 SVN_ERR(log_command(b, conn, pool, "get-lock %s", 3388 svn_path_uri_encode(full_path, pool))); 3389 3390 SVN_CMD_ERR(svn_fs_get_lock(&l, b->repository->fs, full_path, pool)); 3391 3392 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success")); 3393 if (l) 3394 SVN_ERR(write_lock(conn, pool, l)); 3395 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 3396 3397 return SVN_NO_ERROR; 3398} 3399 3400static svn_error_t * 3401get_locks(svn_ra_svn_conn_t *conn, 3402 apr_pool_t *pool, 3403 svn_ra_svn__list_t *params, 3404 void *baton) 3405{ 3406 server_baton_t *b = baton; 3407 const char *path; 3408 const char *full_path; 3409 const char *canonical_path; 3410 const char *depth_word; 3411 svn_depth_t depth; 3412 apr_hash_t *locks; 3413 apr_hash_index_t *hi; 3414 svn_error_t *err; 3415 authz_baton_t ab; 3416 3417 ab.server = b; 3418 ab.conn = conn; 3419 3420 SVN_ERR(svn_ra_svn__parse_tuple(params, "c?(?w)", &path, &depth_word)); 3421 3422 depth = depth_word ? svn_depth_from_word(depth_word) : svn_depth_infinity; 3423 if ((depth != svn_depth_empty) && 3424 (depth != svn_depth_files) && 3425 (depth != svn_depth_immediates) && 3426 (depth != svn_depth_infinity)) 3427 { 3428 err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, 3429 "Invalid 'depth' specified in get-locks request"); 3430 return log_fail_and_flush(err, b, conn, pool); 3431 } 3432 3433 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, 3434 pool, pool)); 3435 full_path = svn_fspath__join(b->repository->fs_path->data, 3436 canonical_path, pool); 3437 3438 SVN_ERR(trivial_auth_request(conn, pool, b)); 3439 3440 SVN_ERR(log_command(b, conn, pool, "get-locks %s", 3441 svn_path_uri_encode(full_path, pool))); 3442 SVN_CMD_ERR(svn_repos_fs_get_locks2(&locks, b->repository->repos, 3443 full_path, depth, 3444 authz_check_access_cb_func(b), &ab, 3445 pool)); 3446 3447 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success")); 3448 for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi)) 3449 { 3450 svn_lock_t *l = apr_hash_this_val(hi); 3451 3452 SVN_ERR(write_lock(conn, pool, l)); 3453 } 3454 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 3455 3456 return SVN_NO_ERROR; 3457} 3458 3459static svn_error_t *replay_one_revision(svn_ra_svn_conn_t *conn, 3460 server_baton_t *b, 3461 svn_revnum_t rev, 3462 svn_revnum_t low_water_mark, 3463 svn_boolean_t send_deltas, 3464 apr_pool_t *pool) 3465{ 3466 const svn_delta_editor_t *editor; 3467 void *edit_baton; 3468 svn_fs_root_t *root; 3469 svn_error_t *err; 3470 authz_baton_t ab; 3471 3472 ab.server = b; 3473 ab.conn = conn; 3474 3475 SVN_ERR(log_command(b, conn, pool, 3476 svn_log__replay(b->repository->fs_path->data, rev, 3477 pool))); 3478 3479 svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL); 3480 3481 err = svn_fs_revision_root(&root, b->repository->fs, rev, pool); 3482 3483 if (! err) 3484 err = svn_repos_replay2(root, b->repository->fs_path->data, 3485 low_water_mark, send_deltas, editor, edit_baton, 3486 authz_check_access_cb_func(b), &ab, pool); 3487 3488 if (err) 3489 svn_error_clear(editor->abort_edit(edit_baton, pool)); 3490 SVN_CMD_ERR(err); 3491 3492 return svn_ra_svn__write_cmd_finish_replay(conn, pool); 3493} 3494 3495static svn_error_t * 3496replay(svn_ra_svn_conn_t *conn, 3497 apr_pool_t *pool, 3498 svn_ra_svn__list_t *params, 3499 void *baton) 3500{ 3501 svn_revnum_t rev, low_water_mark; 3502 svn_boolean_t send_deltas; 3503 server_baton_t *b = baton; 3504 3505 SVN_ERR(svn_ra_svn__parse_tuple(params, "rrb", &rev, &low_water_mark, 3506 &send_deltas)); 3507 3508 SVN_ERR(trivial_auth_request(conn, pool, b)); 3509 3510 SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark, 3511 send_deltas, pool)); 3512 3513 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 3514 3515 return SVN_NO_ERROR; 3516} 3517 3518static svn_error_t * 3519replay_range(svn_ra_svn_conn_t *conn, 3520 apr_pool_t *pool, 3521 svn_ra_svn__list_t *params, 3522 void *baton) 3523{ 3524 svn_revnum_t start_rev, end_rev, rev, low_water_mark; 3525 svn_boolean_t send_deltas; 3526 server_baton_t *b = baton; 3527 apr_pool_t *iterpool; 3528 authz_baton_t ab; 3529 3530 ab.server = b; 3531 ab.conn = conn; 3532 3533 SVN_ERR(svn_ra_svn__parse_tuple(params, "rrrb", &start_rev, 3534 &end_rev, &low_water_mark, 3535 &send_deltas)); 3536 3537 SVN_ERR(trivial_auth_request(conn, pool, b)); 3538 3539 iterpool = svn_pool_create(pool); 3540 for (rev = start_rev; rev <= end_rev; rev++) 3541 { 3542 apr_hash_t *props; 3543 3544 svn_pool_clear(iterpool); 3545 3546 SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, 3547 b->repository->repos, rev, 3548 authz_check_access_cb_func(b), 3549 &ab, 3550 iterpool)); 3551 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "w(!", "revprops")); 3552 SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, props)); 3553 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!)")); 3554 3555 SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark, 3556 send_deltas, iterpool)); 3557 3558 } 3559 svn_pool_destroy(iterpool); 3560 3561 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 3562 3563 return SVN_NO_ERROR; 3564} 3565 3566static svn_error_t * 3567get_deleted_rev(svn_ra_svn_conn_t *conn, 3568 apr_pool_t *pool, 3569 svn_ra_svn__list_t *params, 3570 void *baton) 3571{ 3572 server_baton_t *b = baton; 3573 const char *path, *full_path, *canonical_path; 3574 svn_revnum_t peg_revision; 3575 svn_revnum_t end_revision; 3576 svn_revnum_t revision_deleted; 3577 3578 SVN_ERR(svn_ra_svn__parse_tuple(params, "crr", 3579 &path, &peg_revision, &end_revision)); 3580 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, 3581 pool, pool)); 3582 full_path = svn_fspath__join(b->repository->fs_path->data, 3583 canonical_path, pool); 3584 SVN_ERR(log_command(b, conn, pool, "get-deleted-rev")); 3585 SVN_ERR(trivial_auth_request(conn, pool, b)); 3586 SVN_CMD_ERR(svn_repos_deleted_rev(b->repository->fs, full_path, peg_revision, 3587 end_revision, &revision_deleted, pool)); 3588 3589 /* The protocol does not allow for a reply of SVN_INVALID_REVNUM directly. 3590 Instead, return SVN_ERR_ENTRY_MISSING_REVISION. A new enough client 3591 knows that this means the answer to the query is SVN_INVALID_REVNUM. 3592 (An older client reports this as an error.) */ 3593 if (revision_deleted == SVN_INVALID_REVNUM) 3594 SVN_CMD_ERR(svn_error_createf(SVN_ERR_ENTRY_MISSING_REVISION, NULL, 3595 "svn protocol command 'get-deleted-rev': " 3596 "path '%s' was not deleted in r%ld-%ld; " 3597 "NOTE: newer clients handle this case " 3598 "and do not report it as an error", 3599 full_path, peg_revision, end_revision)); 3600 3601 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", revision_deleted)); 3602 return SVN_NO_ERROR; 3603} 3604 3605static svn_error_t * 3606get_inherited_props(svn_ra_svn_conn_t *conn, 3607 apr_pool_t *pool, 3608 svn_ra_svn__list_t *params, 3609 void *baton) 3610{ 3611 server_baton_t *b = baton; 3612 const char *path, *full_path, *canonical_path; 3613 svn_revnum_t rev; 3614 svn_fs_root_t *root; 3615 apr_array_header_t *inherited_props; 3616 int i; 3617 apr_pool_t *iterpool = svn_pool_create(pool); 3618 authz_baton_t ab; 3619 svn_node_kind_t node_kind; 3620 3621 ab.server = b; 3622 ab.conn = conn; 3623 3624 /* Parse arguments. */ 3625 SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)", &path, &rev)); 3626 3627 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, 3628 iterpool, iterpool)); 3629 full_path = svn_fspath__join(b->repository->fs_path->data, 3630 canonical_path, pool); 3631 3632 /* Check authorizations */ 3633 SVN_ERR(must_have_access(conn, iterpool, b, svn_authz_read, 3634 full_path, FALSE)); 3635 3636 SVN_ERR(log_command(b, conn, pool, "%s", 3637 svn_log__get_inherited_props(full_path, rev, 3638 iterpool))); 3639 3640 /* Fetch the properties and a stream for the contents. */ 3641 SVN_CMD_ERR(svn_fs_revision_root(&root, b->repository->fs, rev, iterpool)); 3642 SVN_CMD_ERR(svn_fs_check_path(&node_kind, root, full_path, pool)); 3643 if (node_kind == svn_node_none) 3644 { 3645 SVN_CMD_ERR(svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 3646 _("'%s' path not found"), full_path)); 3647 } 3648 SVN_CMD_ERR(get_props(NULL, &inherited_props, &ab, root, full_path, pool)); 3649 3650 /* Send successful command response with revision and props. */ 3651 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "w(!", "success")); 3652 3653 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(?!")); 3654 3655 for (i = 0; i < inherited_props->nelts; i++) 3656 { 3657 svn_prop_inherited_item_t *iprop = 3658 APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *); 3659 3660 svn_pool_clear(iterpool); 3661 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!", 3662 iprop->path_or_url)); 3663 SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash)); 3664 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!", 3665 iprop->path_or_url)); 3666 } 3667 3668 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))")); 3669 svn_pool_destroy(iterpool); 3670 return SVN_NO_ERROR; 3671} 3672 3673/* Baton type to be used with list_receiver. */ 3674typedef struct list_receiver_baton_t 3675{ 3676 /* Send the data through this connection. */ 3677 svn_ra_svn_conn_t *conn; 3678 3679 /* Send the field selected by these flags. */ 3680 apr_uint32_t dirent_fields; 3681} list_receiver_baton_t; 3682 3683/* Implements svn_repos_dirent_receiver_t, sending DIRENT and PATH to the 3684 * client. BATON must be a list_receiver_baton_t. */ 3685static svn_error_t * 3686list_receiver(const char *path, 3687 svn_dirent_t *dirent, 3688 void *baton, 3689 apr_pool_t *pool) 3690{ 3691 list_receiver_baton_t *b = baton; 3692 return svn_error_trace(svn_ra_svn__write_dirent(b->conn, pool, path, dirent, 3693 b->dirent_fields)); 3694} 3695 3696static svn_error_t * 3697list(svn_ra_svn_conn_t *conn, 3698 apr_pool_t *pool, 3699 svn_ra_svn__list_t *params, 3700 void *baton) 3701{ 3702 server_baton_t *b = baton; 3703 const char *path, *full_path, *canonical_path; 3704 svn_revnum_t rev; 3705 svn_depth_t depth; 3706 apr_array_header_t *patterns = NULL; 3707 svn_fs_root_t *root; 3708 const char *depth_word; 3709 svn_boolean_t path_info_only; 3710 svn_ra_svn__list_t *dirent_fields_list = NULL; 3711 svn_ra_svn__list_t *patterns_list = NULL; 3712 int i; 3713 list_receiver_baton_t rb; 3714 svn_error_t *err, *write_err; 3715 3716 authz_baton_t ab; 3717 ab.server = b; 3718 ab.conn = conn; 3719 3720 /* Read the command parameters. */ 3721 SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)w?l?l", &path, &rev, 3722 &depth_word, &dirent_fields_list, 3723 &patterns_list)); 3724 3725 rb.conn = conn; 3726 SVN_ERR(parse_dirent_fields(&rb.dirent_fields, dirent_fields_list)); 3727 3728 depth = svn_depth_from_word(depth_word); 3729 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, 3730 pool, pool)); 3731 full_path = svn_fspath__join(b->repository->fs_path->data, 3732 canonical_path, pool); 3733 3734 /* Read the patterns list. */ 3735 if (patterns_list) 3736 { 3737 patterns = apr_array_make(pool, 0, sizeof(const char *)); 3738 for (i = 0; i < patterns_list->nelts; ++i) 3739 { 3740 svn_ra_svn__item_t *elt = &SVN_RA_SVN__LIST_ITEM(patterns_list, i); 3741 3742 if (elt->kind != SVN_RA_SVN_STRING) 3743 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 3744 "Pattern field not a string"); 3745 3746 APR_ARRAY_PUSH(patterns, const char *) = elt->u.string.data; 3747 } 3748 } 3749 3750 /* Check authorizations */ 3751 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, 3752 full_path, FALSE)); 3753 3754 if (!SVN_IS_VALID_REVNUM(rev)) 3755 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool)); 3756 3757 SVN_ERR(log_command(b, conn, pool, "%s", 3758 svn_log__list(full_path, rev, patterns, depth, 3759 rb.dirent_fields, pool))); 3760 3761 /* Fetch the root of the appropriate revision. */ 3762 SVN_CMD_ERR(svn_fs_revision_root(&root, b->repository->fs, rev, pool)); 3763 3764 /* Fetch the directory entries if requested and send them immediately. */ 3765 path_info_only = (rb.dirent_fields & ~SVN_DIRENT_KIND) == 0; 3766 err = svn_repos_list(root, full_path, patterns, depth, path_info_only, 3767 authz_check_access_cb_func(b), &ab, list_receiver, 3768 &rb, NULL, NULL, pool); 3769 3770 3771 /* Finish response. */ 3772 write_err = svn_ra_svn__write_word(conn, pool, "done"); 3773 if (write_err) 3774 { 3775 svn_error_clear(err); 3776 return write_err; 3777 } 3778 SVN_CMD_ERR(err); 3779 3780 return svn_error_trace(svn_ra_svn__write_cmd_response(conn, pool, "")); 3781} 3782 3783static const svn_ra_svn__cmd_entry_t main_commands[] = { 3784 { "reparent", reparent }, 3785 { "get-latest-rev", get_latest_rev }, 3786 { "get-dated-rev", get_dated_rev }, 3787 { "change-rev-prop", change_rev_prop }, 3788 { "change-rev-prop2",change_rev_prop2 }, 3789 { "rev-proplist", rev_proplist }, 3790 { "rev-prop", rev_prop }, 3791 { "commit", commit }, 3792 { "get-file", get_file }, 3793 { "get-dir", get_dir }, 3794 { "update", update }, 3795 { "switch", switch_cmd }, 3796 { "status", status }, 3797 { "diff", diff }, 3798 { "get-mergeinfo", get_mergeinfo }, 3799 { "log", log_cmd }, 3800 { "check-path", check_path }, 3801 { "stat", stat_cmd }, 3802 { "get-locations", get_locations }, 3803 { "get-location-segments", get_location_segments }, 3804 { "get-file-revs", get_file_revs }, 3805 { "lock", lock }, 3806 { "lock-many", lock_many }, 3807 { "unlock", unlock }, 3808 { "unlock-many", unlock_many }, 3809 { "get-lock", get_lock }, 3810 { "get-locks", get_locks }, 3811 { "replay", replay }, 3812 { "replay-range", replay_range }, 3813 { "get-deleted-rev", get_deleted_rev }, 3814 { "get-iprops", get_inherited_props }, 3815 { "list", list }, 3816 { NULL } 3817}; 3818 3819/* Skip past the scheme part of a URL, including the tunnel specification 3820 * if present. Return NULL if the scheme part is invalid for ra_svn. */ 3821static const char *skip_scheme_part(const char *url) 3822{ 3823 if (strncmp(url, "svn", 3) != 0) 3824 return NULL; 3825 url += 3; 3826 if (*url == '+') 3827 url += strcspn(url, ":"); 3828 if (strncmp(url, "://", 3) != 0) 3829 return NULL; 3830 return url + 3; 3831} 3832 3833/* Check that PATH is a valid repository path, meaning it doesn't contain any 3834 '..' path segments. 3835 NOTE: This is similar to svn_path_is_backpath_present, but that function 3836 assumes the path separator is '/'. This function also checks for 3837 segments delimited by the local path separator. */ 3838static svn_boolean_t 3839repos_path_valid(const char *path) 3840{ 3841 const char *s = path; 3842 3843 while (*s) 3844 { 3845 /* Scan for the end of the segment. */ 3846 while (*path && *path != '/' && *path != SVN_PATH_LOCAL_SEPARATOR) 3847 ++path; 3848 3849 /* Check for '..'. */ 3850#ifdef WIN32 3851 /* On Windows, don't allow sequences of more than one character 3852 consisting of just dots and spaces. Win32 functions treat 3853 paths such as ".. " and "......." inconsistently. Make sure 3854 no one can escape out of the root. */ 3855 if (path - s >= 2 && strspn(s, ". ") == (size_t)(path - s)) 3856 return FALSE; 3857#else /* ! WIN32 */ 3858 if (path - s == 2 && s[0] == '.' && s[1] == '.') 3859 return FALSE; 3860#endif 3861 3862 /* Skip all separators. */ 3863 while (*path && (*path == '/' || *path == SVN_PATH_LOCAL_SEPARATOR)) 3864 ++path; 3865 s = path; 3866 } 3867 3868 return TRUE; 3869} 3870 3871/* Look for the repository given by URL, using ROOT as the virtual 3872 * repository root. If we find one, fill in the repos, fs, repos_url, 3873 * and fs_path fields of REPOSITORY. VHOST and READ_ONLY flags are the 3874 * same as in the server baton. 3875 * 3876 * CONFIG_POOL shall be used to load config objects. 3877 * 3878 * Use SCRATCH_POOL for temporary allocations. 3879 * 3880 */ 3881static svn_error_t * 3882find_repos(const char *url, 3883 const char *root, 3884 svn_boolean_t vhost, 3885 svn_boolean_t read_only, 3886 svn_config_t *cfg, 3887 repository_t *repository, 3888 svn_repos__config_pool_t *config_pool, 3889 apr_hash_t *fs_config, 3890 svn_repos_authz_warning_func_t authz_warning_func, 3891 void *authz_warning_baton, 3892 apr_pool_t *result_pool, 3893 apr_pool_t *scratch_pool) 3894{ 3895 const char *path, *full_path, *fs_path, *hooks_env, *canonical_path; 3896 const char *canonical_root; 3897 svn_stringbuf_t *url_buf; 3898 svn_boolean_t sasl_requested; 3899 3900 /* Skip past the scheme and authority part. */ 3901 path = skip_scheme_part(url); 3902 if (path == NULL) 3903 return svn_error_createf(SVN_ERR_BAD_URL, NULL, 3904 "Non-svn URL passed to svn server: '%s'", url); 3905 3906 if (! vhost) 3907 { 3908 path = strchr(path, '/'); 3909 if (path == NULL) 3910 path = ""; 3911 } 3912 SVN_ERR(svn_relpath_canonicalize_safe(&canonical_path, NULL, path, 3913 scratch_pool, scratch_pool)); 3914 path = svn_path_uri_decode(canonical_path, scratch_pool); 3915 3916 /* Ensure that it isn't possible to escape the root by disallowing 3917 '..' segments. */ 3918 if (!repos_path_valid(path)) 3919 return svn_error_create(SVN_ERR_BAD_FILENAME, NULL, 3920 "Couldn't determine repository path"); 3921 3922 /* Join the server-configured root with the client path. */ 3923 SVN_ERR(svn_dirent_canonicalize_safe(&canonical_root, NULL, root, 3924 scratch_pool, scratch_pool)); 3925 full_path = svn_dirent_join(canonical_root, path, scratch_pool); 3926 3927 /* Search for a repository in the full path. */ 3928 repository->repos_root = svn_repos_find_root_path(full_path, result_pool); 3929 if (!repository->repos_root) 3930 return svn_error_createf(SVN_ERR_RA_SVN_REPOS_NOT_FOUND, NULL, 3931 "No repository found in '%s'", url); 3932 3933 /* Open the repository and fill in b with the resulting information. */ 3934 SVN_ERR(svn_repos_open3(&repository->repos, repository->repos_root, 3935 fs_config, result_pool, scratch_pool)); 3936 SVN_ERR(svn_repos_remember_client_capabilities(repository->repos, 3937 repository->capabilities)); 3938 repository->fs = svn_repos_fs(repository->repos); 3939 fs_path = full_path + strlen(repository->repos_root); 3940 repository->fs_path = svn_stringbuf_create(*fs_path ? fs_path : "/", 3941 result_pool); 3942 url_buf = svn_stringbuf_create(url, result_pool); 3943 svn_path_remove_components(url_buf, 3944 svn_path_component_count(repository->fs_path->data)); 3945 repository->repos_url = url_buf->data; 3946 repository->authz_repos_name = svn_dirent_is_child(canonical_root, 3947 repository->repos_root, 3948 result_pool); 3949 if (repository->authz_repos_name == NULL) 3950 repository->repos_name = svn_dirent_basename(repository->repos_root, 3951 result_pool); 3952 else 3953 repository->repos_name = repository->authz_repos_name; 3954 repository->repos_name = svn_path_uri_encode(repository->repos_name, 3955 result_pool); 3956 3957 /* If the svnserve configuration has not been loaded then load it from the 3958 * repository. */ 3959 if (NULL == cfg) 3960 { 3961 repository->base = svn_repos_conf_dir(repository->repos, result_pool); 3962 3963 SVN_ERR(svn_repos__config_pool_get(&cfg, config_pool, 3964 svn_repos_svnserve_conf 3965 (repository->repos, result_pool), 3966 FALSE, repository->repos, 3967 result_pool)); 3968 } 3969 3970 SVN_ERR(load_pwdb_config(repository, cfg, config_pool, result_pool)); 3971 SVN_ERR(load_authz_config(repository, repository->repos_root, cfg, 3972 authz_warning_func, authz_warning_baton, 3973 result_pool, scratch_pool)); 3974 3975 /* Should we use Cyrus SASL? */ 3976 SVN_ERR(svn_config_get_bool(cfg, &sasl_requested, 3977 SVN_CONFIG_SECTION_SASL, 3978 SVN_CONFIG_OPTION_USE_SASL, FALSE)); 3979 if (sasl_requested) 3980 { 3981#ifdef SVN_HAVE_SASL 3982 const char *val; 3983 3984 repository->use_sasl = sasl_requested; 3985 3986 svn_config_get(cfg, &val, SVN_CONFIG_SECTION_SASL, 3987 SVN_CONFIG_OPTION_MIN_SSF, "0"); 3988 SVN_ERR(svn_cstring_atoui(&repository->min_ssf, val)); 3989 3990 svn_config_get(cfg, &val, SVN_CONFIG_SECTION_SASL, 3991 SVN_CONFIG_OPTION_MAX_SSF, "256"); 3992 SVN_ERR(svn_cstring_atoui(&repository->max_ssf, val)); 3993#else /* !SVN_HAVE_SASL */ 3994 return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL, 3995 _("SASL requested but not compiled in; " 3996 "set '%s' to 'false' or recompile " 3997 "svnserve with SASL support"), 3998 SVN_CONFIG_OPTION_USE_SASL); 3999#endif /* SVN_HAVE_SASL */ 4000 } 4001 else 4002 { 4003 repository->use_sasl = FALSE; 4004 } 4005 4006 /* Use the repository UUID as the default realm. */ 4007 SVN_ERR(svn_fs_get_uuid(repository->fs, &repository->realm, scratch_pool)); 4008 svn_config_get(cfg, &repository->realm, SVN_CONFIG_SECTION_GENERAL, 4009 SVN_CONFIG_OPTION_REALM, repository->realm); 4010 repository->realm = apr_pstrdup(result_pool, repository->realm); 4011 4012 /* Make sure it's possible for the client to authenticate. Note 4013 that this doesn't take into account any authz configuration read 4014 above, because we can't know about access it grants until paths 4015 are given by the client. */ 4016 set_access(repository, cfg, read_only); 4017 4018 /* Configure hook script environment variables. */ 4019 svn_config_get(cfg, &hooks_env, SVN_CONFIG_SECTION_GENERAL, 4020 SVN_CONFIG_OPTION_HOOKS_ENV, NULL); 4021 if (hooks_env) 4022 hooks_env = svn_dirent_internal_style(hooks_env, scratch_pool); 4023 4024 SVN_ERR(svn_repos_hooks_setenv(repository->repos, hooks_env, scratch_pool)); 4025 repository->hooks_env = apr_pstrdup(result_pool, hooks_env); 4026 4027 return SVN_NO_ERROR; 4028} 4029 4030/* Compute the authentication name EXTERNAL should be able to get, if any. */ 4031static const char *get_tunnel_user(serve_params_t *params, apr_pool_t *pool) 4032{ 4033 /* Only offer EXTERNAL for connections tunneled over a login agent. */ 4034 if (!params->tunnel) 4035 return NULL; 4036 4037 /* If a tunnel user was provided on the command line, use that. */ 4038 if (params->tunnel_user) 4039 return params->tunnel_user; 4040 4041 return svn_user_get_name(pool); 4042} 4043 4044static void 4045fs_warning_func(void *baton, svn_error_t *err) 4046{ 4047 fs_warning_baton_t *b = baton; 4048 log_error(err, b->server); 4049} 4050 4051/* Return the normalized repository-relative path for the given PATH 4052 * (may be a URL, full path or relative path) and fs contained in the 4053 * server baton BATON. Allocate the result in POOL. 4054 */ 4055static const char * 4056get_normalized_repo_rel_path(void *baton, 4057 const char *path, 4058 apr_pool_t *pool) 4059{ 4060 server_baton_t *sb = baton; 4061 4062 if (svn_path_is_url(path)) 4063 { 4064 /* This is a copyfrom URL. */ 4065 path = svn_uri_skip_ancestor(sb->repository->repos_url, path, pool); 4066 path = svn_fspath__canonicalize(path, pool); 4067 } 4068 else 4069 { 4070 /* This is a base-relative path. */ 4071 if ((path)[0] != '/') 4072 /* Get an absolute path for use in the FS. */ 4073 path = svn_fspath__join(sb->repository->fs_path->data, path, pool); 4074 } 4075 4076 return path; 4077} 4078 4079/* Get the revision root for REVISION in fs given by server baton BATON 4080 * and return it in *FS_ROOT. Use HEAD if REVISION is SVN_INVALID_REVNUM. 4081 * Use POOL for allocations. 4082 */ 4083static svn_error_t * 4084get_revision_root(svn_fs_root_t **fs_root, 4085 void *baton, 4086 svn_revnum_t revision, 4087 apr_pool_t *pool) 4088{ 4089 server_baton_t *sb = baton; 4090 4091 if (!SVN_IS_VALID_REVNUM(revision)) 4092 SVN_ERR(svn_fs_youngest_rev(&revision, sb->repository->fs, pool)); 4093 4094 SVN_ERR(svn_fs_revision_root(fs_root, sb->repository->fs, revision, pool)); 4095 4096 return SVN_NO_ERROR; 4097} 4098 4099static svn_error_t * 4100fetch_props_func(apr_hash_t **props, 4101 void *baton, 4102 const char *path, 4103 svn_revnum_t base_revision, 4104 apr_pool_t *result_pool, 4105 apr_pool_t *scratch_pool) 4106{ 4107 svn_fs_root_t *fs_root; 4108 svn_error_t *err; 4109 4110 path = get_normalized_repo_rel_path(baton, path, scratch_pool); 4111 SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool)); 4112 4113 err = svn_fs_node_proplist(props, fs_root, path, result_pool); 4114 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 4115 { 4116 svn_error_clear(err); 4117 *props = apr_hash_make(result_pool); 4118 return SVN_NO_ERROR; 4119 } 4120 else if (err) 4121 return svn_error_trace(err); 4122 4123 return SVN_NO_ERROR; 4124} 4125 4126static svn_error_t * 4127fetch_kind_func(svn_node_kind_t *kind, 4128 void *baton, 4129 const char *path, 4130 svn_revnum_t base_revision, 4131 apr_pool_t *scratch_pool) 4132{ 4133 svn_fs_root_t *fs_root; 4134 4135 path = get_normalized_repo_rel_path(baton, path, scratch_pool); 4136 SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool)); 4137 4138 SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool)); 4139 4140 return SVN_NO_ERROR; 4141} 4142 4143static svn_error_t * 4144fetch_base_func(const char **filename, 4145 void *baton, 4146 const char *path, 4147 svn_revnum_t base_revision, 4148 apr_pool_t *result_pool, 4149 apr_pool_t *scratch_pool) 4150{ 4151 svn_stream_t *contents; 4152 svn_stream_t *file_stream; 4153 const char *tmp_filename; 4154 svn_fs_root_t *fs_root; 4155 svn_error_t *err; 4156 4157 path = get_normalized_repo_rel_path(baton, path, scratch_pool); 4158 SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool)); 4159 4160 err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool); 4161 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 4162 { 4163 svn_error_clear(err); 4164 *filename = NULL; 4165 return SVN_NO_ERROR; 4166 } 4167 else if (err) 4168 return svn_error_trace(err); 4169 SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL, 4170 svn_io_file_del_on_pool_cleanup, 4171 scratch_pool, scratch_pool)); 4172 SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool)); 4173 4174 *filename = apr_pstrdup(result_pool, tmp_filename); 4175 4176 return SVN_NO_ERROR; 4177} 4178 4179client_info_t * 4180get_client_info(svn_ra_svn_conn_t *conn, 4181 serve_params_t *params, 4182 apr_pool_t *pool) 4183{ 4184 client_info_t *client_info = apr_pcalloc(pool, sizeof(*client_info)); 4185 4186 client_info->tunnel = params->tunnel; 4187 client_info->tunnel_user = get_tunnel_user(params, pool); 4188 client_info->user = NULL; 4189 client_info->authz_user = NULL; 4190 client_info->remote_host = svn_ra_svn_conn_remote_host(conn); 4191 4192 return client_info; 4193} 4194 4195static void 4196handle_authz_warning(void *baton, 4197 const svn_error_t *err, 4198 apr_pool_t *scratch_pool) 4199{ 4200 server_baton_t *const server_baton = baton; 4201 log_warning(err, server_baton); 4202 SVN_UNUSED(scratch_pool); 4203} 4204 4205/* Construct the server baton for CONN using PARAMS and return it in *BATON. 4206 * It's lifetime is the same as that of CONN. SCRATCH_POOL 4207 */ 4208static svn_error_t * 4209construct_server_baton(server_baton_t **baton, 4210 svn_ra_svn_conn_t *conn, 4211 serve_params_t *params, 4212 apr_pool_t *scratch_pool) 4213{ 4214 svn_error_t *err; 4215 apr_uint64_t ver; 4216 const char *client_url, *ra_client_string, *client_string, *canonical_url; 4217 svn_ra_svn__list_t *caplist; 4218 apr_pool_t *conn_pool = svn_ra_svn__get_pool(conn); 4219 server_baton_t *b = apr_pcalloc(conn_pool, sizeof(*b)); 4220 fs_warning_baton_t *warn_baton; 4221 svn_stringbuf_t *cap_log = svn_stringbuf_create_empty(scratch_pool); 4222 4223 b->repository = apr_pcalloc(conn_pool, sizeof(*b->repository)); 4224 b->repository->username_case = params->username_case; 4225 b->repository->base = params->base; 4226 b->repository->pwdb = NULL; 4227 b->repository->authzdb = NULL; 4228 b->repository->realm = NULL; 4229 b->repository->use_sasl = FALSE; 4230 4231 b->read_only = params->read_only; 4232 b->pool = conn_pool; 4233 b->vhost = params->vhost; 4234 4235 b->logger = params->logger; 4236 b->client_info = get_client_info(conn, params, conn_pool); 4237 4238 /* Send greeting. We don't support version 1 any more, so we can 4239 * send an empty mechlist. */ 4240 if (params->compression_level > 0) 4241 SVN_ERR(svn_ra_svn__write_cmd_response(conn, scratch_pool, 4242 "nn()(wwwwwwwwwwwww)", 4243 (apr_uint64_t) 2, (apr_uint64_t) 2, 4244 SVN_RA_SVN_CAP_EDIT_PIPELINE, 4245 SVN_RA_SVN_CAP_SVNDIFF1, 4246 SVN_RA_SVN_CAP_SVNDIFF2_ACCEPTED, 4247 SVN_RA_SVN_CAP_ABSENT_ENTRIES, 4248 SVN_RA_SVN_CAP_COMMIT_REVPROPS, 4249 SVN_RA_SVN_CAP_DEPTH, 4250 SVN_RA_SVN_CAP_LOG_REVPROPS, 4251 SVN_RA_SVN_CAP_ATOMIC_REVPROPS, 4252 SVN_RA_SVN_CAP_PARTIAL_REPLAY, 4253 SVN_RA_SVN_CAP_INHERITED_PROPS, 4254 SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS, 4255 SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE, 4256 SVN_RA_SVN_CAP_LIST 4257 )); 4258 else 4259 SVN_ERR(svn_ra_svn__write_cmd_response(conn, scratch_pool, 4260 "nn()(wwwwwwwwwww)", 4261 (apr_uint64_t) 2, (apr_uint64_t) 2, 4262 SVN_RA_SVN_CAP_EDIT_PIPELINE, 4263 SVN_RA_SVN_CAP_ABSENT_ENTRIES, 4264 SVN_RA_SVN_CAP_COMMIT_REVPROPS, 4265 SVN_RA_SVN_CAP_DEPTH, 4266 SVN_RA_SVN_CAP_LOG_REVPROPS, 4267 SVN_RA_SVN_CAP_ATOMIC_REVPROPS, 4268 SVN_RA_SVN_CAP_PARTIAL_REPLAY, 4269 SVN_RA_SVN_CAP_INHERITED_PROPS, 4270 SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS, 4271 SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE, 4272 SVN_RA_SVN_CAP_LIST 4273 )); 4274 4275 /* Read client response, which we assume to be in version 2 format: 4276 * version, capability list, and client URL; then we do an auth 4277 * request. */ 4278 SVN_ERR(svn_ra_svn__read_tuple(conn, scratch_pool, "nlc?c(?c)", 4279 &ver, &caplist, &client_url, 4280 &ra_client_string, 4281 &client_string)); 4282 if (ver != 2) 4283 return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL, 4284 "Unsupported ra_svn protocol version" 4285 " %"APR_UINT64_T_FMT 4286 " (supported versions: [2])", ver); 4287 4288 SVN_ERR(svn_uri_canonicalize_safe(&canonical_url, NULL, client_url, 4289 conn_pool, scratch_pool)); 4290 client_url = canonical_url; 4291 SVN_ERR(svn_ra_svn__set_capabilities(conn, caplist)); 4292 4293 /* All released versions of Subversion support edit-pipeline, 4294 * so we do not accept connections from clients that do not. */ 4295 if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE)) 4296 return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL, 4297 "Missing edit-pipeline capability"); 4298 4299 /* find_repos needs the capabilities as a list of words (eventually 4300 they get handed to the start-commit hook). While we could add a 4301 new interface to re-retrieve them from conn and convert the 4302 result to a list, it's simpler to just convert caplist by hand 4303 here, since we already have it and turning 'svn_ra_svn__item_t's 4304 into 'const char *'s is pretty easy. 4305 4306 We only record capabilities we care about. The client may report 4307 more (because it doesn't know what the server cares about). */ 4308 { 4309 int i; 4310 svn_ra_svn__item_t *item; 4311 4312 b->repository->capabilities = apr_array_make(conn_pool, 1, 4313 sizeof(const char *)); 4314 for (i = 0; i < caplist->nelts; i++) 4315 { 4316 static const svn_string_t str_cap_mergeinfo 4317 = SVN__STATIC_STRING(SVN_RA_SVN_CAP_MERGEINFO); 4318 4319 item = &SVN_RA_SVN__LIST_ITEM(caplist, i); 4320 /* ra_svn_set_capabilities() already type-checked for us */ 4321 if (svn_string_compare(&item->u.word, &str_cap_mergeinfo)) 4322 { 4323 APR_ARRAY_PUSH(b->repository->capabilities, const char *) 4324 = SVN_RA_CAPABILITY_MERGEINFO; 4325 } 4326 /* Save for operational log. */ 4327 if (cap_log->len > 0) 4328 svn_stringbuf_appendcstr(cap_log, " "); 4329 svn_stringbuf_appendcstr(cap_log, item->u.word.data); 4330 } 4331 } 4332 4333 /* (*b) has the logger, repository and client_info set, so it can 4334 be used as the authz_warning_baton that eventyally gets passed 4335 to log_warning(). */ 4336 err = handle_config_error(find_repos(client_url, params->root, b->vhost, 4337 b->read_only, params->cfg, 4338 b->repository, params->config_pool, 4339 params->fs_config, 4340 handle_authz_warning, b, 4341 conn_pool, scratch_pool), 4342 b); 4343 if (!err) 4344 { 4345 if (b->repository->anon_access == NO_ACCESS 4346 && (b->repository->auth_access == NO_ACCESS 4347 || (!b->client_info->tunnel_user && !b->repository->pwdb 4348 && !b->repository->use_sasl))) 4349 err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 4350 "No access allowed to this repository", 4351 b); 4352 } 4353 if (!err) 4354 { 4355 SVN_ERR(auth_request(conn, scratch_pool, b, READ_ACCESS, FALSE)); 4356 if (current_access(b) == NO_ACCESS) 4357 err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 4358 "Not authorized for access", b); 4359 } 4360 if (err) 4361 { 4362 /* Report these errors to the client before closing the connection. */ 4363 err = svn_error_compose_create(err, 4364 svn_ra_svn__write_cmd_failure(conn, scratch_pool, err)); 4365 err = svn_error_compose_create(err, 4366 svn_ra_svn__flush(conn, scratch_pool)); 4367 return err; 4368 } 4369 4370 SVN_ERR(svn_fs_get_uuid(b->repository->fs, &b->repository->uuid, 4371 conn_pool)); 4372 4373 /* We can't claim mergeinfo capability until we know whether the 4374 repository supports mergeinfo (i.e., is not a 1.4 repository), 4375 but we don't get the repository url from the client until after 4376 we've already sent the initial list of server capabilities. So 4377 we list repository capabilities here, in our first response after 4378 the client has sent the url. */ 4379 { 4380 svn_boolean_t supports_mergeinfo; 4381 SVN_ERR(svn_repos_has_capability(b->repository->repos, 4382 &supports_mergeinfo, 4383 SVN_REPOS_CAPABILITY_MERGEINFO, 4384 scratch_pool)); 4385 4386 SVN_ERR(svn_ra_svn__write_tuple(conn, scratch_pool, "w(cc(!", 4387 "success", b->repository->uuid, 4388 b->repository->repos_url)); 4389 if (supports_mergeinfo) 4390 SVN_ERR(svn_ra_svn__write_word(conn, scratch_pool, 4391 SVN_RA_SVN_CAP_MERGEINFO)); 4392 SVN_ERR(svn_ra_svn__write_tuple(conn, scratch_pool, "!))")); 4393 SVN_ERR(svn_ra_svn__flush(conn, scratch_pool)); 4394 } 4395 4396 /* Log the open. */ 4397 if (ra_client_string == NULL || ra_client_string[0] == '\0') 4398 ra_client_string = "-"; 4399 else 4400 ra_client_string = svn_path_uri_encode(ra_client_string, scratch_pool); 4401 if (client_string == NULL || client_string[0] == '\0') 4402 client_string = "-"; 4403 else 4404 client_string = svn_path_uri_encode(client_string, scratch_pool); 4405 SVN_ERR(log_command(b, conn, scratch_pool, 4406 "open %" APR_UINT64_T_FMT " cap=(%s) %s %s %s", 4407 ver, cap_log->data, 4408 svn_path_uri_encode(b->repository->fs_path->data, 4409 scratch_pool), 4410 ra_client_string, client_string)); 4411 4412 warn_baton = apr_pcalloc(conn_pool, sizeof(*warn_baton)); 4413 warn_baton->server = b; 4414 warn_baton->conn = conn; 4415 svn_fs_set_warning_func(b->repository->fs, fs_warning_func, warn_baton); 4416 4417 /* Set up editor shims. */ 4418 { 4419 svn_delta_shim_callbacks_t *callbacks = 4420 svn_delta_shim_callbacks_default(conn_pool); 4421 4422 callbacks->fetch_base_func = fetch_base_func; 4423 callbacks->fetch_props_func = fetch_props_func; 4424 callbacks->fetch_kind_func = fetch_kind_func; 4425 callbacks->fetch_baton = b; 4426 4427 SVN_ERR(svn_ra_svn__set_shim_callbacks(conn, callbacks)); 4428 } 4429 4430 *baton = b; 4431 4432 return SVN_NO_ERROR; 4433} 4434 4435svn_error_t * 4436serve_interruptable(svn_boolean_t *terminate_p, 4437 connection_t *connection, 4438 svn_boolean_t (* is_busy)(connection_t *), 4439 apr_pool_t *pool) 4440{ 4441 svn_boolean_t terminate = FALSE; 4442 svn_error_t *err = NULL; 4443 const svn_ra_svn__cmd_entry_t *command; 4444 apr_pool_t *iterpool = svn_pool_create(pool); 4445 4446 /* Prepare command parser. */ 4447 apr_hash_t *cmd_hash = apr_hash_make(pool); 4448 for (command = main_commands; command->cmdname; command++) 4449 svn_hash_sets(cmd_hash, command->cmdname, command); 4450 4451 /* Auto-initialize connection */ 4452 if (! connection->conn) 4453 { 4454 apr_status_t ar; 4455 4456 /* Enable TCP keep-alives on the socket so we time out when 4457 * the connection breaks due to network-layer problems. 4458 * If the peer has dropped the connection due to a network partition 4459 * or a crash, or if the peer no longer considers the connection 4460 * valid because we are behind a NAT and our public IP has changed, 4461 * it will respond to the keep-alive probe with a RST instead of an 4462 * acknowledgment segment, which will cause svn to abort the session 4463 * even while it is currently blocked waiting for data from the peer. */ 4464 ar = apr_socket_opt_set(connection->usock, APR_SO_KEEPALIVE, 1); 4465 if (ar) 4466 { 4467 /* It's not a fatal error if we cannot enable keep-alives. */ 4468 } 4469 4470 /* create the connection, configure ports etc. */ 4471 connection->conn 4472 = svn_ra_svn_create_conn5(connection->usock, NULL, NULL, 4473 connection->params->compression_level, 4474 connection->params->zero_copy_limit, 4475 connection->params->error_check_interval, 4476 connection->params->max_request_size, 4477 connection->params->max_response_size, 4478 connection->pool); 4479 4480 /* Construct server baton and open the repository for the first time. */ 4481 err = construct_server_baton(&connection->baton, connection->conn, 4482 connection->params, pool); 4483 } 4484 4485 /* If we can't access the repo for some reason, end this connection. */ 4486 if (err) 4487 terminate = TRUE; 4488 4489 /* Process incoming commands. */ 4490 while (!terminate && !err) 4491 { 4492 svn_pool_clear(iterpool); 4493 if (is_busy && is_busy(connection)) 4494 { 4495 svn_boolean_t has_command; 4496 4497 /* If the server is busy, execute just one command and only if 4498 * there is one currently waiting in our receive buffers. 4499 */ 4500 err = svn_ra_svn__has_command(&has_command, &terminate, 4501 connection->conn, iterpool); 4502 if (!err && has_command) 4503 err = svn_ra_svn__handle_command(&terminate, cmd_hash, 4504 connection->baton, 4505 connection->conn, 4506 FALSE, iterpool); 4507 4508 break; 4509 } 4510 else 4511 { 4512 /* The server is not busy, thus let's serve whichever command 4513 * comes in next and whenever it comes in. This requires the 4514 * busy() callback test to return TRUE while there are still some 4515 * resources left. 4516 */ 4517 err = svn_ra_svn__handle_command(&terminate, cmd_hash, 4518 connection->baton, 4519 connection->conn, 4520 FALSE, iterpool); 4521 } 4522 } 4523 4524 /* error or normal end of session. Close the connection */ 4525 svn_pool_destroy(iterpool); 4526 if (terminate_p) 4527 *terminate_p = terminate; 4528 4529 return svn_error_trace(err); 4530} 4531 4532svn_error_t *serve(svn_ra_svn_conn_t *conn, 4533 serve_params_t *params, 4534 apr_pool_t *pool) 4535{ 4536 server_baton_t *baton = NULL; 4537 4538 SVN_ERR(construct_server_baton(&baton, conn, params, pool)); 4539 return svn_ra_svn__handle_commands2(conn, pool, main_commands, baton, FALSE); 4540} 4541