serve.c revision 289166
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 /* Fetch the directories' entries before starting the response, to allow 1714 proper error handling in cases like when FULL_PATH doesn't exist */ 1715 if (want_contents) 1716 SVN_CMD_ERR(svn_fs_dir_entries(&entries, root, full_path, pool)); 1717 1718 /* Begin response ... */ 1719 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(r(!", "success", rev)); 1720 SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props)); 1721 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(!")); 1722 1723 /* Fetch the directory entries if requested and send them immediately. */ 1724 if (want_contents) 1725 { 1726 /* Use epoch for a placeholder for a missing date. */ 1727 const char *missing_date = svn_time_to_cstring(0, pool); 1728 1729 /* Transform the hash table's FS entries into dirents. This probably 1730 * belongs in libsvn_repos. */ 1731 subpool = svn_pool_create(pool); 1732 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) 1733 { 1734 const char *name = svn__apr_hash_index_key(hi); 1735 svn_fs_dirent_t *fsent = svn__apr_hash_index_val(hi); 1736 const char *file_path; 1737 1738 /* The fields in the entry tuple. */ 1739 svn_node_kind_t entry_kind = svn_node_none; 1740 svn_filesize_t entry_size = 0; 1741 svn_boolean_t has_props = FALSE; 1742 /* If 'created rev' was not requested, send 0. We can't use 1743 * SVN_INVALID_REVNUM as the tuple field is not optional. 1744 * See the email thread on dev@, 2012-03-28, subject 1745 * "buildbot failure in ASF Buildbot on svn-slik-w2k3-x64-ra", 1746 * <http://svn.haxx.se/dev/archive-2012-03/0655.shtml>. */ 1747 svn_revnum_t created_rev = 0; 1748 const char *cdate = NULL; 1749 const char *last_author = NULL; 1750 1751 svn_pool_clear(subpool); 1752 1753 file_path = svn_fspath__join(full_path, name, subpool); 1754 if (! lookup_access(subpool, b, conn, svn_authz_read, 1755 file_path, FALSE)) 1756 continue; 1757 1758 if (dirent_fields & SVN_DIRENT_KIND) 1759 entry_kind = fsent->kind; 1760 1761 if (dirent_fields & SVN_DIRENT_SIZE) 1762 if (entry_kind != svn_node_dir) 1763 SVN_CMD_ERR(svn_fs_file_length(&entry_size, root, file_path, 1764 subpool)); 1765 1766 if (dirent_fields & SVN_DIRENT_HAS_PROPS) 1767 { 1768 apr_hash_t *file_props; 1769 1770 /* has_props */ 1771 SVN_CMD_ERR(svn_fs_node_proplist(&file_props, root, file_path, 1772 subpool)); 1773 has_props = (apr_hash_count(file_props) > 0); 1774 } 1775 1776 if ((dirent_fields & SVN_DIRENT_LAST_AUTHOR) 1777 || (dirent_fields & SVN_DIRENT_TIME) 1778 || (dirent_fields & SVN_DIRENT_CREATED_REV)) 1779 { 1780 /* created_rev, last_author, time */ 1781 SVN_CMD_ERR(svn_repos_get_committed_info(&created_rev, 1782 &cdate, 1783 &last_author, 1784 root, 1785 file_path, 1786 subpool)); 1787 } 1788 1789 /* The client does not properly handle a missing CDATE. For 1790 interoperability purposes, we must fill in some junk. 1791 1792 See libsvn_ra_svn/client.c:ra_svn_get_dir() */ 1793 if (cdate == NULL) 1794 cdate = missing_date; 1795 1796 /* Send the entry. */ 1797 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "cwnbr(?c)(?c)", name, 1798 svn_node_kind_to_word(entry_kind), 1799 (apr_uint64_t) entry_size, 1800 has_props, created_rev, 1801 cdate, last_author)); 1802 } 1803 svn_pool_destroy(subpool); 1804 } 1805 1806 if (wants_inherited_props) 1807 { 1808 apr_pool_t *iterpool = svn_pool_create(pool); 1809 1810 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?!")); 1811 for (i = 0; i < inherited_props->nelts; i++) 1812 { 1813 svn_prop_inherited_item_t *iprop = 1814 APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *); 1815 1816 svn_pool_clear(iterpool); 1817 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!", 1818 iprop->path_or_url)); 1819 SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash)); 1820 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!", 1821 iprop->path_or_url)); 1822 } 1823 svn_pool_destroy(iterpool); 1824 } 1825 1826 /* Finish response. */ 1827 return svn_ra_svn__write_tuple(conn, pool, "!))"); 1828} 1829 1830static svn_error_t *update(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 1831 apr_array_header_t *params, void *baton) 1832{ 1833 server_baton_t *b = baton; 1834 svn_revnum_t rev; 1835 const char *target, *full_path, *depth_word; 1836 svn_boolean_t recurse; 1837 apr_uint64_t send_copyfrom_args; /* Optional; default FALSE */ 1838 apr_uint64_t ignore_ancestry; /* Optional; default FALSE */ 1839 /* Default to unknown. Old clients won't send depth, but we'll 1840 handle that by converting recurse if necessary. */ 1841 svn_depth_t depth = svn_depth_unknown; 1842 svn_boolean_t is_checkout; 1843 1844 /* Parse the arguments. */ 1845 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cb?wB?B", &rev, &target, 1846 &recurse, &depth_word, 1847 &send_copyfrom_args, &ignore_ancestry)); 1848 target = svn_relpath_canonicalize(target, pool); 1849 1850 if (depth_word) 1851 depth = svn_depth_from_word(depth_word); 1852 else 1853 depth = SVN_DEPTH_INFINITY_OR_FILES(recurse); 1854 1855 full_path = svn_fspath__join(b->fs_path->data, target, pool); 1856 /* Check authorization and authenticate the user if necessary. */ 1857 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, full_path, FALSE)); 1858 1859 if (!SVN_IS_VALID_REVNUM(rev)) 1860 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); 1861 1862 SVN_ERR(accept_report(&is_checkout, NULL, 1863 conn, pool, b, rev, target, NULL, TRUE, 1864 depth, 1865 (send_copyfrom_args == TRUE) /* send_copyfrom_args */, 1866 (ignore_ancestry == TRUE) /* ignore_ancestry */)); 1867 if (is_checkout) 1868 { 1869 SVN_ERR(log_command(b, conn, pool, "%s", 1870 svn_log__checkout(full_path, rev, 1871 depth, pool))); 1872 } 1873 else 1874 { 1875 SVN_ERR(log_command(b, conn, pool, "%s", 1876 svn_log__update(full_path, rev, depth, 1877 send_copyfrom_args, pool))); 1878 } 1879 1880 return SVN_NO_ERROR; 1881} 1882 1883static svn_error_t *switch_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 1884 apr_array_header_t *params, void *baton) 1885{ 1886 server_baton_t *b = baton; 1887 svn_revnum_t rev; 1888 const char *target, *depth_word; 1889 const char *switch_url, *switch_path; 1890 svn_boolean_t recurse; 1891 /* Default to unknown. Old clients won't send depth, but we'll 1892 handle that by converting recurse if necessary. */ 1893 svn_depth_t depth = svn_depth_unknown; 1894 apr_uint64_t send_copyfrom_args; /* Optional; default FALSE */ 1895 apr_uint64_t ignore_ancestry; /* Optional; default TRUE */ 1896 1897 /* Parse the arguments. */ 1898 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cbc?w?BB", &rev, &target, 1899 &recurse, &switch_url, &depth_word, 1900 &send_copyfrom_args, &ignore_ancestry)); 1901 target = svn_relpath_canonicalize(target, pool); 1902 switch_url = svn_uri_canonicalize(switch_url, pool); 1903 1904 if (depth_word) 1905 depth = svn_depth_from_word(depth_word); 1906 else 1907 depth = SVN_DEPTH_INFINITY_OR_FILES(recurse); 1908 1909 SVN_ERR(trivial_auth_request(conn, pool, b)); 1910 if (!SVN_IS_VALID_REVNUM(rev)) 1911 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); 1912 1913 SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool), 1914 svn_path_uri_decode(switch_url, pool), 1915 &switch_path)); 1916 1917 { 1918 const char *full_path = svn_fspath__join(b->fs_path->data, target, pool); 1919 SVN_ERR(log_command(b, conn, pool, "%s", 1920 svn_log__switch(full_path, switch_path, rev, 1921 depth, pool))); 1922 } 1923 1924 return accept_report(NULL, NULL, 1925 conn, pool, b, rev, target, switch_path, TRUE, 1926 depth, 1927 (send_copyfrom_args == TRUE) /* send_copyfrom_args */, 1928 (ignore_ancestry != FALSE) /* ignore_ancestry */); 1929} 1930 1931static svn_error_t *status(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 1932 apr_array_header_t *params, void *baton) 1933{ 1934 server_baton_t *b = baton; 1935 svn_revnum_t rev; 1936 const char *target, *depth_word; 1937 svn_boolean_t recurse; 1938 /* Default to unknown. Old clients won't send depth, but we'll 1939 handle that by converting recurse if necessary. */ 1940 svn_depth_t depth = svn_depth_unknown; 1941 1942 /* Parse the arguments. */ 1943 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cb?(?r)?w", 1944 &target, &recurse, &rev, &depth_word)); 1945 target = svn_relpath_canonicalize(target, pool); 1946 1947 if (depth_word) 1948 depth = svn_depth_from_word(depth_word); 1949 else 1950 depth = SVN_DEPTH_INFINITY_OR_EMPTY(recurse); 1951 1952 SVN_ERR(trivial_auth_request(conn, pool, b)); 1953 if (!SVN_IS_VALID_REVNUM(rev)) 1954 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); 1955 1956 { 1957 const char *full_path = svn_fspath__join(b->fs_path->data, target, pool); 1958 SVN_ERR(log_command(b, conn, pool, "%s", 1959 svn_log__status(full_path, rev, depth, pool))); 1960 } 1961 1962 return accept_report(NULL, NULL, conn, pool, b, rev, target, NULL, FALSE, 1963 depth, FALSE, FALSE); 1964} 1965 1966static svn_error_t *diff(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 1967 apr_array_header_t *params, void *baton) 1968{ 1969 server_baton_t *b = baton; 1970 svn_revnum_t rev; 1971 const char *target, *versus_url, *versus_path, *depth_word; 1972 svn_boolean_t recurse, ignore_ancestry; 1973 svn_boolean_t text_deltas; 1974 /* Default to unknown. Old clients won't send depth, but we'll 1975 handle that by converting recurse if necessary. */ 1976 svn_depth_t depth = svn_depth_unknown; 1977 1978 /* Parse the arguments. */ 1979 if (params->nelts == 5) 1980 { 1981 /* Clients before 1.4 don't send the text_deltas boolean or depth. */ 1982 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cbbc", &rev, &target, 1983 &recurse, &ignore_ancestry, &versus_url)); 1984 text_deltas = TRUE; 1985 depth_word = NULL; 1986 } 1987 else 1988 { 1989 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cbbcb?w", 1990 &rev, &target, &recurse, 1991 &ignore_ancestry, &versus_url, 1992 &text_deltas, &depth_word)); 1993 } 1994 target = svn_relpath_canonicalize(target, pool); 1995 versus_url = svn_uri_canonicalize(versus_url, pool); 1996 1997 if (depth_word) 1998 depth = svn_depth_from_word(depth_word); 1999 else 2000 depth = SVN_DEPTH_INFINITY_OR_FILES(recurse); 2001 2002 SVN_ERR(trivial_auth_request(conn, pool, b)); 2003 2004 if (!SVN_IS_VALID_REVNUM(rev)) 2005 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); 2006 SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool), 2007 svn_path_uri_decode(versus_url, pool), 2008 &versus_path)); 2009 2010 { 2011 const char *full_path = svn_fspath__join(b->fs_path->data, target, pool); 2012 svn_revnum_t from_rev; 2013 SVN_ERR(accept_report(NULL, &from_rev, 2014 conn, pool, b, rev, target, versus_path, 2015 text_deltas, depth, FALSE, ignore_ancestry)); 2016 SVN_ERR(log_command(b, conn, pool, "%s", 2017 svn_log__diff(full_path, from_rev, versus_path, 2018 rev, depth, ignore_ancestry, 2019 pool))); 2020 } 2021 return SVN_NO_ERROR; 2022} 2023 2024/* Regardless of whether a client's capabilities indicate an 2025 understanding of this command (by way of SVN_RA_SVN_CAP_MERGEINFO), 2026 we provide a response. 2027 2028 ASSUMPTION: When performing a 'merge' with two URLs at different 2029 revisions, the client will call this command more than once. */ 2030static svn_error_t *get_mergeinfo(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 2031 apr_array_header_t *params, void *baton) 2032{ 2033 server_baton_t *b = baton; 2034 svn_revnum_t rev; 2035 apr_array_header_t *paths, *canonical_paths; 2036 svn_mergeinfo_catalog_t mergeinfo; 2037 int i; 2038 apr_hash_index_t *hi; 2039 const char *inherit_word; 2040 svn_mergeinfo_inheritance_t inherit; 2041 svn_boolean_t include_descendants; 2042 apr_pool_t *iterpool; 2043 authz_baton_t ab; 2044 2045 ab.server = b; 2046 ab.conn = conn; 2047 2048 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "l(?r)wb", &paths, &rev, 2049 &inherit_word, &include_descendants)); 2050 inherit = svn_inheritance_from_word(inherit_word); 2051 2052 /* Canonicalize the paths which mergeinfo has been requested for. */ 2053 canonical_paths = apr_array_make(pool, paths->nelts, sizeof(const char *)); 2054 for (i = 0; i < paths->nelts; i++) 2055 { 2056 svn_ra_svn_item_t *item = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t); 2057 const char *full_path; 2058 2059 if (item->kind != SVN_RA_SVN_STRING) 2060 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2061 _("Path is not a string")); 2062 full_path = svn_relpath_canonicalize(item->u.string->data, pool); 2063 full_path = svn_fspath__join(b->fs_path->data, full_path, pool); 2064 APR_ARRAY_PUSH(canonical_paths, const char *) = full_path; 2065 } 2066 2067 SVN_ERR(log_command(b, conn, pool, "%s", 2068 svn_log__get_mergeinfo(canonical_paths, inherit, 2069 include_descendants, 2070 pool))); 2071 2072 SVN_ERR(trivial_auth_request(conn, pool, b)); 2073 SVN_CMD_ERR(svn_repos_fs_get_mergeinfo(&mergeinfo, b->repos, 2074 canonical_paths, rev, 2075 inherit, 2076 include_descendants, 2077 authz_check_access_cb_func(b), &ab, 2078 pool)); 2079 SVN_ERR(svn_mergeinfo__remove_prefix_from_catalog(&mergeinfo, mergeinfo, 2080 b->fs_path->data, pool)); 2081 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success")); 2082 iterpool = svn_pool_create(pool); 2083 for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi)) 2084 { 2085 const char *key = svn__apr_hash_index_key(hi); 2086 svn_mergeinfo_t value = svn__apr_hash_index_val(hi); 2087 svn_string_t *mergeinfo_string; 2088 2089 svn_pool_clear(iterpool); 2090 2091 SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, value, iterpool)); 2092 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "cs", key, 2093 mergeinfo_string)); 2094 } 2095 svn_pool_destroy(iterpool); 2096 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 2097 2098 return SVN_NO_ERROR; 2099} 2100 2101/* Send a log entry to the client. */ 2102static svn_error_t *log_receiver(void *baton, 2103 svn_log_entry_t *log_entry, 2104 apr_pool_t *pool) 2105{ 2106 log_baton_t *b = baton; 2107 svn_ra_svn_conn_t *conn = b->conn; 2108 apr_hash_index_t *h; 2109 svn_boolean_t invalid_revnum = FALSE; 2110 char action[2]; 2111 const char *author, *date, *message; 2112 apr_uint64_t revprop_count; 2113 2114 if (log_entry->revision == SVN_INVALID_REVNUM) 2115 { 2116 /* If the stack depth is zero, we've seen the last revision, so don't 2117 send it, just return. */ 2118 if (b->stack_depth == 0) 2119 return SVN_NO_ERROR; 2120 2121 /* Because the svn protocol won't let us send an invalid revnum, we have 2122 to fudge here and send an additional flag. */ 2123 log_entry->revision = 0; 2124 invalid_revnum = TRUE; 2125 b->stack_depth--; 2126 } 2127 2128 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "(!")); 2129 if (log_entry->changed_paths2) 2130 { 2131 for (h = apr_hash_first(pool, log_entry->changed_paths2); h; 2132 h = apr_hash_next(h)) 2133 { 2134 const char *path = svn__apr_hash_index_key(h); 2135 svn_log_changed_path2_t *change = svn__apr_hash_index_val(h); 2136 2137 action[0] = change->action; 2138 action[1] = '\0'; 2139 SVN_ERR(svn_ra_svn__write_tuple( 2140 conn, pool, "cw(?cr)(cbb)", 2141 path, 2142 action, 2143 change->copyfrom_path, 2144 change->copyfrom_rev, 2145 svn_node_kind_to_word(change->node_kind), 2146 /* text_modified and props_modified are never unknown */ 2147 change->text_modified == svn_tristate_true, 2148 change->props_modified == svn_tristate_true)); 2149 } 2150 } 2151 svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops); 2152 svn_compat_log_revprops_clear(log_entry->revprops); 2153 if (log_entry->revprops) 2154 revprop_count = apr_hash_count(log_entry->revprops); 2155 else 2156 revprop_count = 0; 2157 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)r(?c)(?c)(?c)bbn(!", 2158 log_entry->revision, 2159 author, date, message, 2160 log_entry->has_children, 2161 invalid_revnum, revprop_count)); 2162 SVN_ERR(svn_ra_svn__write_proplist(conn, pool, log_entry->revprops)); 2163 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b", 2164 log_entry->subtractive_merge)); 2165 2166 if (log_entry->has_children) 2167 b->stack_depth++; 2168 2169 return SVN_NO_ERROR; 2170} 2171 2172static svn_error_t *log_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 2173 apr_array_header_t *params, void *baton) 2174{ 2175 svn_error_t *err, *write_err; 2176 server_baton_t *b = baton; 2177 svn_revnum_t start_rev, end_rev; 2178 const char *full_path; 2179 svn_boolean_t send_changed_paths, strict_node, include_merged_revisions; 2180 apr_array_header_t *paths, *full_paths, *revprop_items, *revprops; 2181 char *revprop_word; 2182 svn_ra_svn_item_t *elt; 2183 int i; 2184 apr_uint64_t limit, include_merged_revs_param; 2185 log_baton_t lb; 2186 authz_baton_t ab; 2187 2188 ab.server = b; 2189 ab.conn = conn; 2190 2191 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "l(?r)(?r)bb?n?Bwl", &paths, 2192 &start_rev, &end_rev, &send_changed_paths, 2193 &strict_node, &limit, 2194 &include_merged_revs_param, 2195 &revprop_word, &revprop_items)); 2196 2197 if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) 2198 include_merged_revisions = FALSE; 2199 else 2200 include_merged_revisions = (svn_boolean_t) include_merged_revs_param; 2201 2202 if (revprop_word == NULL) 2203 /* pre-1.5 client */ 2204 revprops = svn_compat_log_revprops_in(pool); 2205 else if (strcmp(revprop_word, "all-revprops") == 0) 2206 revprops = NULL; 2207 else if (strcmp(revprop_word, "revprops") == 0) 2208 { 2209 SVN_ERR_ASSERT(revprop_items); 2210 2211 revprops = apr_array_make(pool, revprop_items->nelts, 2212 sizeof(char *)); 2213 for (i = 0; i < revprop_items->nelts; i++) 2214 { 2215 elt = &APR_ARRAY_IDX(revprop_items, i, svn_ra_svn_item_t); 2216 if (elt->kind != SVN_RA_SVN_STRING) 2217 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2218 _("Log revprop entry not a string")); 2219 APR_ARRAY_PUSH(revprops, const char *) = elt->u.string->data; 2220 } 2221 } 2222 else 2223 return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2224 _("Unknown revprop word '%s' in log command"), 2225 revprop_word); 2226 2227 /* If we got an unspecified number then the user didn't send us anything, 2228 so we assume no limit. If it's larger than INT_MAX then someone is 2229 messing with us, since we know the svn client libraries will never send 2230 us anything that big, so play it safe and default to no limit. */ 2231 if (limit == SVN_RA_SVN_UNSPECIFIED_NUMBER || limit > INT_MAX) 2232 limit = 0; 2233 2234 full_paths = apr_array_make(pool, paths->nelts, sizeof(const char *)); 2235 for (i = 0; i < paths->nelts; i++) 2236 { 2237 elt = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t); 2238 if (elt->kind != SVN_RA_SVN_STRING) 2239 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2240 _("Log path entry not a string")); 2241 full_path = svn_relpath_canonicalize(elt->u.string->data, pool), 2242 full_path = svn_fspath__join(b->fs_path->data, full_path, pool); 2243 APR_ARRAY_PUSH(full_paths, const char *) = full_path; 2244 } 2245 SVN_ERR(trivial_auth_request(conn, pool, b)); 2246 2247 SVN_ERR(log_command(b, conn, pool, "%s", 2248 svn_log__log(full_paths, start_rev, end_rev, 2249 (int) limit, send_changed_paths, 2250 strict_node, include_merged_revisions, 2251 revprops, pool))); 2252 2253 /* Get logs. (Can't report errors back to the client at this point.) */ 2254 lb.fs_path = b->fs_path->data; 2255 lb.conn = conn; 2256 lb.stack_depth = 0; 2257 err = svn_repos_get_logs4(b->repos, full_paths, start_rev, end_rev, 2258 (int) limit, send_changed_paths, strict_node, 2259 include_merged_revisions, revprops, 2260 authz_check_access_cb_func(b), &ab, log_receiver, 2261 &lb, pool); 2262 2263 write_err = svn_ra_svn__write_word(conn, pool, "done"); 2264 if (write_err) 2265 { 2266 svn_error_clear(err); 2267 return write_err; 2268 } 2269 SVN_CMD_ERR(err); 2270 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 2271 return SVN_NO_ERROR; 2272} 2273 2274static svn_error_t *check_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 2275 apr_array_header_t *params, void *baton) 2276{ 2277 server_baton_t *b = baton; 2278 svn_revnum_t rev; 2279 const char *path, *full_path; 2280 svn_fs_root_t *root; 2281 svn_node_kind_t kind; 2282 2283 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)", &path, &rev)); 2284 full_path = svn_fspath__join(b->fs_path->data, 2285 svn_relpath_canonicalize(path, pool), pool); 2286 2287 /* Check authorizations */ 2288 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, 2289 full_path, FALSE)); 2290 2291 if (!SVN_IS_VALID_REVNUM(rev)) 2292 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); 2293 2294 SVN_ERR(log_command(b, conn, pool, "check-path %s@%d", 2295 svn_path_uri_encode(full_path, pool), rev)); 2296 2297 SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool)); 2298 SVN_CMD_ERR(svn_fs_check_path(&kind, root, full_path, pool)); 2299 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "w", 2300 svn_node_kind_to_word(kind))); 2301 return SVN_NO_ERROR; 2302} 2303 2304static svn_error_t *stat_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 2305 apr_array_header_t *params, void *baton) 2306{ 2307 server_baton_t *b = baton; 2308 svn_revnum_t rev; 2309 const char *path, *full_path, *cdate; 2310 svn_fs_root_t *root; 2311 svn_dirent_t *dirent; 2312 2313 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)", &path, &rev)); 2314 full_path = svn_fspath__join(b->fs_path->data, 2315 svn_relpath_canonicalize(path, pool), pool); 2316 2317 /* Check authorizations */ 2318 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, 2319 full_path, FALSE)); 2320 2321 if (!SVN_IS_VALID_REVNUM(rev)) 2322 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); 2323 2324 SVN_ERR(log_command(b, conn, pool, "stat %s@%d", 2325 svn_path_uri_encode(full_path, pool), rev)); 2326 2327 SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool)); 2328 SVN_CMD_ERR(svn_repos_stat(&dirent, root, full_path, pool)); 2329 2330 /* Need to return the equivalent of "(?l)", since that's what the 2331 client is reading. */ 2332 2333 if (dirent == NULL) 2334 { 2335 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "()")); 2336 return SVN_NO_ERROR; 2337 } 2338 2339 cdate = (dirent->time == (time_t) -1) ? NULL 2340 : svn_time_to_cstring(dirent->time, pool); 2341 2342 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "((wnbr(?c)(?c)))", 2343 svn_node_kind_to_word(dirent->kind), 2344 (apr_uint64_t) dirent->size, 2345 dirent->has_props, dirent->created_rev, 2346 cdate, dirent->last_author)); 2347 2348 return SVN_NO_ERROR; 2349} 2350 2351static svn_error_t *get_locations(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 2352 apr_array_header_t *params, void *baton) 2353{ 2354 svn_error_t *err, *write_err; 2355 server_baton_t *b = baton; 2356 svn_revnum_t revision; 2357 apr_array_header_t *location_revisions, *loc_revs_proto; 2358 svn_ra_svn_item_t *elt; 2359 int i; 2360 const char *relative_path; 2361 svn_revnum_t peg_revision; 2362 apr_hash_t *fs_locations; 2363 const char *abs_path; 2364 authz_baton_t ab; 2365 2366 ab.server = b; 2367 ab.conn = conn; 2368 2369 /* Parse the arguments. */ 2370 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "crl", &relative_path, 2371 &peg_revision, 2372 &loc_revs_proto)); 2373 relative_path = svn_relpath_canonicalize(relative_path, pool); 2374 2375 abs_path = svn_fspath__join(b->fs_path->data, relative_path, pool); 2376 2377 location_revisions = apr_array_make(pool, loc_revs_proto->nelts, 2378 sizeof(svn_revnum_t)); 2379 for (i = 0; i < loc_revs_proto->nelts; i++) 2380 { 2381 elt = &APR_ARRAY_IDX(loc_revs_proto, i, svn_ra_svn_item_t); 2382 if (elt->kind != SVN_RA_SVN_NUMBER) 2383 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2384 "Get-locations location revisions entry " 2385 "not a revision number"); 2386 revision = (svn_revnum_t)(elt->u.number); 2387 APR_ARRAY_PUSH(location_revisions, svn_revnum_t) = revision; 2388 } 2389 SVN_ERR(trivial_auth_request(conn, pool, b)); 2390 SVN_ERR(log_command(b, conn, pool, "%s", 2391 svn_log__get_locations(abs_path, peg_revision, 2392 location_revisions, pool))); 2393 2394 /* All the parameters are fine - let's perform the query against the 2395 * repository. */ 2396 2397 /* We store both err and write_err here, so the client will get 2398 * the "done" even if there was an error in fetching the results. */ 2399 2400 err = svn_repos_trace_node_locations(b->fs, &fs_locations, abs_path, 2401 peg_revision, location_revisions, 2402 authz_check_access_cb_func(b), &ab, 2403 pool); 2404 2405 /* Now, write the results to the connection. */ 2406 if (!err) 2407 { 2408 if (fs_locations) 2409 { 2410 apr_hash_index_t *iter; 2411 2412 for (iter = apr_hash_first(pool, fs_locations); iter; 2413 iter = apr_hash_next(iter)) 2414 { 2415 const svn_revnum_t *iter_key = svn__apr_hash_index_key(iter); 2416 const char *iter_value = svn__apr_hash_index_val(iter); 2417 2418 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "rc", 2419 *iter_key, iter_value)); 2420 } 2421 } 2422 } 2423 2424 write_err = svn_ra_svn__write_word(conn, pool, "done"); 2425 if (write_err) 2426 { 2427 svn_error_clear(err); 2428 return write_err; 2429 } 2430 SVN_CMD_ERR(err); 2431 2432 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 2433 2434 return SVN_NO_ERROR; 2435} 2436 2437static svn_error_t *gls_receiver(svn_location_segment_t *segment, 2438 void *baton, 2439 apr_pool_t *pool) 2440{ 2441 svn_ra_svn_conn_t *conn = baton; 2442 return svn_ra_svn__write_tuple(conn, pool, "rr(?c)", 2443 segment->range_start, 2444 segment->range_end, 2445 segment->path); 2446} 2447 2448static svn_error_t *get_location_segments(svn_ra_svn_conn_t *conn, 2449 apr_pool_t *pool, 2450 apr_array_header_t *params, 2451 void *baton) 2452{ 2453 svn_error_t *err, *write_err; 2454 server_baton_t *b = baton; 2455 svn_revnum_t peg_revision, start_rev, end_rev; 2456 const char *relative_path; 2457 const char *abs_path; 2458 authz_baton_t ab; 2459 2460 ab.server = b; 2461 ab.conn = conn; 2462 2463 /* Parse the arguments. */ 2464 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)(?r)(?r)", 2465 &relative_path, &peg_revision, 2466 &start_rev, &end_rev)); 2467 relative_path = svn_relpath_canonicalize(relative_path, pool); 2468 2469 abs_path = svn_fspath__join(b->fs_path->data, relative_path, pool); 2470 2471 SVN_ERR(trivial_auth_request(conn, pool, b)); 2472 SVN_ERR(log_command(baton, conn, pool, "%s", 2473 svn_log__get_location_segments(abs_path, peg_revision, 2474 start_rev, end_rev, 2475 pool))); 2476 2477 /* No START_REV or PEG_REVISION? We'll use HEAD. */ 2478 if (!SVN_IS_VALID_REVNUM(start_rev) || !SVN_IS_VALID_REVNUM(peg_revision)) 2479 { 2480 svn_revnum_t youngest; 2481 2482 SVN_CMD_ERR(svn_fs_youngest_rev(&youngest, b->fs, pool)); 2483 2484 if (!SVN_IS_VALID_REVNUM(start_rev)) 2485 start_rev = youngest; 2486 if (!SVN_IS_VALID_REVNUM(peg_revision)) 2487 peg_revision = youngest; 2488 } 2489 2490 /* No END_REV? We'll use 0. */ 2491 if (!SVN_IS_VALID_REVNUM(end_rev)) 2492 end_rev = 0; 2493 2494 if (end_rev > start_rev) 2495 { 2496 err = svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, 2497 "Get-location-segments end revision must not be " 2498 "younger than start revision"); 2499 return log_fail_and_flush(err, b, conn, pool); 2500 } 2501 2502 if (start_rev > peg_revision) 2503 { 2504 err = svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, 2505 "Get-location-segments start revision must not " 2506 "be younger than peg revision"); 2507 return log_fail_and_flush(err, b, conn, pool); 2508 } 2509 2510 /* All the parameters are fine - let's perform the query against the 2511 * repository. */ 2512 2513 /* We store both err and write_err here, so the client will get 2514 * the "done" even if there was an error in fetching the results. */ 2515 2516 err = svn_repos_node_location_segments(b->repos, abs_path, 2517 peg_revision, start_rev, end_rev, 2518 gls_receiver, (void *)conn, 2519 authz_check_access_cb_func(b), &ab, 2520 pool); 2521 write_err = svn_ra_svn__write_word(conn, pool, "done"); 2522 if (write_err) 2523 { 2524 svn_error_clear(err); 2525 return write_err; 2526 } 2527 SVN_CMD_ERR(err); 2528 2529 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 2530 2531 return SVN_NO_ERROR; 2532} 2533 2534/* This implements svn_write_fn_t. Write LEN bytes starting at DATA to the 2535 client as a string. */ 2536static svn_error_t *svndiff_handler(void *baton, const char *data, 2537 apr_size_t *len) 2538{ 2539 file_revs_baton_t *b = baton; 2540 svn_string_t str; 2541 2542 str.data = data; 2543 str.len = *len; 2544 return svn_ra_svn__write_string(b->conn, b->pool, &str); 2545} 2546 2547/* This implements svn_close_fn_t. Mark the end of the data by writing an 2548 empty string to the client. */ 2549static svn_error_t *svndiff_close_handler(void *baton) 2550{ 2551 file_revs_baton_t *b = baton; 2552 2553 SVN_ERR(svn_ra_svn__write_cstring(b->conn, b->pool, "")); 2554 return SVN_NO_ERROR; 2555} 2556 2557/* This implements the svn_repos_file_rev_handler_t interface. */ 2558static svn_error_t *file_rev_handler(void *baton, const char *path, 2559 svn_revnum_t rev, apr_hash_t *rev_props, 2560 svn_boolean_t merged_revision, 2561 svn_txdelta_window_handler_t *d_handler, 2562 void **d_baton, 2563 apr_array_header_t *prop_diffs, 2564 apr_pool_t *pool) 2565{ 2566 file_revs_baton_t *frb = baton; 2567 svn_stream_t *stream; 2568 2569 SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "cr(!", 2570 path, rev)); 2571 SVN_ERR(svn_ra_svn__write_proplist(frb->conn, pool, rev_props)); 2572 SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "!)(!")); 2573 SVN_ERR(write_prop_diffs(frb->conn, pool, prop_diffs)); 2574 SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "!)b", merged_revision)); 2575 2576 /* Store the pool for the delta stream. */ 2577 frb->pool = pool; 2578 2579 /* Prepare for the delta or just write an empty string. */ 2580 if (d_handler) 2581 { 2582 stream = svn_stream_create(baton, pool); 2583 svn_stream_set_write(stream, svndiff_handler); 2584 svn_stream_set_close(stream, svndiff_close_handler); 2585 2586 /* If the connection does not support SVNDIFF1 or if we don't want to use 2587 * compression, use the non-compressing "version 0" implementation */ 2588 if ( svn_ra_svn_compression_level(frb->conn) > 0 2589 && svn_ra_svn_has_capability(frb->conn, SVN_RA_SVN_CAP_SVNDIFF1)) 2590 svn_txdelta_to_svndiff3(d_handler, d_baton, stream, 1, 2591 svn_ra_svn_compression_level(frb->conn), pool); 2592 else 2593 svn_txdelta_to_svndiff3(d_handler, d_baton, stream, 0, 2594 svn_ra_svn_compression_level(frb->conn), pool); 2595 } 2596 else 2597 SVN_ERR(svn_ra_svn__write_cstring(frb->conn, pool, "")); 2598 2599 return SVN_NO_ERROR; 2600} 2601 2602static svn_error_t *get_file_revs(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 2603 apr_array_header_t *params, void *baton) 2604{ 2605 server_baton_t *b = baton; 2606 svn_error_t *err, *write_err; 2607 file_revs_baton_t frb; 2608 svn_revnum_t start_rev, end_rev; 2609 const char *path; 2610 const char *full_path; 2611 apr_uint64_t include_merged_revs_param; 2612 svn_boolean_t include_merged_revisions; 2613 authz_baton_t ab; 2614 2615 ab.server = b; 2616 ab.conn = conn; 2617 2618 /* Parse arguments. */ 2619 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)(?r)?B", 2620 &path, &start_rev, &end_rev, 2621 &include_merged_revs_param)); 2622 path = svn_relpath_canonicalize(path, pool); 2623 SVN_ERR(trivial_auth_request(conn, pool, b)); 2624 full_path = svn_fspath__join(b->fs_path->data, path, pool); 2625 2626 if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) 2627 include_merged_revisions = FALSE; 2628 else 2629 include_merged_revisions = (svn_boolean_t) include_merged_revs_param; 2630 2631 SVN_ERR(log_command(b, conn, pool, "%s", 2632 svn_log__get_file_revs(full_path, start_rev, end_rev, 2633 include_merged_revisions, 2634 pool))); 2635 2636 frb.conn = conn; 2637 frb.pool = NULL; 2638 2639 err = svn_repos_get_file_revs2(b->repos, full_path, start_rev, end_rev, 2640 include_merged_revisions, 2641 authz_check_access_cb_func(b), &ab, 2642 file_rev_handler, &frb, pool); 2643 write_err = svn_ra_svn__write_word(conn, pool, "done"); 2644 if (write_err) 2645 { 2646 svn_error_clear(err); 2647 return write_err; 2648 } 2649 SVN_CMD_ERR(err); 2650 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 2651 2652 return SVN_NO_ERROR; 2653} 2654 2655static svn_error_t *lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 2656 apr_array_header_t *params, void *baton) 2657{ 2658 server_baton_t *b = baton; 2659 const char *path; 2660 const char *comment; 2661 const char *full_path; 2662 svn_boolean_t steal_lock; 2663 svn_revnum_t current_rev; 2664 svn_lock_t *l; 2665 2666 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?c)b(?r)", &path, &comment, 2667 &steal_lock, ¤t_rev)); 2668 full_path = svn_fspath__join(b->fs_path->data, 2669 svn_relpath_canonicalize(path, pool), pool); 2670 2671 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, 2672 full_path, TRUE)); 2673 SVN_ERR(log_command(b, conn, pool, "%s", 2674 svn_log__lock_one_path(full_path, steal_lock, pool))); 2675 2676 SVN_CMD_ERR(svn_repos_fs_lock(&l, b->repos, full_path, NULL, comment, 0, 2677 0, /* No expiration time. */ 2678 current_rev, steal_lock, pool)); 2679 2680 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(!", "success")); 2681 SVN_ERR(write_lock(conn, pool, l)); 2682 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)")); 2683 2684 return SVN_NO_ERROR; 2685} 2686 2687static svn_error_t *lock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 2688 apr_array_header_t *params, void *baton) 2689{ 2690 server_baton_t *b = baton; 2691 apr_array_header_t *path_revs; 2692 const char *comment; 2693 svn_boolean_t steal_lock; 2694 int i; 2695 apr_pool_t *subpool; 2696 const char *path; 2697 const char *full_path; 2698 svn_revnum_t current_rev; 2699 apr_array_header_t *log_paths; 2700 svn_lock_t *l; 2701 svn_error_t *err = SVN_NO_ERROR, *write_err; 2702 2703 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?c)bl", &comment, &steal_lock, 2704 &path_revs)); 2705 2706 subpool = svn_pool_create(pool); 2707 2708 /* Because we can only send a single auth reply per request, we send 2709 a reply before parsing the lock commands. This means an authz 2710 access denial will abort the processing of the locks and return 2711 an error. */ 2712 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, TRUE)); 2713 2714 /* Loop through the lock requests. */ 2715 log_paths = apr_array_make(pool, path_revs->nelts, sizeof(full_path)); 2716 for (i = 0; i < path_revs->nelts; ++i) 2717 { 2718 svn_ra_svn_item_t *item = &APR_ARRAY_IDX(path_revs, i, 2719 svn_ra_svn_item_t); 2720 2721 svn_pool_clear(subpool); 2722 2723 if (item->kind != SVN_RA_SVN_LIST) 2724 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2725 "Lock requests should be list of lists"); 2726 2727 SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, pool, "c(?r)", &path, 2728 ¤t_rev)); 2729 2730 /* Allocate the full_path out of pool so it will survive for use 2731 * by operational logging, after this loop. */ 2732 full_path = svn_fspath__join(b->fs_path->data, 2733 svn_relpath_canonicalize(path, subpool), 2734 pool); 2735 APR_ARRAY_PUSH(log_paths, const char *) = full_path; 2736 2737 if (! lookup_access(pool, b, conn, svn_authz_write, full_path, TRUE)) 2738 { 2739 err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, NULL, 2740 b, conn, pool); 2741 break; 2742 } 2743 2744 err = svn_repos_fs_lock(&l, b->repos, full_path, 2745 NULL, comment, FALSE, 2746 0, /* No expiration time. */ 2747 current_rev, 2748 steal_lock, subpool); 2749 2750 if (err) 2751 { 2752 if (SVN_ERR_IS_LOCK_ERROR(err)) 2753 { 2754 write_err = svn_ra_svn__write_cmd_failure(conn, pool, err); 2755 svn_error_clear(err); 2756 err = NULL; 2757 SVN_ERR(write_err); 2758 } 2759 else 2760 break; 2761 } 2762 else 2763 { 2764 SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "w!", "success")); 2765 SVN_ERR(write_lock(conn, subpool, l)); 2766 SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "!")); 2767 } 2768 } 2769 2770 svn_pool_destroy(subpool); 2771 2772 SVN_ERR(log_command(b, conn, pool, "%s", 2773 svn_log__lock(log_paths, steal_lock, pool))); 2774 2775 /* NOTE: err might contain a fatal locking error from the loop above. */ 2776 write_err = svn_ra_svn__write_word(conn, pool, "done"); 2777 if (!write_err) 2778 SVN_CMD_ERR(err); 2779 svn_error_clear(err); 2780 SVN_ERR(write_err); 2781 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 2782 2783 return SVN_NO_ERROR; 2784} 2785 2786static svn_error_t *unlock(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 2787 apr_array_header_t *params, void *baton) 2788{ 2789 server_baton_t *b = baton; 2790 const char *path, *token, *full_path; 2791 svn_boolean_t break_lock; 2792 2793 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?c)b", &path, &token, 2794 &break_lock)); 2795 2796 full_path = svn_fspath__join(b->fs_path->data, 2797 svn_relpath_canonicalize(path, pool), pool); 2798 2799 /* Username required unless break_lock was specified. */ 2800 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, 2801 full_path, ! break_lock)); 2802 SVN_ERR(log_command(b, conn, pool, "%s", 2803 svn_log__unlock_one_path(full_path, break_lock, pool))); 2804 2805 SVN_CMD_ERR(svn_repos_fs_unlock(b->repos, full_path, token, break_lock, 2806 pool)); 2807 2808 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 2809 2810 return SVN_NO_ERROR; 2811} 2812 2813static svn_error_t *unlock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 2814 apr_array_header_t *params, void *baton) 2815{ 2816 server_baton_t *b = baton; 2817 svn_boolean_t break_lock; 2818 apr_array_header_t *unlock_tokens; 2819 int i; 2820 apr_pool_t *subpool; 2821 const char *path; 2822 const char *full_path; 2823 apr_array_header_t *log_paths; 2824 const char *token; 2825 svn_error_t *err = SVN_NO_ERROR, *write_err; 2826 2827 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "bl", &break_lock, 2828 &unlock_tokens)); 2829 2830 /* Username required unless break_lock was specified. */ 2831 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, ! break_lock)); 2832 2833 subpool = svn_pool_create(pool); 2834 2835 /* Loop through the unlock requests. */ 2836 log_paths = apr_array_make(pool, unlock_tokens->nelts, sizeof(full_path)); 2837 for (i = 0; i < unlock_tokens->nelts; i++) 2838 { 2839 svn_ra_svn_item_t *item = &APR_ARRAY_IDX(unlock_tokens, i, 2840 svn_ra_svn_item_t); 2841 2842 svn_pool_clear(subpool); 2843 2844 if (item->kind != SVN_RA_SVN_LIST) 2845 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2846 "Unlock request should be a list of lists"); 2847 2848 SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, subpool, "c(?c)", &path, 2849 &token)); 2850 2851 /* Allocate the full_path out of pool so it will survive for use 2852 * by operational logging, after this loop. */ 2853 full_path = svn_fspath__join(b->fs_path->data, 2854 svn_relpath_canonicalize(path, subpool), 2855 pool); 2856 APR_ARRAY_PUSH(log_paths, const char *) = full_path; 2857 2858 if (! lookup_access(subpool, b, conn, svn_authz_write, full_path, 2859 ! break_lock)) 2860 return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR, 2861 error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, 2862 NULL, NULL, 2863 b, conn, pool), 2864 NULL); 2865 2866 err = svn_repos_fs_unlock(b->repos, full_path, token, break_lock, 2867 subpool); 2868 if (err) 2869 { 2870 if (SVN_ERR_IS_UNLOCK_ERROR(err)) 2871 { 2872 write_err = svn_ra_svn__write_cmd_failure(conn, pool, err); 2873 svn_error_clear(err); 2874 err = NULL; 2875 SVN_ERR(write_err); 2876 } 2877 else 2878 break; 2879 } 2880 else 2881 SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "w(c)", "success", 2882 path)); 2883 } 2884 2885 svn_pool_destroy(subpool); 2886 2887 SVN_ERR(log_command(b, conn, pool, "%s", 2888 svn_log__unlock(log_paths, break_lock, pool))); 2889 2890 /* NOTE: err might contain a fatal unlocking error from the loop above. */ 2891 write_err = svn_ra_svn__write_word(conn, pool, "done"); 2892 if (! write_err) 2893 SVN_CMD_ERR(err); 2894 svn_error_clear(err); 2895 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 2896 2897 return SVN_NO_ERROR; 2898} 2899 2900static svn_error_t *get_lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 2901 apr_array_header_t *params, void *baton) 2902{ 2903 server_baton_t *b = baton; 2904 const char *path; 2905 const char *full_path; 2906 svn_lock_t *l; 2907 2908 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &path)); 2909 2910 full_path = svn_fspath__join(b->fs_path->data, 2911 svn_relpath_canonicalize(path, pool), pool); 2912 2913 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, 2914 full_path, FALSE)); 2915 SVN_ERR(log_command(b, conn, pool, "get-lock %s", 2916 svn_path_uri_encode(full_path, pool))); 2917 2918 SVN_CMD_ERR(svn_fs_get_lock(&l, b->fs, full_path, pool)); 2919 2920 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success")); 2921 if (l) 2922 SVN_ERR(write_lock(conn, pool, l)); 2923 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 2924 2925 return SVN_NO_ERROR; 2926} 2927 2928static svn_error_t *get_locks(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 2929 apr_array_header_t *params, void *baton) 2930{ 2931 server_baton_t *b = baton; 2932 const char *path; 2933 const char *full_path; 2934 const char *depth_word; 2935 svn_depth_t depth; 2936 apr_hash_t *locks; 2937 apr_hash_index_t *hi; 2938 svn_error_t *err; 2939 authz_baton_t ab; 2940 2941 ab.server = b; 2942 ab.conn = conn; 2943 2944 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c?(?w)", &path, &depth_word)); 2945 2946 depth = depth_word ? svn_depth_from_word(depth_word) : svn_depth_infinity; 2947 if ((depth != svn_depth_empty) && 2948 (depth != svn_depth_files) && 2949 (depth != svn_depth_immediates) && 2950 (depth != svn_depth_infinity)) 2951 { 2952 err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, 2953 "Invalid 'depth' specified in get-locks request"); 2954 return log_fail_and_flush(err, b, conn, pool); 2955 } 2956 2957 full_path = svn_fspath__join(b->fs_path->data, 2958 svn_relpath_canonicalize(path, pool), pool); 2959 2960 SVN_ERR(trivial_auth_request(conn, pool, b)); 2961 2962 SVN_ERR(log_command(b, conn, pool, "get-locks %s", 2963 svn_path_uri_encode(full_path, pool))); 2964 SVN_CMD_ERR(svn_repos_fs_get_locks2(&locks, b->repos, full_path, depth, 2965 authz_check_access_cb_func(b), &ab, 2966 pool)); 2967 2968 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success")); 2969 for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi)) 2970 { 2971 svn_lock_t *l = svn__apr_hash_index_val(hi); 2972 2973 SVN_ERR(write_lock(conn, pool, l)); 2974 } 2975 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 2976 2977 return SVN_NO_ERROR; 2978} 2979 2980static svn_error_t *replay_one_revision(svn_ra_svn_conn_t *conn, 2981 server_baton_t *b, 2982 svn_revnum_t rev, 2983 svn_revnum_t low_water_mark, 2984 svn_boolean_t send_deltas, 2985 apr_pool_t *pool) 2986{ 2987 const svn_delta_editor_t *editor; 2988 void *edit_baton; 2989 svn_fs_root_t *root; 2990 svn_error_t *err; 2991 authz_baton_t ab; 2992 2993 ab.server = b; 2994 ab.conn = conn; 2995 2996 SVN_ERR(log_command(b, conn, pool, 2997 svn_log__replay(b->fs_path->data, rev, pool))); 2998 2999 svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL); 3000 3001 err = svn_fs_revision_root(&root, b->fs, rev, pool); 3002 3003 if (! err) 3004 err = svn_repos_replay2(root, b->fs_path->data, low_water_mark, 3005 send_deltas, editor, edit_baton, 3006 authz_check_access_cb_func(b), &ab, pool); 3007 3008 if (err) 3009 svn_error_clear(editor->abort_edit(edit_baton, pool)); 3010 SVN_CMD_ERR(err); 3011 3012 return svn_ra_svn__write_cmd_finish_replay(conn, pool); 3013} 3014 3015static svn_error_t *replay(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 3016 apr_array_header_t *params, void *baton) 3017{ 3018 svn_revnum_t rev, low_water_mark; 3019 svn_boolean_t send_deltas; 3020 server_baton_t *b = baton; 3021 3022 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rrb", &rev, &low_water_mark, 3023 &send_deltas)); 3024 3025 SVN_ERR(trivial_auth_request(conn, pool, b)); 3026 3027 SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark, 3028 send_deltas, pool)); 3029 3030 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 3031 3032 return SVN_NO_ERROR; 3033} 3034 3035static svn_error_t *replay_range(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 3036 apr_array_header_t *params, void *baton) 3037{ 3038 svn_revnum_t start_rev, end_rev, rev, low_water_mark; 3039 svn_boolean_t send_deltas; 3040 server_baton_t *b = baton; 3041 apr_pool_t *iterpool; 3042 authz_baton_t ab; 3043 3044 ab.server = b; 3045 ab.conn = conn; 3046 3047 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rrrb", &start_rev, 3048 &end_rev, &low_water_mark, 3049 &send_deltas)); 3050 3051 SVN_ERR(trivial_auth_request(conn, pool, b)); 3052 3053 iterpool = svn_pool_create(pool); 3054 for (rev = start_rev; rev <= end_rev; rev++) 3055 { 3056 apr_hash_t *props; 3057 3058 svn_pool_clear(iterpool); 3059 3060 SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repos, rev, 3061 authz_check_access_cb_func(b), 3062 &ab, 3063 iterpool)); 3064 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "w(!", "revprops")); 3065 SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, props)); 3066 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!)")); 3067 3068 SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark, 3069 send_deltas, iterpool)); 3070 3071 } 3072 svn_pool_destroy(iterpool); 3073 3074 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "")); 3075 3076 return SVN_NO_ERROR; 3077} 3078 3079static svn_error_t * 3080get_deleted_rev(svn_ra_svn_conn_t *conn, 3081 apr_pool_t *pool, 3082 apr_array_header_t *params, 3083 void *baton) 3084{ 3085 server_baton_t *b = baton; 3086 const char *path, *full_path; 3087 svn_revnum_t peg_revision; 3088 svn_revnum_t end_revision; 3089 svn_revnum_t revision_deleted; 3090 3091 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "crr", 3092 &path, &peg_revision, &end_revision)); 3093 full_path = svn_fspath__join(b->fs_path->data, 3094 svn_relpath_canonicalize(path, pool), pool); 3095 SVN_ERR(log_command(b, conn, pool, "get-deleted-rev")); 3096 SVN_ERR(trivial_auth_request(conn, pool, b)); 3097 SVN_ERR(svn_repos_deleted_rev(b->fs, full_path, peg_revision, end_revision, 3098 &revision_deleted, pool)); 3099 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", revision_deleted)); 3100 return SVN_NO_ERROR; 3101} 3102 3103static svn_error_t * 3104get_inherited_props(svn_ra_svn_conn_t *conn, 3105 apr_pool_t *pool, 3106 apr_array_header_t *params, 3107 void *baton) 3108{ 3109 server_baton_t *b = baton; 3110 const char *path, *full_path; 3111 svn_revnum_t rev; 3112 svn_fs_root_t *root; 3113 apr_array_header_t *inherited_props; 3114 int i; 3115 apr_pool_t *iterpool = svn_pool_create(pool); 3116 authz_baton_t ab; 3117 3118 ab.server = b; 3119 ab.conn = conn; 3120 3121 /* Parse arguments. */ 3122 SVN_ERR(svn_ra_svn__parse_tuple(params, iterpool, "c(?r)", &path, &rev)); 3123 3124 full_path = svn_fspath__join(b->fs_path->data, 3125 svn_relpath_canonicalize(path, iterpool), 3126 pool); 3127 3128 /* Check authorizations */ 3129 SVN_ERR(must_have_access(conn, iterpool, b, svn_authz_read, 3130 full_path, FALSE)); 3131 3132 if (!SVN_IS_VALID_REVNUM(rev)) 3133 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); 3134 3135 SVN_ERR(log_command(b, conn, pool, "%s", 3136 svn_log__get_inherited_props(full_path, rev, 3137 iterpool))); 3138 3139 /* Fetch the properties and a stream for the contents. */ 3140 SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, iterpool)); 3141 SVN_CMD_ERR(get_props(NULL, &inherited_props, &ab, root, full_path, pool)); 3142 3143 /* Send successful command response with revision and props. */ 3144 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "w(!", "success")); 3145 3146 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(?!")); 3147 3148 for (i = 0; i < inherited_props->nelts; i++) 3149 { 3150 svn_prop_inherited_item_t *iprop = 3151 APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *); 3152 3153 svn_pool_clear(iterpool); 3154 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!", 3155 iprop->path_or_url)); 3156 SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash)); 3157 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!", 3158 iprop->path_or_url)); 3159 } 3160 3161 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))")); 3162 svn_pool_destroy(iterpool); 3163 return SVN_NO_ERROR; 3164} 3165 3166static const svn_ra_svn_cmd_entry_t main_commands[] = { 3167 { "reparent", reparent }, 3168 { "get-latest-rev", get_latest_rev }, 3169 { "get-dated-rev", get_dated_rev }, 3170 { "change-rev-prop", change_rev_prop }, 3171 { "change-rev-prop2",change_rev_prop2 }, 3172 { "rev-proplist", rev_proplist }, 3173 { "rev-prop", rev_prop }, 3174 { "commit", commit }, 3175 { "get-file", get_file }, 3176 { "get-dir", get_dir }, 3177 { "update", update }, 3178 { "switch", switch_cmd }, 3179 { "status", status }, 3180 { "diff", diff }, 3181 { "get-mergeinfo", get_mergeinfo }, 3182 { "log", log_cmd }, 3183 { "check-path", check_path }, 3184 { "stat", stat_cmd }, 3185 { "get-locations", get_locations }, 3186 { "get-location-segments", get_location_segments }, 3187 { "get-file-revs", get_file_revs }, 3188 { "lock", lock }, 3189 { "lock-many", lock_many }, 3190 { "unlock", unlock }, 3191 { "unlock-many", unlock_many }, 3192 { "get-lock", get_lock }, 3193 { "get-locks", get_locks }, 3194 { "replay", replay }, 3195 { "replay-range", replay_range }, 3196 { "get-deleted-rev", get_deleted_rev }, 3197 { "get-iprops", get_inherited_props }, 3198 { NULL } 3199}; 3200 3201/* Skip past the scheme part of a URL, including the tunnel specification 3202 * if present. Return NULL if the scheme part is invalid for ra_svn. */ 3203static const char *skip_scheme_part(const char *url) 3204{ 3205 if (strncmp(url, "svn", 3) != 0) 3206 return NULL; 3207 url += 3; 3208 if (*url == '+') 3209 url += strcspn(url, ":"); 3210 if (strncmp(url, "://", 3) != 0) 3211 return NULL; 3212 return url + 3; 3213} 3214 3215/* Check that PATH is a valid repository path, meaning it doesn't contain any 3216 '..' path segments. 3217 NOTE: This is similar to svn_path_is_backpath_present, but that function 3218 assumes the path separator is '/'. This function also checks for 3219 segments delimited by the local path separator. */ 3220static svn_boolean_t 3221repos_path_valid(const char *path) 3222{ 3223 const char *s = path; 3224 3225 while (*s) 3226 { 3227 /* Scan for the end of the segment. */ 3228 while (*path && *path != '/' && *path != SVN_PATH_LOCAL_SEPARATOR) 3229 ++path; 3230 3231 /* Check for '..'. */ 3232#ifdef WIN32 3233 /* On Windows, don't allow sequences of more than one character 3234 consisting of just dots and spaces. Win32 functions treat 3235 paths such as ".. " and "......." inconsistently. Make sure 3236 no one can escape out of the root. */ 3237 if (path - s >= 2 && strspn(s, ". ") == (size_t)(path - s)) 3238 return FALSE; 3239#else /* ! WIN32 */ 3240 if (path - s == 2 && s[0] == '.' && s[1] == '.') 3241 return FALSE; 3242#endif 3243 3244 /* Skip all separators. */ 3245 while (*path && (*path == '/' || *path == SVN_PATH_LOCAL_SEPARATOR)) 3246 ++path; 3247 s = path; 3248 } 3249 3250 return TRUE; 3251} 3252 3253/* Look for the repository given by URL, using ROOT as the virtual 3254 * repository root. If we find one, fill in the repos, fs, cfg, 3255 * repos_url, and fs_path fields of B. Set B->repos's client 3256 * capabilities to CAPABILITIES, which must be at least as long-lived 3257 * as POOL, and whose elements are SVN_RA_CAPABILITY_*. 3258 */ 3259static svn_error_t *find_repos(const char *url, const char *root, 3260 server_baton_t *b, 3261 svn_ra_svn_conn_t *conn, 3262 const apr_array_header_t *capabilities, 3263 apr_pool_t *pool) 3264{ 3265 const char *path, *full_path, *repos_root, *fs_path, *hooks_env; 3266 svn_stringbuf_t *url_buf; 3267 3268 /* Skip past the scheme and authority part. */ 3269 path = skip_scheme_part(url); 3270 if (path == NULL) 3271 return svn_error_createf(SVN_ERR_BAD_URL, NULL, 3272 "Non-svn URL passed to svn server: '%s'", url); 3273 3274 if (! b->vhost) 3275 { 3276 path = strchr(path, '/'); 3277 if (path == NULL) 3278 path = ""; 3279 } 3280 path = svn_relpath_canonicalize(path, pool); 3281 path = svn_path_uri_decode(path, pool); 3282 3283 /* Ensure that it isn't possible to escape the root by disallowing 3284 '..' segments. */ 3285 if (!repos_path_valid(path)) 3286 return svn_error_create(SVN_ERR_BAD_FILENAME, NULL, 3287 "Couldn't determine repository path"); 3288 3289 /* Join the server-configured root with the client path. */ 3290 full_path = svn_dirent_join(svn_dirent_canonicalize(root, pool), 3291 path, pool); 3292 3293 /* Search for a repository in the full path. */ 3294 repos_root = svn_repos_find_root_path(full_path, pool); 3295 if (!repos_root) 3296 return svn_error_createf(SVN_ERR_RA_SVN_REPOS_NOT_FOUND, NULL, 3297 "No repository found in '%s'", url); 3298 3299 /* Open the repository and fill in b with the resulting information. */ 3300 SVN_ERR(svn_repos_open2(&b->repos, repos_root, b->fs_config, pool)); 3301 SVN_ERR(svn_repos_remember_client_capabilities(b->repos, capabilities)); 3302 b->fs = svn_repos_fs(b->repos); 3303 fs_path = full_path + strlen(repos_root); 3304 b->fs_path = svn_stringbuf_create(*fs_path ? fs_path : "/", pool); 3305 url_buf = svn_stringbuf_create(url, pool); 3306 svn_path_remove_components(url_buf, 3307 svn_path_component_count(b->fs_path->data)); 3308 b->repos_url = url_buf->data; 3309 b->authz_repos_name = svn_dirent_is_child(root, repos_root, pool); 3310 if (b->authz_repos_name == NULL) 3311 b->repos_name = svn_dirent_basename(repos_root, pool); 3312 else 3313 b->repos_name = b->authz_repos_name; 3314 b->repos_name = svn_path_uri_encode(b->repos_name, pool); 3315 3316 /* If the svnserve configuration has not been loaded then load it from the 3317 * repository. */ 3318 if (NULL == b->cfg) 3319 { 3320 b->base = svn_repos_conf_dir(b->repos, pool); 3321 3322 SVN_ERR(svn_config_read3(&b->cfg, svn_repos_svnserve_conf(b->repos, pool), 3323 FALSE, /* must_exist */ 3324 FALSE, /* section_names_case_sensitive */ 3325 FALSE, /* option_names_case_sensitive */ 3326 pool)); 3327 SVN_ERR(load_pwdb_config(b, conn, pool)); 3328 SVN_ERR(load_authz_config(b, conn, repos_root, pool)); 3329 } 3330 /* svnserve.conf has been loaded via the --config-file option so need 3331 * to load pwdb and authz. */ 3332 else 3333 { 3334 SVN_ERR(load_pwdb_config(b, conn, pool)); 3335 SVN_ERR(load_authz_config(b, conn, repos_root, pool)); 3336 } 3337 3338#ifdef SVN_HAVE_SASL 3339 /* Should we use Cyrus SASL? */ 3340 SVN_ERR(svn_config_get_bool(b->cfg, &b->use_sasl, SVN_CONFIG_SECTION_SASL, 3341 SVN_CONFIG_OPTION_USE_SASL, FALSE)); 3342#endif 3343 3344 /* Use the repository UUID as the default realm. */ 3345 SVN_ERR(svn_fs_get_uuid(b->fs, &b->realm, pool)); 3346 svn_config_get(b->cfg, &b->realm, SVN_CONFIG_SECTION_GENERAL, 3347 SVN_CONFIG_OPTION_REALM, b->realm); 3348 3349 /* Make sure it's possible for the client to authenticate. Note 3350 that this doesn't take into account any authz configuration read 3351 above, because we can't know about access it grants until paths 3352 are given by the client. */ 3353 if (get_access(b, UNAUTHENTICATED) == NO_ACCESS 3354 && (get_access(b, AUTHENTICATED) == NO_ACCESS 3355 || (!b->tunnel_user && !b->pwdb && !b->use_sasl))) 3356 return error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 3357 "No access allowed to this repository", 3358 b, conn, pool); 3359 3360 /* Configure hook script environment variables. */ 3361 svn_config_get(b->cfg, &hooks_env, SVN_CONFIG_SECTION_GENERAL, 3362 SVN_CONFIG_OPTION_HOOKS_ENV, NULL); 3363 if (hooks_env) 3364 hooks_env = svn_dirent_internal_style(hooks_env, pool); 3365 SVN_ERR(svn_repos_hooks_setenv(b->repos, hooks_env, pool)); 3366 3367 return SVN_NO_ERROR; 3368} 3369 3370/* Compute the authentication name EXTERNAL should be able to get, if any. */ 3371static const char *get_tunnel_user(serve_params_t *params, apr_pool_t *pool) 3372{ 3373 /* Only offer EXTERNAL for connections tunneled over a login agent. */ 3374 if (!params->tunnel) 3375 return NULL; 3376 3377 /* If a tunnel user was provided on the command line, use that. */ 3378 if (params->tunnel_user) 3379 return params->tunnel_user; 3380 3381 return svn_user_get_name(pool); 3382} 3383 3384static void 3385fs_warning_func(void *baton, svn_error_t *err) 3386{ 3387 fs_warning_baton_t *b = baton; 3388 log_server_error(err, b->server, b->conn, b->pool); 3389 /* TODO: Keep log_pool in the server baton, cleared after every log? */ 3390 svn_pool_clear(b->pool); 3391} 3392 3393/* Return the normalized repository-relative path for the given PATH 3394 * (may be a URL, full path or relative path) and fs contained in the 3395 * server baton BATON. Allocate the result in POOL. 3396 */ 3397static const char * 3398get_normalized_repo_rel_path(void *baton, 3399 const char *path, 3400 apr_pool_t *pool) 3401{ 3402 server_baton_t *sb = baton; 3403 3404 if (svn_path_is_url(path)) 3405 { 3406 /* This is a copyfrom URL. */ 3407 path = svn_uri_skip_ancestor(sb->repos_url, path, pool); 3408 path = svn_fspath__canonicalize(path, pool); 3409 } 3410 else 3411 { 3412 /* This is a base-relative path. */ 3413 if ((path)[0] != '/') 3414 /* Get an absolute path for use in the FS. */ 3415 path = svn_fspath__join(sb->fs_path->data, path, pool); 3416 } 3417 3418 return path; 3419} 3420 3421/* Get the revision root for REVISION in fs given by server baton BATON 3422 * and return it in *FS_ROOT. Use HEAD if REVISION is SVN_INVALID_REVNUM. 3423 * Use POOL for allocations. 3424 */ 3425static svn_error_t * 3426get_revision_root(svn_fs_root_t **fs_root, 3427 void *baton, 3428 svn_revnum_t revision, 3429 apr_pool_t *pool) 3430{ 3431 server_baton_t *sb = baton; 3432 3433 if (!SVN_IS_VALID_REVNUM(revision)) 3434 SVN_ERR(svn_fs_youngest_rev(&revision, sb->fs, pool)); 3435 3436 SVN_ERR(svn_fs_revision_root(fs_root, sb->fs, revision, pool)); 3437 3438 return SVN_NO_ERROR; 3439} 3440 3441static svn_error_t * 3442fetch_props_func(apr_hash_t **props, 3443 void *baton, 3444 const char *path, 3445 svn_revnum_t base_revision, 3446 apr_pool_t *result_pool, 3447 apr_pool_t *scratch_pool) 3448{ 3449 svn_fs_root_t *fs_root; 3450 svn_error_t *err; 3451 3452 path = get_normalized_repo_rel_path(baton, path, scratch_pool); 3453 SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool)); 3454 3455 err = svn_fs_node_proplist(props, fs_root, path, result_pool); 3456 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 3457 { 3458 svn_error_clear(err); 3459 *props = apr_hash_make(result_pool); 3460 return SVN_NO_ERROR; 3461 } 3462 else if (err) 3463 return svn_error_trace(err); 3464 3465 return SVN_NO_ERROR; 3466} 3467 3468static svn_error_t * 3469fetch_kind_func(svn_node_kind_t *kind, 3470 void *baton, 3471 const char *path, 3472 svn_revnum_t base_revision, 3473 apr_pool_t *scratch_pool) 3474{ 3475 svn_fs_root_t *fs_root; 3476 3477 path = get_normalized_repo_rel_path(baton, path, scratch_pool); 3478 SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool)); 3479 3480 SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool)); 3481 3482 return SVN_NO_ERROR; 3483} 3484 3485static svn_error_t * 3486fetch_base_func(const char **filename, 3487 void *baton, 3488 const char *path, 3489 svn_revnum_t base_revision, 3490 apr_pool_t *result_pool, 3491 apr_pool_t *scratch_pool) 3492{ 3493 svn_stream_t *contents; 3494 svn_stream_t *file_stream; 3495 const char *tmp_filename; 3496 svn_fs_root_t *fs_root; 3497 svn_error_t *err; 3498 3499 path = get_normalized_repo_rel_path(baton, path, scratch_pool); 3500 SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool)); 3501 3502 err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool); 3503 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 3504 { 3505 svn_error_clear(err); 3506 *filename = NULL; 3507 return SVN_NO_ERROR; 3508 } 3509 else if (err) 3510 return svn_error_trace(err); 3511 SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL, 3512 svn_io_file_del_on_pool_cleanup, 3513 scratch_pool, scratch_pool)); 3514 SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool)); 3515 3516 *filename = apr_pstrdup(result_pool, tmp_filename); 3517 3518 return SVN_NO_ERROR; 3519} 3520 3521svn_error_t *serve(svn_ra_svn_conn_t *conn, serve_params_t *params, 3522 apr_pool_t *pool) 3523{ 3524 svn_error_t *err, *io_err; 3525 apr_uint64_t ver; 3526 const char *uuid, *client_url, *ra_client_string, *client_string; 3527 apr_array_header_t *caplist, *cap_words; 3528 server_baton_t b; 3529 fs_warning_baton_t warn_baton; 3530 svn_stringbuf_t *cap_log = svn_stringbuf_create_empty(pool); 3531 3532 b.tunnel = params->tunnel; 3533 b.tunnel_user = get_tunnel_user(params, pool); 3534 b.read_only = params->read_only; 3535 b.user = NULL; 3536 b.username_case = params->username_case; 3537 b.authz_user = NULL; 3538 b.base = params->base; 3539 b.cfg = params->cfg; 3540 b.pwdb = NULL; 3541 b.authzdb = NULL; 3542 b.realm = NULL; 3543 b.log_file = params->log_file; 3544 b.pool = pool; 3545 b.use_sasl = FALSE; 3546 b.vhost = params->vhost; 3547 3548 /* construct FS configuration parameters */ 3549 b.fs_config = apr_hash_make(pool); 3550 svn_hash_sets(b.fs_config, SVN_FS_CONFIG_FSFS_CACHE_DELTAS, 3551 params->cache_txdeltas ? "1" :"0"); 3552 svn_hash_sets(b.fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS, 3553 params->cache_fulltexts ? "1" :"0"); 3554 svn_hash_sets(b.fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS, 3555 params->cache_revprops ? "1" :"0"); 3556 3557 /* Send greeting. We don't support version 1 any more, so we can 3558 * send an empty mechlist. */ 3559 if (params->compression_level > 0) 3560 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "nn()(wwwwwwwwwww)", 3561 (apr_uint64_t) 2, (apr_uint64_t) 2, 3562 SVN_RA_SVN_CAP_EDIT_PIPELINE, 3563 SVN_RA_SVN_CAP_SVNDIFF1, 3564 SVN_RA_SVN_CAP_ABSENT_ENTRIES, 3565 SVN_RA_SVN_CAP_COMMIT_REVPROPS, 3566 SVN_RA_SVN_CAP_DEPTH, 3567 SVN_RA_SVN_CAP_LOG_REVPROPS, 3568 SVN_RA_SVN_CAP_ATOMIC_REVPROPS, 3569 SVN_RA_SVN_CAP_PARTIAL_REPLAY, 3570 SVN_RA_SVN_CAP_INHERITED_PROPS, 3571 SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS, 3572 SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE 3573 )); 3574 else 3575 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "nn()(wwwwwwwwww)", 3576 (apr_uint64_t) 2, (apr_uint64_t) 2, 3577 SVN_RA_SVN_CAP_EDIT_PIPELINE, 3578 SVN_RA_SVN_CAP_ABSENT_ENTRIES, 3579 SVN_RA_SVN_CAP_COMMIT_REVPROPS, 3580 SVN_RA_SVN_CAP_DEPTH, 3581 SVN_RA_SVN_CAP_LOG_REVPROPS, 3582 SVN_RA_SVN_CAP_ATOMIC_REVPROPS, 3583 SVN_RA_SVN_CAP_PARTIAL_REPLAY, 3584 SVN_RA_SVN_CAP_INHERITED_PROPS, 3585 SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS, 3586 SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE 3587 )); 3588 3589 /* Read client response, which we assume to be in version 2 format: 3590 * version, capability list, and client URL; then we do an auth 3591 * request. */ 3592 SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "nlc?c(?c)", 3593 &ver, &caplist, &client_url, 3594 &ra_client_string, 3595 &client_string)); 3596 if (ver != 2) 3597 return SVN_NO_ERROR; 3598 3599 client_url = svn_uri_canonicalize(client_url, pool); 3600 SVN_ERR(svn_ra_svn_set_capabilities(conn, caplist)); 3601 3602 /* All released versions of Subversion support edit-pipeline, 3603 * so we do not accept connections from clients that do not. */ 3604 if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE)) 3605 return SVN_NO_ERROR; 3606 3607 /* find_repos needs the capabilities as a list of words (eventually 3608 they get handed to the start-commit hook). While we could add a 3609 new interface to re-retrieve them from conn and convert the 3610 result to a list, it's simpler to just convert caplist by hand 3611 here, since we already have it and turning 'svn_ra_svn_item_t's 3612 into 'const char *'s is pretty easy. 3613 3614 We only record capabilities we care about. The client may report 3615 more (because it doesn't know what the server cares about). */ 3616 { 3617 int i; 3618 svn_ra_svn_item_t *item; 3619 3620 cap_words = apr_array_make(pool, 1, sizeof(const char *)); 3621 for (i = 0; i < caplist->nelts; i++) 3622 { 3623 item = &APR_ARRAY_IDX(caplist, i, svn_ra_svn_item_t); 3624 /* ra_svn_set_capabilities() already type-checked for us */ 3625 if (strcmp(item->u.word, SVN_RA_SVN_CAP_MERGEINFO) == 0) 3626 { 3627 APR_ARRAY_PUSH(cap_words, const char *) 3628 = SVN_RA_CAPABILITY_MERGEINFO; 3629 } 3630 /* Save for operational log. */ 3631 if (cap_log->len > 0) 3632 svn_stringbuf_appendcstr(cap_log, " "); 3633 svn_stringbuf_appendcstr(cap_log, item->u.word); 3634 } 3635 } 3636 3637 err = find_repos(client_url, params->root, &b, conn, cap_words, pool); 3638 if (!err) 3639 { 3640 SVN_ERR(auth_request(conn, pool, &b, READ_ACCESS, FALSE)); 3641 if (current_access(&b) == NO_ACCESS) 3642 err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 3643 "Not authorized for access", 3644 &b, conn, pool); 3645 } 3646 if (err) 3647 { 3648 log_error(err, b.log_file, svn_ra_svn_conn_remote_host(conn), 3649 b.user, NULL, pool); 3650 io_err = svn_ra_svn__write_cmd_failure(conn, pool, err); 3651 svn_error_clear(err); 3652 SVN_ERR(io_err); 3653 return svn_ra_svn__flush(conn, pool); 3654 } 3655 3656 /* Log the open. */ 3657 if (ra_client_string == NULL || ra_client_string[0] == '\0') 3658 ra_client_string = "-"; 3659 else 3660 ra_client_string = svn_path_uri_encode(ra_client_string, pool); 3661 if (client_string == NULL || client_string[0] == '\0') 3662 client_string = "-"; 3663 else 3664 client_string = svn_path_uri_encode(client_string, pool); 3665 SVN_ERR(log_command(&b, conn, pool, 3666 "open %" APR_UINT64_T_FMT " cap=(%s) %s %s %s", 3667 ver, cap_log->data, 3668 svn_path_uri_encode(b.fs_path->data, pool), 3669 ra_client_string, client_string)); 3670 3671 warn_baton.server = &b; 3672 warn_baton.conn = conn; 3673 warn_baton.pool = svn_pool_create(pool); 3674 svn_fs_set_warning_func(b.fs, fs_warning_func, &warn_baton); 3675 3676 SVN_ERR(svn_fs_get_uuid(b.fs, &uuid, pool)); 3677 3678 /* We can't claim mergeinfo capability until we know whether the 3679 repository supports mergeinfo (i.e., is not a 1.4 repository), 3680 but we don't get the repository url from the client until after 3681 we've already sent the initial list of server capabilities. So 3682 we list repository capabilities here, in our first response after 3683 the client has sent the url. */ 3684 { 3685 svn_boolean_t supports_mergeinfo; 3686 SVN_ERR(svn_repos_has_capability(b.repos, &supports_mergeinfo, 3687 SVN_REPOS_CAPABILITY_MERGEINFO, pool)); 3688 3689 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(cc(!", 3690 "success", uuid, b.repos_url)); 3691 if (supports_mergeinfo) 3692 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_CAP_MERGEINFO)); 3693 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 3694 } 3695 3696 /* Set up editor shims. */ 3697 { 3698 svn_delta_shim_callbacks_t *callbacks = 3699 svn_delta_shim_callbacks_default(pool); 3700 3701 callbacks->fetch_base_func = fetch_base_func; 3702 callbacks->fetch_props_func = fetch_props_func; 3703 callbacks->fetch_kind_func = fetch_kind_func; 3704 callbacks->fetch_baton = &b; 3705 3706 SVN_ERR(svn_ra_svn__set_shim_callbacks(conn, callbacks)); 3707 } 3708 3709 return svn_ra_svn__handle_commands2(conn, pool, main_commands, &b, FALSE); 3710} 3711