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