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