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