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