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