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