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