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