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
66typedef 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
74typedef 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
86typedef 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
95typedef 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
100typedef struct fs_warning_baton_t {
101  server_baton_t *server;
102  svn_ra_svn_conn_t *conn;
103} fs_warning_baton_t;
104
105typedef 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. */
111static void
112log_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. */
119static void
120log_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. */
128static svn_error_t *
129error_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. */
139static svn_error_t *
140log_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. */
153static 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 */
184static svn_error_t *
185log_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 */
217static svn_error_t *
218load_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. */
268static svn_error_t *
269canonicalize_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. */
306static svn_error_t *
307load_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 */
376static svn_error_t *
377handle_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. */
405static 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. */
426static 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. */
440static 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 */
496static 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. */
510static 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 */
522static 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 */
538static enum access_type
539get_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 */
557static void
558set_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 */
570static enum access_type
571current_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. */
580static 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. */
594struct 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. */
602static 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. */
622static svn_error_t *
623create_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. */
649static 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. */
693static svn_error_t *
694internal_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. */
727static 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 */
743static 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 */
760static 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 */
807static 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
859static 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
888static 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
903static 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
939static 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
951static svn_error_t *
952abort_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
964static 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 */
986static 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. */
1051static 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. */
1069static 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. */
1088static svn_error_t *
1089get_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. */
1134static svn_error_t *
1135reparent(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
1157static svn_error_t *
1158get_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
1174static svn_error_t *
1175get_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(). */
1196static 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
1223static svn_error_t *
1224change_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
1263static svn_error_t *
1264change_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
1283static svn_error_t *
1284rev_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
1311static svn_error_t *
1312rev_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
1338static 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 */
1360static svn_error_t *
1361add_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. */
1412static svn_error_t *
1413lock_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. */
1428static svn_error_t *
1429unlock_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
1470static svn_error_t *
1471commit(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
1585static svn_error_t *
1586get_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. */
1716static svn_error_t *
1717parse_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
1772static svn_error_t *
1773get_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
1947static svn_error_t *
1948update(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
2007static svn_error_t *
2008switch_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
2063static svn_error_t *
2064status(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
2104static svn_error_t *
2105diff(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. */
2173typedef 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. */
2187static svn_error_t *
2188send_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. */
2203static svn_error_t *
2204mergeinfo_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. */
2233static svn_error_t *
2234get_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. */
2304static svn_error_t *
2305path_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. */
2347static svn_error_t *
2348revision_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
2427static svn_error_t *
2428log_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
2538static svn_error_t *
2539check_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
2573static svn_error_t *
2574stat_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
2625static svn_error_t *
2626get_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
2719static 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
2730static svn_error_t *
2731get_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. */
2832static 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. */
2845static 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. */
2854static 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
2892static svn_error_t *
2893get_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
2951static svn_error_t *
2952lock(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
2989struct lock_result_t {
2990  const svn_lock_t *lock;
2991  svn_error_t *err;
2992};
2993
2994struct lock_many_baton_t {
2995  apr_hash_t *results;
2996  apr_pool_t *pool;
2997};
2998
2999/* Implements svn_fs_lock_callback_t. */
3000static svn_error_t *
3001lock_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
3018static void
3019clear_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
3031static svn_error_t *
3032lock_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
3190static svn_error_t *
3191unlock(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
3222static svn_error_t *
3223unlock_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
3366static svn_error_t *
3367get_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
3400static svn_error_t *
3401get_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
3459static 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
3495static svn_error_t *
3496replay(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
3518static svn_error_t *
3519replay_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
3566static svn_error_t *
3567get_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
3605static svn_error_t *
3606get_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. */
3674typedef 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. */
3685static svn_error_t *
3686list_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
3696static svn_error_t *
3697list(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
3783static 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. */
3821static 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. */
3838static svn_boolean_t
3839repos_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 */
3881static svn_error_t *
3882find_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. */
4031static 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
4044static void
4045fs_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 */
4055static const char *
4056get_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 */
4083static svn_error_t *
4084get_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
4099static svn_error_t *
4100fetch_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
4126static svn_error_t *
4127fetch_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
4143static svn_error_t *
4144fetch_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
4179client_info_t *
4180get_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
4195static void
4196handle_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 */
4208static svn_error_t *
4209construct_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
4435svn_error_t *
4436serve_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
4532svn_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