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