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