]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/subversion/subversion/svnserve/serve.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.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   if (wants_inherited_props == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1530     wants_inherited_props = FALSE;
1531
1532   full_path = svn_fspath__join(b->fs_path->data,
1533                                svn_relpath_canonicalize(path, pool), pool);
1534
1535   /* Check authorizations */
1536   SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
1537                            full_path, FALSE));
1538
1539   if (!SVN_IS_VALID_REVNUM(rev))
1540     SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1541
1542   SVN_ERR(log_command(b, conn, pool, "%s",
1543                       svn_log__get_file(full_path, rev,
1544                                         want_contents, want_props, pool)));
1545
1546   /* Fetch the properties and a stream for the contents. */
1547   SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
1548   SVN_CMD_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, root,
1549                                    full_path, TRUE, pool));
1550   hex_digest = svn_checksum_to_cstring_display(checksum, pool);
1551
1552   /* Fetch the file's explicit and/or inherited properties if
1553      requested.  Although the wants-iprops boolean was added to the
1554      protocol in 1.8 a standard 1.8 client never requests iprops. */
1555   if (want_props || wants_inherited_props)
1556     SVN_CMD_ERR(get_props(want_props ? &props : NULL,
1557                           wants_inherited_props ? &inherited_props : NULL,
1558                           &ab, root, full_path,
1559                           pool));
1560   if (want_contents)
1561     SVN_CMD_ERR(svn_fs_file_contents(&contents, root, full_path, pool));
1562
1563   /* Send successful command response with revision and props. */
1564   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)r(!", "success",
1565                                   hex_digest, rev));
1566   SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props));
1567
1568   if (wants_inherited_props)
1569     {
1570       apr_pool_t *iterpool = svn_pool_create(pool);
1571
1572       SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?!"));
1573       for (i = 0; i < inherited_props->nelts; i++)
1574         {
1575           svn_prop_inherited_item_t *iprop =
1576             APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
1577
1578           svn_pool_clear(iterpool);
1579           SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!",
1580                                           iprop->path_or_url));
1581           SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash));
1582           SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!",
1583                                           iprop->path_or_url));
1584         }
1585       svn_pool_destroy(iterpool);
1586     }
1587
1588   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1589
1590   /* Now send the file's contents. */
1591   if (want_contents)
1592     {
1593       err = SVN_NO_ERROR;
1594       while (1)
1595         {
1596           len = sizeof(buf);
1597           err = svn_stream_read(contents, buf, &len);
1598           if (err)
1599             break;
1600           if (len > 0)
1601             {
1602               write_str.data = buf;
1603               write_str.len = len;
1604               SVN_ERR(svn_ra_svn__write_string(conn, pool, &write_str));
1605             }
1606           if (len < sizeof(buf))
1607             {
1608               err = svn_stream_close(contents);
1609               break;
1610             }
1611         }
1612       write_err = svn_ra_svn__write_cstring(conn, pool, "");
1613       if (write_err)
1614         {
1615           svn_error_clear(err);
1616           return write_err;
1617         }
1618       SVN_CMD_ERR(err);
1619       SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1620     }
1621
1622   return SVN_NO_ERROR;
1623 }
1624
1625 static svn_error_t *get_dir(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1626                             apr_array_header_t *params, void *baton)
1627 {
1628   server_baton_t *b = baton;
1629   const char *path, *full_path;
1630   svn_revnum_t rev;
1631   apr_hash_t *entries, *props = NULL;
1632   apr_array_header_t *inherited_props;
1633   apr_hash_index_t *hi;
1634   svn_fs_root_t *root;
1635   apr_pool_t *subpool;
1636   svn_boolean_t want_props, want_contents;
1637   apr_uint64_t wants_inherited_props;
1638   apr_uint64_t dirent_fields;
1639   apr_array_header_t *dirent_fields_list = NULL;
1640   svn_ra_svn_item_t *elt;
1641   int i;
1642   authz_baton_t ab;
1643
1644   ab.server = b;
1645   ab.conn = conn;
1646
1647   SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)bb?l?B", &path, &rev,
1648                                   &want_props, &want_contents,
1649                                   &dirent_fields_list,
1650                                   &wants_inherited_props));
1651
1652   if (wants_inherited_props == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1653     wants_inherited_props = FALSE;
1654
1655   if (! dirent_fields_list)
1656     {
1657       dirent_fields = SVN_DIRENT_ALL;
1658     }
1659   else
1660     {
1661       dirent_fields = 0;
1662
1663       for (i = 0; i < dirent_fields_list->nelts; ++i)
1664         {
1665           elt = &APR_ARRAY_IDX(dirent_fields_list, i, svn_ra_svn_item_t);
1666
1667           if (elt->kind != SVN_RA_SVN_WORD)
1668             return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1669                                     "Dirent field not a string");
1670
1671           if (strcmp(SVN_RA_SVN_DIRENT_KIND, elt->u.word) == 0)
1672             dirent_fields |= SVN_DIRENT_KIND;
1673           else if (strcmp(SVN_RA_SVN_DIRENT_SIZE, elt->u.word) == 0)
1674             dirent_fields |= SVN_DIRENT_SIZE;
1675           else if (strcmp(SVN_RA_SVN_DIRENT_HAS_PROPS, elt->u.word) == 0)
1676             dirent_fields |= SVN_DIRENT_HAS_PROPS;
1677           else if (strcmp(SVN_RA_SVN_DIRENT_CREATED_REV, elt->u.word) == 0)
1678             dirent_fields |= SVN_DIRENT_CREATED_REV;
1679           else if (strcmp(SVN_RA_SVN_DIRENT_TIME, elt->u.word) == 0)
1680             dirent_fields |= SVN_DIRENT_TIME;
1681           else if (strcmp(SVN_RA_SVN_DIRENT_LAST_AUTHOR, elt->u.word) == 0)
1682             dirent_fields |= SVN_DIRENT_LAST_AUTHOR;
1683         }
1684     }
1685
1686   full_path = svn_fspath__join(b->fs_path->data,
1687                                svn_relpath_canonicalize(path, pool), pool);
1688
1689   /* Check authorizations */
1690   SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
1691                            full_path, FALSE));
1692
1693   if (!SVN_IS_VALID_REVNUM(rev))
1694     SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1695
1696   SVN_ERR(log_command(b, conn, pool, "%s",
1697                       svn_log__get_dir(full_path, rev,
1698                                        want_contents, want_props,
1699                                        dirent_fields, pool)));
1700
1701   /* Fetch the root of the appropriate revision. */
1702   SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
1703
1704   /* Fetch the directory's explicit and/or inherited properties if
1705      requested.  Although the wants-iprops boolean was added to the
1706      protocol in 1.8 a standard 1.8 client never requests iprops. */
1707   if (want_props || wants_inherited_props)
1708     SVN_CMD_ERR(get_props(want_props ? &props : NULL,
1709                           wants_inherited_props ? &inherited_props : NULL,
1710                           &ab, root, full_path,
1711                           pool));
1712
1713   /* Begin response ... */
1714   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(r(!", "success", rev));
1715   SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props));
1716   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(!"));
1717
1718   /* Fetch the directory entries if requested and send them immediately. */
1719   if (want_contents)
1720     {
1721       /* Use epoch for a placeholder for a missing date.  */
1722       const char *missing_date = svn_time_to_cstring(0, pool);
1723
1724       SVN_CMD_ERR(svn_fs_dir_entries(&entries, root, full_path, pool));
1725
1726       /* Transform the hash table's FS entries into dirents.  This probably
1727        * belongs in libsvn_repos. */
1728       subpool = svn_pool_create(pool);
1729       for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
1730         {
1731           const char *name = svn__apr_hash_index_key(hi);
1732           svn_fs_dirent_t *fsent = svn__apr_hash_index_val(hi);
1733           const char *file_path;
1734
1735           /* The fields in the entry tuple.  */
1736           svn_node_kind_t entry_kind = svn_node_none;
1737           svn_filesize_t entry_size = 0;
1738           svn_boolean_t has_props = FALSE;
1739           /* If 'created rev' was not requested, send 0.  We can't use
1740            * SVN_INVALID_REVNUM as the tuple field is not optional.
1741            * See the email thread on dev@, 2012-03-28, subject
1742            * "buildbot failure in ASF Buildbot on svn-slik-w2k3-x64-ra",
1743            * <http://svn.haxx.se/dev/archive-2012-03/0655.shtml>. */
1744           svn_revnum_t created_rev = 0;
1745           const char *cdate = NULL;
1746           const char *last_author = NULL;
1747
1748           svn_pool_clear(subpool);
1749
1750           file_path = svn_fspath__join(full_path, name, subpool);
1751           if (! lookup_access(subpool, b, conn, svn_authz_read,
1752                               file_path, FALSE))
1753             continue;
1754
1755           if (dirent_fields & SVN_DIRENT_KIND)
1756               entry_kind = fsent->kind;
1757
1758           if (dirent_fields & SVN_DIRENT_SIZE)
1759               if (entry_kind != svn_node_dir)
1760                 SVN_CMD_ERR(svn_fs_file_length(&entry_size, root, file_path,
1761                                                subpool));
1762
1763           if (dirent_fields & SVN_DIRENT_HAS_PROPS)
1764             {
1765               apr_hash_t *file_props;
1766
1767               /* has_props */
1768               SVN_CMD_ERR(svn_fs_node_proplist(&file_props, root, file_path,
1769                                                subpool));
1770               has_props = (apr_hash_count(file_props) > 0);
1771             }
1772
1773           if ((dirent_fields & SVN_DIRENT_LAST_AUTHOR)
1774               || (dirent_fields & SVN_DIRENT_TIME)
1775               || (dirent_fields & SVN_DIRENT_CREATED_REV))
1776             {
1777               /* created_rev, last_author, time */
1778               SVN_CMD_ERR(svn_repos_get_committed_info(&created_rev,
1779                                                        &cdate,
1780                                                        &last_author,
1781                                                        root,
1782                                                        file_path,
1783                                                        subpool));
1784             }
1785
1786           /* The client does not properly handle a missing CDATE. For
1787              interoperability purposes, we must fill in some junk.
1788
1789              See libsvn_ra_svn/client.c:ra_svn_get_dir()  */
1790           if (cdate == NULL)
1791             cdate = missing_date;
1792
1793           /* Send the entry. */
1794           SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "cwnbr(?c)(?c)", name,
1795                                           svn_node_kind_to_word(entry_kind),
1796                                           (apr_uint64_t) entry_size,
1797                                           has_props, created_rev,
1798                                           cdate, last_author));
1799         }
1800       svn_pool_destroy(subpool);
1801     }
1802
1803   if (wants_inherited_props)
1804     {
1805       apr_pool_t *iterpool = svn_pool_create(pool);
1806
1807       SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?!"));
1808       for (i = 0; i < inherited_props->nelts; i++)
1809         {
1810           svn_prop_inherited_item_t *iprop =
1811             APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
1812
1813           svn_pool_clear(iterpool);
1814           SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!",
1815                                           iprop->path_or_url));
1816           SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash));
1817           SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!",
1818                                           iprop->path_or_url));
1819         }
1820       svn_pool_destroy(iterpool);
1821     }
1822
1823   /* Finish response. */
1824   return svn_ra_svn__write_tuple(conn, pool, "!))");
1825 }
1826
1827 static svn_error_t *update(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1828                            apr_array_header_t *params, void *baton)
1829 {
1830   server_baton_t *b = baton;
1831   svn_revnum_t rev;
1832   const char *target, *full_path, *depth_word;
1833   svn_boolean_t recurse;
1834   apr_uint64_t send_copyfrom_args; /* Optional; default FALSE */
1835   apr_uint64_t ignore_ancestry; /* Optional; default FALSE */
1836   /* Default to unknown.  Old clients won't send depth, but we'll
1837      handle that by converting recurse if necessary. */
1838   svn_depth_t depth = svn_depth_unknown;
1839   svn_boolean_t is_checkout;
1840
1841   /* Parse the arguments. */
1842   SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cb?wB?B", &rev, &target,
1843                                   &recurse, &depth_word,
1844                                   &send_copyfrom_args, &ignore_ancestry));
1845   target = svn_relpath_canonicalize(target, pool);
1846
1847   if (depth_word)
1848     depth = svn_depth_from_word(depth_word);
1849   else
1850     depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
1851
1852   full_path = svn_fspath__join(b->fs_path->data, target, pool);
1853   /* Check authorization and authenticate the user if necessary. */
1854   SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, full_path, FALSE));
1855
1856   if (!SVN_IS_VALID_REVNUM(rev))
1857     SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1858
1859   SVN_ERR(accept_report(&is_checkout, NULL,
1860                         conn, pool, b, rev, target, NULL, TRUE,
1861                         depth,
1862                         (send_copyfrom_args == TRUE) /* send_copyfrom_args */,
1863                         (ignore_ancestry == TRUE) /* ignore_ancestry */));
1864   if (is_checkout)
1865     {
1866       SVN_ERR(log_command(b, conn, pool, "%s",
1867                           svn_log__checkout(full_path, rev,
1868                                             depth, pool)));
1869     }
1870   else
1871     {
1872       SVN_ERR(log_command(b, conn, pool, "%s",
1873                           svn_log__update(full_path, rev, depth,
1874                                           send_copyfrom_args, pool)));
1875     }
1876
1877   return SVN_NO_ERROR;
1878 }
1879
1880 static svn_error_t *switch_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1881                                apr_array_header_t *params, void *baton)
1882 {
1883   server_baton_t *b = baton;
1884   svn_revnum_t rev;
1885   const char *target, *depth_word;
1886   const char *switch_url, *switch_path;
1887   svn_boolean_t recurse;
1888   /* Default to unknown.  Old clients won't send depth, but we'll
1889      handle that by converting recurse if necessary. */
1890   svn_depth_t depth = svn_depth_unknown;
1891   apr_uint64_t send_copyfrom_args; /* Optional; default FALSE */
1892   apr_uint64_t ignore_ancestry; /* Optional; default TRUE */
1893
1894   /* Parse the arguments. */
1895   SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cbc?w?BB", &rev, &target,
1896                                   &recurse, &switch_url, &depth_word,
1897                                   &send_copyfrom_args, &ignore_ancestry));
1898   target = svn_relpath_canonicalize(target, pool);
1899   switch_url = svn_uri_canonicalize(switch_url, pool);
1900
1901   if (depth_word)
1902     depth = svn_depth_from_word(depth_word);
1903   else
1904     depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
1905
1906   SVN_ERR(trivial_auth_request(conn, pool, b));
1907   if (!SVN_IS_VALID_REVNUM(rev))
1908     SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1909
1910   SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool),
1911                           svn_path_uri_decode(switch_url, pool),
1912                           &switch_path));
1913
1914   {
1915     const char *full_path = svn_fspath__join(b->fs_path->data, target, pool);
1916     SVN_ERR(log_command(b, conn, pool, "%s",
1917                         svn_log__switch(full_path, switch_path, rev,
1918                                         depth, pool)));
1919   }
1920
1921   return accept_report(NULL, NULL,
1922                        conn, pool, b, rev, target, switch_path, TRUE,
1923                        depth,
1924                        (send_copyfrom_args == TRUE) /* send_copyfrom_args */,
1925                        (ignore_ancestry != FALSE) /* ignore_ancestry */);
1926 }
1927
1928 static svn_error_t *status(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1929                            apr_array_header_t *params, void *baton)
1930 {
1931   server_baton_t *b = baton;
1932   svn_revnum_t rev;
1933   const char *target, *depth_word;
1934   svn_boolean_t recurse;
1935   /* Default to unknown.  Old clients won't send depth, but we'll
1936      handle that by converting recurse if necessary. */
1937   svn_depth_t depth = svn_depth_unknown;
1938
1939   /* Parse the arguments. */
1940   SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cb?(?r)?w",
1941                                   &target, &recurse, &rev, &depth_word));
1942   target = svn_relpath_canonicalize(target, pool);
1943
1944   if (depth_word)
1945     depth = svn_depth_from_word(depth_word);
1946   else
1947     depth = SVN_DEPTH_INFINITY_OR_EMPTY(recurse);
1948
1949   SVN_ERR(trivial_auth_request(conn, pool, b));
1950   if (!SVN_IS_VALID_REVNUM(rev))
1951     SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1952
1953   {
1954     const char *full_path = svn_fspath__join(b->fs_path->data, target, pool);
1955     SVN_ERR(log_command(b, conn, pool, "%s",
1956                         svn_log__status(full_path, rev, depth, pool)));
1957   }
1958
1959   return accept_report(NULL, NULL, conn, pool, b, rev, target, NULL, FALSE,
1960                        depth, FALSE, FALSE);
1961 }
1962
1963 static svn_error_t *diff(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1964                          apr_array_header_t *params, void *baton)
1965 {
1966   server_baton_t *b = baton;
1967   svn_revnum_t rev;
1968   const char *target, *versus_url, *versus_path, *depth_word;
1969   svn_boolean_t recurse, ignore_ancestry;
1970   svn_boolean_t text_deltas;
1971   /* Default to unknown.  Old clients won't send depth, but we'll
1972      handle that by converting recurse if necessary. */
1973   svn_depth_t depth = svn_depth_unknown;
1974
1975   /* Parse the arguments. */
1976   if (params->nelts == 5)
1977     {
1978       /* Clients before 1.4 don't send the text_deltas boolean or depth. */
1979       SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cbbc", &rev, &target,
1980                                       &recurse, &ignore_ancestry, &versus_url));
1981       text_deltas = TRUE;
1982       depth_word = NULL;
1983     }
1984   else
1985     {
1986       SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cbbcb?w",
1987                                       &rev, &target, &recurse,
1988                                       &ignore_ancestry, &versus_url,
1989                                       &text_deltas, &depth_word));
1990     }
1991   target = svn_relpath_canonicalize(target, pool);
1992   versus_url = svn_uri_canonicalize(versus_url, pool);
1993
1994   if (depth_word)
1995     depth = svn_depth_from_word(depth_word);
1996   else
1997     depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
1998
1999   SVN_ERR(trivial_auth_request(conn, pool, b));
2000
2001   if (!SVN_IS_VALID_REVNUM(rev))
2002     SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
2003   SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool),
2004                           svn_path_uri_decode(versus_url, pool),
2005                           &versus_path));
2006
2007   {
2008     const char *full_path = svn_fspath__join(b->fs_path->data, target, pool);
2009     svn_revnum_t from_rev;
2010     SVN_ERR(accept_report(NULL, &from_rev,
2011                           conn, pool, b, rev, target, versus_path,
2012                           text_deltas, depth, FALSE, ignore_ancestry));
2013     SVN_ERR(log_command(b, conn, pool, "%s",
2014                         svn_log__diff(full_path, from_rev, versus_path,
2015                                       rev, depth, ignore_ancestry,
2016                                       pool)));
2017   }
2018   return SVN_NO_ERROR;
2019 }
2020
2021 /* Regardless of whether a client's capabilities indicate an
2022    understanding of this command (by way of SVN_RA_SVN_CAP_MERGEINFO),
2023    we provide a response.
2024
2025    ASSUMPTION: When performing a 'merge' with two URLs at different
2026    revisions, the client will call this command more than once. */
2027 static svn_error_t *get_mergeinfo(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2028                                   apr_array_header_t *params, void *baton)
2029 {
2030   server_baton_t *b = baton;
2031   svn_revnum_t rev;
2032   apr_array_header_t *paths, *canonical_paths;
2033   svn_mergeinfo_catalog_t mergeinfo;
2034   int i;
2035   apr_hash_index_t *hi;
2036   const char *inherit_word;
2037   svn_mergeinfo_inheritance_t inherit;
2038   svn_boolean_t include_descendants;
2039   apr_pool_t *iterpool;
2040   authz_baton_t ab;
2041
2042   ab.server = b;
2043   ab.conn = conn;
2044
2045   SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "l(?r)wb", &paths, &rev,
2046                                   &inherit_word, &include_descendants));
2047   inherit = svn_inheritance_from_word(inherit_word);
2048
2049   /* Canonicalize the paths which mergeinfo has been requested for. */
2050   canonical_paths = apr_array_make(pool, paths->nelts, sizeof(const char *));
2051   for (i = 0; i < paths->nelts; i++)
2052      {
2053         svn_ra_svn_item_t *item = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t);
2054         const char *full_path;
2055
2056         if (item->kind != SVN_RA_SVN_STRING)
2057           return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2058                                   _("Path is not a string"));
2059         full_path = svn_relpath_canonicalize(item->u.string->data, pool);
2060         full_path = svn_fspath__join(b->fs_path->data, full_path, pool);
2061         APR_ARRAY_PUSH(canonical_paths, const char *) = full_path;
2062      }
2063
2064   SVN_ERR(log_command(b, conn, pool, "%s",
2065                       svn_log__get_mergeinfo(canonical_paths, inherit,
2066                                              include_descendants,
2067                                              pool)));
2068
2069   SVN_ERR(trivial_auth_request(conn, pool, b));
2070   SVN_CMD_ERR(svn_repos_fs_get_mergeinfo(&mergeinfo, b->repos,
2071                                          canonical_paths, rev,
2072                                          inherit,
2073                                          include_descendants,
2074                                          authz_check_access_cb_func(b), &ab,
2075                                          pool));
2076   SVN_ERR(svn_mergeinfo__remove_prefix_from_catalog(&mergeinfo, mergeinfo,
2077                                                     b->fs_path->data, pool));
2078   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
2079   iterpool = svn_pool_create(pool);
2080   for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
2081     {
2082       const char *key = svn__apr_hash_index_key(hi);
2083       svn_mergeinfo_t value = svn__apr_hash_index_val(hi);
2084       svn_string_t *mergeinfo_string;
2085
2086       svn_pool_clear(iterpool);
2087
2088       SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, value, iterpool));
2089       SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "cs", key,
2090                                       mergeinfo_string));
2091     }
2092   svn_pool_destroy(iterpool);
2093   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2094
2095   return SVN_NO_ERROR;
2096 }
2097
2098 /* Send a log entry to the client. */
2099 static svn_error_t *log_receiver(void *baton,
2100                                  svn_log_entry_t *log_entry,
2101                                  apr_pool_t *pool)
2102 {
2103   log_baton_t *b = baton;
2104   svn_ra_svn_conn_t *conn = b->conn;
2105   apr_hash_index_t *h;
2106   svn_boolean_t invalid_revnum = FALSE;
2107   char action[2];
2108   const char *author, *date, *message;
2109   apr_uint64_t revprop_count;
2110
2111   if (log_entry->revision == SVN_INVALID_REVNUM)
2112     {
2113       /* If the stack depth is zero, we've seen the last revision, so don't
2114          send it, just return. */
2115       if (b->stack_depth == 0)
2116         return SVN_NO_ERROR;
2117
2118       /* Because the svn protocol won't let us send an invalid revnum, we have
2119          to fudge here and send an additional flag. */
2120       log_entry->revision = 0;
2121       invalid_revnum = TRUE;
2122       b->stack_depth--;
2123     }
2124
2125   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "(!"));
2126   if (log_entry->changed_paths2)
2127     {
2128       for (h = apr_hash_first(pool, log_entry->changed_paths2); h;
2129                                                         h = apr_hash_next(h))
2130         {
2131           const char *path = svn__apr_hash_index_key(h);
2132           svn_log_changed_path2_t *change = svn__apr_hash_index_val(h);
2133
2134           action[0] = change->action;
2135           action[1] = '\0';
2136           SVN_ERR(svn_ra_svn__write_tuple(
2137                       conn, pool, "cw(?cr)(cbb)",
2138                       path,
2139                       action,
2140                       change->copyfrom_path,
2141                       change->copyfrom_rev,
2142                       svn_node_kind_to_word(change->node_kind),
2143                       /* text_modified and props_modified are never unknown */
2144                       change->text_modified  == svn_tristate_true,
2145                       change->props_modified == svn_tristate_true));
2146         }
2147     }
2148   svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
2149   svn_compat_log_revprops_clear(log_entry->revprops);
2150   if (log_entry->revprops)
2151     revprop_count = apr_hash_count(log_entry->revprops);
2152   else
2153     revprop_count = 0;
2154   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)r(?c)(?c)(?c)bbn(!",
2155                                   log_entry->revision,
2156                                   author, date, message,
2157                                   log_entry->has_children,
2158                                   invalid_revnum, revprop_count));
2159   SVN_ERR(svn_ra_svn__write_proplist(conn, pool, log_entry->revprops));
2160   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b",
2161                                   log_entry->subtractive_merge));
2162
2163   if (log_entry->has_children)
2164     b->stack_depth++;
2165
2166   return SVN_NO_ERROR;
2167 }
2168
2169 static svn_error_t *log_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2170                             apr_array_header_t *params, void *baton)
2171 {
2172   svn_error_t *err, *write_err;
2173   server_baton_t *b = baton;
2174   svn_revnum_t start_rev, end_rev;
2175   const char *full_path;
2176   svn_boolean_t send_changed_paths, strict_node, include_merged_revisions;
2177   apr_array_header_t *paths, *full_paths, *revprop_items, *revprops;
2178   char *revprop_word;
2179   svn_ra_svn_item_t *elt;
2180   int i;
2181   apr_uint64_t limit, include_merged_revs_param;
2182   log_baton_t lb;
2183   authz_baton_t ab;
2184
2185   ab.server = b;
2186   ab.conn = conn;
2187
2188   SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "l(?r)(?r)bb?n?Bwl", &paths,
2189                                   &start_rev, &end_rev, &send_changed_paths,
2190                                   &strict_node, &limit,
2191                                   &include_merged_revs_param,
2192                                   &revprop_word, &revprop_items));
2193
2194   if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
2195     include_merged_revisions = FALSE;
2196   else
2197     include_merged_revisions = (svn_boolean_t) include_merged_revs_param;
2198
2199   if (revprop_word == NULL)
2200     /* pre-1.5 client */
2201     revprops = svn_compat_log_revprops_in(pool);
2202   else if (strcmp(revprop_word, "all-revprops") == 0)
2203     revprops = NULL;
2204   else if (strcmp(revprop_word, "revprops") == 0)
2205     {
2206       SVN_ERR_ASSERT(revprop_items);
2207
2208       revprops = apr_array_make(pool, revprop_items->nelts,
2209                                 sizeof(char *));
2210       for (i = 0; i < revprop_items->nelts; i++)
2211         {
2212           elt = &APR_ARRAY_IDX(revprop_items, i, svn_ra_svn_item_t);
2213           if (elt->kind != SVN_RA_SVN_STRING)
2214             return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2215                                     _("Log revprop entry not a string"));
2216           APR_ARRAY_PUSH(revprops, const char *) = elt->u.string->data;
2217         }
2218     }
2219   else
2220     return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2221                              _("Unknown revprop word '%s' in log command"),
2222                              revprop_word);
2223
2224   /* If we got an unspecified number then the user didn't send us anything,
2225      so we assume no limit.  If it's larger than INT_MAX then someone is
2226      messing with us, since we know the svn client libraries will never send
2227      us anything that big, so play it safe and default to no limit. */
2228   if (limit == SVN_RA_SVN_UNSPECIFIED_NUMBER || limit > INT_MAX)
2229     limit = 0;
2230
2231   full_paths = apr_array_make(pool, paths->nelts, sizeof(const char *));
2232   for (i = 0; i < paths->nelts; i++)
2233     {
2234       elt = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t);
2235       if (elt->kind != SVN_RA_SVN_STRING)
2236         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2237                                 _("Log path entry not a string"));
2238       full_path = svn_relpath_canonicalize(elt->u.string->data, pool),
2239       full_path = svn_fspath__join(b->fs_path->data, full_path, pool);
2240       APR_ARRAY_PUSH(full_paths, const char *) = full_path;
2241     }
2242   SVN_ERR(trivial_auth_request(conn, pool, b));
2243
2244   SVN_ERR(log_command(b, conn, pool, "%s",
2245                       svn_log__log(full_paths, start_rev, end_rev,
2246                                    (int) limit, send_changed_paths,
2247                                    strict_node, include_merged_revisions,
2248                                    revprops, pool)));
2249
2250   /* Get logs.  (Can't report errors back to the client at this point.) */
2251   lb.fs_path = b->fs_path->data;
2252   lb.conn = conn;
2253   lb.stack_depth = 0;
2254   err = svn_repos_get_logs4(b->repos, full_paths, start_rev, end_rev,
2255                             (int) limit, send_changed_paths, strict_node,
2256                             include_merged_revisions, revprops,
2257                             authz_check_access_cb_func(b), &ab, log_receiver,
2258                             &lb, pool);
2259
2260   write_err = svn_ra_svn__write_word(conn, pool, "done");
2261   if (write_err)
2262     {
2263       svn_error_clear(err);
2264       return write_err;
2265     }
2266   SVN_CMD_ERR(err);
2267   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2268   return SVN_NO_ERROR;
2269 }
2270
2271 static svn_error_t *check_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2272                                apr_array_header_t *params, void *baton)
2273 {
2274   server_baton_t *b = baton;
2275   svn_revnum_t rev;
2276   const char *path, *full_path;
2277   svn_fs_root_t *root;
2278   svn_node_kind_t kind;
2279
2280   SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)", &path, &rev));
2281   full_path = svn_fspath__join(b->fs_path->data,
2282                                svn_relpath_canonicalize(path, pool), pool);
2283
2284   /* Check authorizations */
2285   SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
2286                            full_path, FALSE));
2287
2288   if (!SVN_IS_VALID_REVNUM(rev))
2289     SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
2290
2291   SVN_ERR(log_command(b, conn, pool, "check-path %s@%d",
2292                       svn_path_uri_encode(full_path, pool), rev));
2293
2294   SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
2295   SVN_CMD_ERR(svn_fs_check_path(&kind, root, full_path, pool));
2296   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "w",
2297                                          svn_node_kind_to_word(kind)));
2298   return SVN_NO_ERROR;
2299 }
2300
2301 static svn_error_t *stat_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2302                              apr_array_header_t *params, void *baton)
2303 {
2304   server_baton_t *b = baton;
2305   svn_revnum_t rev;
2306   const char *path, *full_path, *cdate;
2307   svn_fs_root_t *root;
2308   svn_dirent_t *dirent;
2309
2310   SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)", &path, &rev));
2311   full_path = svn_fspath__join(b->fs_path->data,
2312                                svn_relpath_canonicalize(path, pool), pool);
2313
2314   /* Check authorizations */
2315   SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
2316                            full_path, FALSE));
2317
2318   if (!SVN_IS_VALID_REVNUM(rev))
2319     SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
2320
2321   SVN_ERR(log_command(b, conn, pool, "stat %s@%d",
2322                       svn_path_uri_encode(full_path, pool), rev));
2323
2324   SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
2325   SVN_CMD_ERR(svn_repos_stat(&dirent, root, full_path, pool));
2326
2327   /* Need to return the equivalent of "(?l)", since that's what the
2328      client is reading.  */
2329
2330   if (dirent == NULL)
2331     {
2332       SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "()"));
2333       return SVN_NO_ERROR;
2334     }
2335
2336   cdate = (dirent->time == (time_t) -1) ? NULL
2337     : svn_time_to_cstring(dirent->time, pool);
2338
2339   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "((wnbr(?c)(?c)))",
2340                                          svn_node_kind_to_word(dirent->kind),
2341                                          (apr_uint64_t) dirent->size,
2342                                          dirent->has_props, dirent->created_rev,
2343                                          cdate, dirent->last_author));
2344
2345   return SVN_NO_ERROR;
2346 }
2347
2348 static svn_error_t *get_locations(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2349                                   apr_array_header_t *params, void *baton)
2350 {
2351   svn_error_t *err, *write_err;
2352   server_baton_t *b = baton;
2353   svn_revnum_t revision;
2354   apr_array_header_t *location_revisions, *loc_revs_proto;
2355   svn_ra_svn_item_t *elt;
2356   int i;
2357   const char *relative_path;
2358   svn_revnum_t peg_revision;
2359   apr_hash_t *fs_locations;
2360   const char *abs_path;
2361   authz_baton_t ab;
2362
2363   ab.server = b;
2364   ab.conn = conn;
2365
2366   /* Parse the arguments. */
2367   SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "crl", &relative_path,
2368                                   &peg_revision,
2369                                   &loc_revs_proto));
2370   relative_path = svn_relpath_canonicalize(relative_path, pool);
2371
2372   abs_path = svn_fspath__join(b->fs_path->data, relative_path, pool);
2373
2374   location_revisions = apr_array_make(pool, loc_revs_proto->nelts,
2375                                       sizeof(svn_revnum_t));
2376   for (i = 0; i < loc_revs_proto->nelts; i++)
2377     {
2378       elt = &APR_ARRAY_IDX(loc_revs_proto, i, svn_ra_svn_item_t);
2379       if (elt->kind != SVN_RA_SVN_NUMBER)
2380         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2381                                 "Get-locations location revisions entry "
2382                                 "not a revision number");
2383       revision = (svn_revnum_t)(elt->u.number);
2384       APR_ARRAY_PUSH(location_revisions, svn_revnum_t) = revision;
2385     }
2386   SVN_ERR(trivial_auth_request(conn, pool, b));
2387   SVN_ERR(log_command(b, conn, pool, "%s",
2388                       svn_log__get_locations(abs_path, peg_revision,
2389                                              location_revisions, pool)));
2390
2391   /* All the parameters are fine - let's perform the query against the
2392    * repository. */
2393
2394   /* We store both err and write_err here, so the client will get
2395    * the "done" even if there was an error in fetching the results. */
2396
2397   err = svn_repos_trace_node_locations(b->fs, &fs_locations, abs_path,
2398                                        peg_revision, location_revisions,
2399                                        authz_check_access_cb_func(b), &ab,
2400                                        pool);
2401
2402   /* Now, write the results to the connection. */
2403   if (!err)
2404     {
2405       if (fs_locations)
2406         {
2407           apr_hash_index_t *iter;
2408
2409           for (iter = apr_hash_first(pool, fs_locations); iter;
2410               iter = apr_hash_next(iter))
2411             {
2412               const svn_revnum_t *iter_key = svn__apr_hash_index_key(iter);
2413               const char *iter_value = svn__apr_hash_index_val(iter);
2414
2415               SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "rc",
2416                                               *iter_key, iter_value));
2417             }
2418         }
2419     }
2420
2421   write_err = svn_ra_svn__write_word(conn, pool, "done");
2422   if (write_err)
2423     {
2424       svn_error_clear(err);
2425       return write_err;
2426     }
2427   SVN_CMD_ERR(err);
2428
2429   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2430
2431   return SVN_NO_ERROR;
2432 }
2433
2434 static svn_error_t *gls_receiver(svn_location_segment_t *segment,
2435                                  void *baton,
2436                                  apr_pool_t *pool)
2437 {
2438   svn_ra_svn_conn_t *conn = baton;
2439   return svn_ra_svn__write_tuple(conn, pool, "rr(?c)",
2440                                  segment->range_start,
2441                                  segment->range_end,
2442                                  segment->path);
2443 }
2444
2445 static svn_error_t *get_location_segments(svn_ra_svn_conn_t *conn,
2446                                           apr_pool_t *pool,
2447                                           apr_array_header_t *params,
2448                                           void *baton)
2449 {
2450   svn_error_t *err, *write_err;
2451   server_baton_t *b = baton;
2452   svn_revnum_t peg_revision, start_rev, end_rev;
2453   const char *relative_path;
2454   const char *abs_path;
2455   authz_baton_t ab;
2456
2457   ab.server = b;
2458   ab.conn = conn;
2459
2460   /* Parse the arguments. */
2461   SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)(?r)(?r)",
2462                                   &relative_path, &peg_revision,
2463                                   &start_rev, &end_rev));
2464   relative_path = svn_relpath_canonicalize(relative_path, pool);
2465
2466   abs_path = svn_fspath__join(b->fs_path->data, relative_path, pool);
2467
2468   if (SVN_IS_VALID_REVNUM(start_rev)
2469       && SVN_IS_VALID_REVNUM(end_rev)
2470       && (end_rev > start_rev))
2471     {
2472       err = svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
2473                               "Get-location-segments end revision must not be "
2474                               "younger than start revision");
2475       return log_fail_and_flush(err, b, conn, pool);
2476     }
2477
2478   if (SVN_IS_VALID_REVNUM(peg_revision)
2479       && SVN_IS_VALID_REVNUM(start_rev)
2480       && (start_rev > peg_revision))
2481     {
2482       err = svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
2483                               "Get-location-segments start revision must not "
2484                               "be younger than peg revision");
2485       return log_fail_and_flush(err, b, conn, pool);
2486     }
2487
2488   SVN_ERR(trivial_auth_request(conn, pool, b));
2489   SVN_ERR(log_command(baton, conn, pool, "%s",
2490                       svn_log__get_location_segments(abs_path, peg_revision,
2491                                                      start_rev, end_rev,
2492                                                      pool)));
2493
2494   /* All the parameters are fine - let's perform the query against the
2495    * repository. */
2496
2497   /* We store both err and write_err here, so the client will get
2498    * the "done" even if there was an error in fetching the results. */
2499
2500   err = svn_repos_node_location_segments(b->repos, abs_path,
2501                                          peg_revision, start_rev, end_rev,
2502                                          gls_receiver, (void *)conn,
2503                                          authz_check_access_cb_func(b), &ab,
2504                                          pool);
2505   write_err = svn_ra_svn__write_word(conn, pool, "done");
2506   if (write_err)
2507     {
2508       svn_error_clear(err);
2509       return write_err;
2510     }
2511   SVN_CMD_ERR(err);
2512
2513   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2514
2515   return SVN_NO_ERROR;
2516 }
2517
2518 /* This implements svn_write_fn_t.  Write LEN bytes starting at DATA to the
2519    client as a string. */
2520 static svn_error_t *svndiff_handler(void *baton, const char *data,
2521                                     apr_size_t *len)
2522 {
2523   file_revs_baton_t *b = baton;
2524   svn_string_t str;
2525
2526   str.data = data;
2527   str.len = *len;
2528   return svn_ra_svn__write_string(b->conn, b->pool, &str);
2529 }
2530
2531 /* This implements svn_close_fn_t.  Mark the end of the data by writing an
2532    empty string to the client. */
2533 static svn_error_t *svndiff_close_handler(void *baton)
2534 {
2535   file_revs_baton_t *b = baton;
2536
2537   SVN_ERR(svn_ra_svn__write_cstring(b->conn, b->pool, ""));
2538   return SVN_NO_ERROR;
2539 }
2540
2541 /* This implements the svn_repos_file_rev_handler_t interface. */
2542 static svn_error_t *file_rev_handler(void *baton, const char *path,
2543                                      svn_revnum_t rev, apr_hash_t *rev_props,
2544                                      svn_boolean_t merged_revision,
2545                                      svn_txdelta_window_handler_t *d_handler,
2546                                      void **d_baton,
2547                                      apr_array_header_t *prop_diffs,
2548                                      apr_pool_t *pool)
2549 {
2550   file_revs_baton_t *frb = baton;
2551   svn_stream_t *stream;
2552
2553   SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "cr(!",
2554                                   path, rev));
2555   SVN_ERR(svn_ra_svn__write_proplist(frb->conn, pool, rev_props));
2556   SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "!)(!"));
2557   SVN_ERR(write_prop_diffs(frb->conn, pool, prop_diffs));
2558   SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "!)b", merged_revision));
2559
2560   /* Store the pool for the delta stream. */
2561   frb->pool = pool;
2562
2563   /* Prepare for the delta or just write an empty string. */
2564   if (d_handler)
2565     {
2566       stream = svn_stream_create(baton, pool);
2567       svn_stream_set_write(stream, svndiff_handler);
2568       svn_stream_set_close(stream, svndiff_close_handler);
2569
2570       /* If the connection does not support SVNDIFF1 or if we don't want to use
2571        * compression, use the non-compressing "version 0" implementation */
2572       if (   svn_ra_svn_compression_level(frb->conn) > 0
2573           && svn_ra_svn_has_capability(frb->conn, SVN_RA_SVN_CAP_SVNDIFF1))
2574         svn_txdelta_to_svndiff3(d_handler, d_baton, stream, 1,
2575                                 svn_ra_svn_compression_level(frb->conn), pool);
2576       else
2577         svn_txdelta_to_svndiff3(d_handler, d_baton, stream, 0,
2578                                 svn_ra_svn_compression_level(frb->conn), pool);
2579     }
2580   else
2581     SVN_ERR(svn_ra_svn__write_cstring(frb->conn, pool, ""));
2582
2583   return SVN_NO_ERROR;
2584 }
2585
2586 static svn_error_t *get_file_revs(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2587                                   apr_array_header_t *params, void *baton)
2588 {
2589   server_baton_t *b = baton;
2590   svn_error_t *err, *write_err;
2591   file_revs_baton_t frb;
2592   svn_revnum_t start_rev, end_rev;
2593   const char *path;
2594   const char *full_path;
2595   apr_uint64_t include_merged_revs_param;
2596   svn_boolean_t include_merged_revisions;
2597   authz_baton_t ab;
2598
2599   ab.server = b;
2600   ab.conn = conn;
2601
2602   /* Parse arguments. */
2603   SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)(?r)?B",
2604                                   &path, &start_rev, &end_rev,
2605                                   &include_merged_revs_param));
2606   path = svn_relpath_canonicalize(path, pool);
2607   SVN_ERR(trivial_auth_request(conn, pool, b));
2608   full_path = svn_fspath__join(b->fs_path->data, path, pool);
2609
2610   if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
2611     include_merged_revisions = FALSE;
2612   else
2613     include_merged_revisions = (svn_boolean_t) include_merged_revs_param;
2614
2615   SVN_ERR(log_command(b, conn, pool, "%s",
2616                       svn_log__get_file_revs(full_path, start_rev, end_rev,
2617                                              include_merged_revisions,
2618                                              pool)));
2619
2620   frb.conn = conn;
2621   frb.pool = NULL;
2622
2623   err = svn_repos_get_file_revs2(b->repos, full_path, start_rev, end_rev,
2624                                  include_merged_revisions,
2625                                  authz_check_access_cb_func(b), &ab,
2626                                  file_rev_handler, &frb, pool);
2627   write_err = svn_ra_svn__write_word(conn, pool, "done");
2628   if (write_err)
2629     {
2630       svn_error_clear(err);
2631       return write_err;
2632     }
2633   SVN_CMD_ERR(err);
2634   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2635
2636   return SVN_NO_ERROR;
2637 }
2638
2639 static svn_error_t *lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2640                          apr_array_header_t *params, void *baton)
2641 {
2642   server_baton_t *b = baton;
2643   const char *path;
2644   const char *comment;
2645   const char *full_path;
2646   svn_boolean_t steal_lock;
2647   svn_revnum_t current_rev;
2648   svn_lock_t *l;
2649
2650   SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?c)b(?r)", &path, &comment,
2651                                   &steal_lock, &current_rev));
2652   full_path = svn_fspath__join(b->fs_path->data,
2653                                svn_relpath_canonicalize(path, pool), pool);
2654
2655   SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
2656                            full_path, TRUE));
2657   SVN_ERR(log_command(b, conn, pool, "%s",
2658                       svn_log__lock_one_path(full_path, steal_lock, pool)));
2659
2660   SVN_CMD_ERR(svn_repos_fs_lock(&l, b->repos, full_path, NULL, comment, 0,
2661                                 0, /* No expiration time. */
2662                                 current_rev, steal_lock, pool));
2663
2664   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(!", "success"));
2665   SVN_ERR(write_lock(conn, pool, l));
2666   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)"));
2667
2668   return SVN_NO_ERROR;
2669 }
2670
2671 static svn_error_t *lock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2672                               apr_array_header_t *params, void *baton)
2673 {
2674   server_baton_t *b = baton;
2675   apr_array_header_t *path_revs;
2676   const char *comment;
2677   svn_boolean_t steal_lock;
2678   int i;
2679   apr_pool_t *subpool;
2680   const char *path;
2681   const char *full_path;
2682   svn_revnum_t current_rev;
2683   apr_array_header_t *log_paths;
2684   svn_lock_t *l;
2685   svn_error_t *err = SVN_NO_ERROR, *write_err;
2686
2687   SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?c)bl", &comment, &steal_lock,
2688                                   &path_revs));
2689
2690   subpool = svn_pool_create(pool);
2691
2692   /* Because we can only send a single auth reply per request, we send
2693      a reply before parsing the lock commands.  This means an authz
2694      access denial will abort the processing of the locks and return
2695      an error. */
2696   SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, TRUE));
2697
2698   /* Loop through the lock requests. */
2699   log_paths = apr_array_make(pool, path_revs->nelts, sizeof(full_path));
2700   for (i = 0; i < path_revs->nelts; ++i)
2701     {
2702       svn_ra_svn_item_t *item = &APR_ARRAY_IDX(path_revs, i,
2703                                                svn_ra_svn_item_t);
2704
2705       svn_pool_clear(subpool);
2706
2707       if (item->kind != SVN_RA_SVN_LIST)
2708         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2709                                 "Lock requests should be list of lists");
2710
2711       SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, pool, "c(?r)", &path,
2712                                       &current_rev));
2713
2714       /* Allocate the full_path out of pool so it will survive for use
2715        * by operational logging, after this loop. */
2716       full_path = svn_fspath__join(b->fs_path->data,
2717                                    svn_relpath_canonicalize(path, subpool),
2718                                    pool);
2719       APR_ARRAY_PUSH(log_paths, const char *) = full_path;
2720
2721       if (! lookup_access(pool, b, conn, svn_authz_write, full_path, TRUE))
2722         {
2723           err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, NULL,
2724                                      b, conn, pool);
2725           break;
2726         }
2727
2728       err = svn_repos_fs_lock(&l, b->repos, full_path,
2729                               NULL, comment, FALSE,
2730                               0, /* No expiration time. */
2731                               current_rev,
2732                               steal_lock, subpool);
2733
2734       if (err)
2735         {
2736           if (SVN_ERR_IS_LOCK_ERROR(err))
2737             {
2738               write_err = svn_ra_svn__write_cmd_failure(conn, pool, err);
2739               svn_error_clear(err);
2740               err = NULL;
2741               SVN_ERR(write_err);
2742             }
2743           else
2744             break;
2745         }
2746       else
2747         {
2748           SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "w!", "success"));
2749           SVN_ERR(write_lock(conn, subpool, l));
2750           SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "!"));
2751         }
2752     }
2753
2754   svn_pool_destroy(subpool);
2755
2756   SVN_ERR(log_command(b, conn, pool, "%s",
2757                       svn_log__lock(log_paths, steal_lock, pool)));
2758
2759   /* NOTE: err might contain a fatal locking error from the loop above. */
2760   write_err = svn_ra_svn__write_word(conn, pool, "done");
2761   if (!write_err)
2762     SVN_CMD_ERR(err);
2763   svn_error_clear(err);
2764   SVN_ERR(write_err);
2765   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2766
2767   return SVN_NO_ERROR;
2768 }
2769
2770 static svn_error_t *unlock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2771                            apr_array_header_t *params, void *baton)
2772 {
2773   server_baton_t *b = baton;
2774   const char *path, *token, *full_path;
2775   svn_boolean_t break_lock;
2776
2777   SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?c)b", &path, &token,
2778                                  &break_lock));
2779
2780   full_path = svn_fspath__join(b->fs_path->data,
2781                                svn_relpath_canonicalize(path, pool), pool);
2782
2783   /* Username required unless break_lock was specified. */
2784   SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
2785                            full_path, ! break_lock));
2786   SVN_ERR(log_command(b, conn, pool, "%s",
2787                       svn_log__unlock_one_path(full_path, break_lock, pool)));
2788
2789   SVN_CMD_ERR(svn_repos_fs_unlock(b->repos, full_path, token, break_lock,
2790                                   pool));
2791
2792   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2793
2794   return SVN_NO_ERROR;
2795 }
2796
2797 static svn_error_t *unlock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2798                                 apr_array_header_t *params, void *baton)
2799 {
2800   server_baton_t *b = baton;
2801   svn_boolean_t break_lock;
2802   apr_array_header_t *unlock_tokens;
2803   int i;
2804   apr_pool_t *subpool;
2805   const char *path;
2806   const char *full_path;
2807   apr_array_header_t *log_paths;
2808   const char *token;
2809   svn_error_t *err = SVN_NO_ERROR, *write_err;
2810
2811   SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "bl", &break_lock,
2812                                   &unlock_tokens));
2813
2814   /* Username required unless break_lock was specified. */
2815   SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, ! break_lock));
2816
2817   subpool = svn_pool_create(pool);
2818
2819   /* Loop through the unlock requests. */
2820   log_paths = apr_array_make(pool, unlock_tokens->nelts, sizeof(full_path));
2821   for (i = 0; i < unlock_tokens->nelts; i++)
2822     {
2823       svn_ra_svn_item_t *item = &APR_ARRAY_IDX(unlock_tokens, i,
2824                                                svn_ra_svn_item_t);
2825
2826       svn_pool_clear(subpool);
2827
2828       if (item->kind != SVN_RA_SVN_LIST)
2829         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2830                                 "Unlock request should be a list of lists");
2831
2832       SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, subpool, "c(?c)", &path,
2833                                       &token));
2834
2835       /* Allocate the full_path out of pool so it will survive for use
2836        * by operational logging, after this loop. */
2837       full_path = svn_fspath__join(b->fs_path->data,
2838                                    svn_relpath_canonicalize(path, subpool),
2839                                    pool);
2840       APR_ARRAY_PUSH(log_paths, const char *) = full_path;
2841
2842       if (! lookup_access(subpool, b, conn, svn_authz_write, full_path,
2843                           ! break_lock))
2844         return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR,
2845                                 error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED,
2846                                                      NULL, NULL,
2847                                                      b, conn, pool),
2848                                 NULL);
2849
2850       err = svn_repos_fs_unlock(b->repos, full_path, token, break_lock,
2851                                 subpool);
2852       if (err)
2853         {
2854           if (SVN_ERR_IS_UNLOCK_ERROR(err))
2855             {
2856               write_err = svn_ra_svn__write_cmd_failure(conn, pool, err);
2857               svn_error_clear(err);
2858               err = NULL;
2859               SVN_ERR(write_err);
2860             }
2861           else
2862             break;
2863         }
2864       else
2865         SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "w(c)", "success",
2866                                         path));
2867     }
2868
2869   svn_pool_destroy(subpool);
2870
2871   SVN_ERR(log_command(b, conn, pool, "%s",
2872                       svn_log__unlock(log_paths, break_lock, pool)));
2873
2874   /* NOTE: err might contain a fatal unlocking error from the loop above. */
2875   write_err = svn_ra_svn__write_word(conn, pool, "done");
2876   if (! write_err)
2877     SVN_CMD_ERR(err);
2878   svn_error_clear(err);
2879   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2880
2881   return SVN_NO_ERROR;
2882 }
2883
2884 static svn_error_t *get_lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2885                              apr_array_header_t *params, void *baton)
2886 {
2887   server_baton_t *b = baton;
2888   const char *path;
2889   const char *full_path;
2890   svn_lock_t *l;
2891
2892   SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &path));
2893
2894   full_path = svn_fspath__join(b->fs_path->data,
2895                                svn_relpath_canonicalize(path, pool), pool);
2896
2897   SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
2898                            full_path, FALSE));
2899   SVN_ERR(log_command(b, conn, pool, "get-lock %s",
2900                       svn_path_uri_encode(full_path, pool)));
2901
2902   SVN_CMD_ERR(svn_fs_get_lock(&l, b->fs, full_path, pool));
2903
2904   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
2905   if (l)
2906     SVN_ERR(write_lock(conn, pool, l));
2907   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2908
2909   return SVN_NO_ERROR;
2910 }
2911
2912 static svn_error_t *get_locks(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2913                               apr_array_header_t *params, void *baton)
2914 {
2915   server_baton_t *b = baton;
2916   const char *path;
2917   const char *full_path;
2918   const char *depth_word;
2919   svn_depth_t depth;
2920   apr_hash_t *locks;
2921   apr_hash_index_t *hi;
2922   svn_error_t *err;
2923   authz_baton_t ab;
2924
2925   ab.server = b;
2926   ab.conn = conn;
2927
2928   SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c?(?w)", &path, &depth_word));
2929
2930   depth = depth_word ? svn_depth_from_word(depth_word) : svn_depth_infinity;
2931   if ((depth != svn_depth_empty) &&
2932       (depth != svn_depth_files) &&
2933       (depth != svn_depth_immediates) &&
2934       (depth != svn_depth_infinity))
2935     {
2936       err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
2937                              "Invalid 'depth' specified in get-locks request");
2938       return log_fail_and_flush(err, b, conn, pool);
2939     }
2940
2941   full_path = svn_fspath__join(b->fs_path->data,
2942                                svn_relpath_canonicalize(path, pool), pool);
2943
2944   SVN_ERR(trivial_auth_request(conn, pool, b));
2945
2946   SVN_ERR(log_command(b, conn, pool, "get-locks %s",
2947                       svn_path_uri_encode(full_path, pool)));
2948   SVN_CMD_ERR(svn_repos_fs_get_locks2(&locks, b->repos, full_path, depth,
2949                                       authz_check_access_cb_func(b), &ab,
2950                                       pool));
2951
2952   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
2953   for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
2954     {
2955       svn_lock_t *l = svn__apr_hash_index_val(hi);
2956
2957       SVN_ERR(write_lock(conn, pool, l));
2958     }
2959   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2960
2961   return SVN_NO_ERROR;
2962 }
2963
2964 static svn_error_t *replay_one_revision(svn_ra_svn_conn_t *conn,
2965                                         server_baton_t *b,
2966                                         svn_revnum_t rev,
2967                                         svn_revnum_t low_water_mark,
2968                                         svn_boolean_t send_deltas,
2969                                         apr_pool_t *pool)
2970 {
2971   const svn_delta_editor_t *editor;
2972   void *edit_baton;
2973   svn_fs_root_t *root;
2974   svn_error_t *err;
2975   authz_baton_t ab;
2976
2977   ab.server = b;
2978   ab.conn = conn;
2979
2980   SVN_ERR(log_command(b, conn, pool,
2981                       svn_log__replay(b->fs_path->data, rev, pool)));
2982
2983   svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL);
2984
2985   err = svn_fs_revision_root(&root, b->fs, rev, pool);
2986
2987   if (! err)
2988     err = svn_repos_replay2(root, b->fs_path->data, low_water_mark,
2989                             send_deltas, editor, edit_baton,
2990                             authz_check_access_cb_func(b), &ab, pool);
2991
2992   if (err)
2993     svn_error_clear(editor->abort_edit(edit_baton, pool));
2994   SVN_CMD_ERR(err);
2995
2996   return svn_ra_svn__write_cmd_finish_replay(conn, pool);
2997 }
2998
2999 static svn_error_t *replay(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
3000                            apr_array_header_t *params, void *baton)
3001 {
3002   svn_revnum_t rev, low_water_mark;
3003   svn_boolean_t send_deltas;
3004   server_baton_t *b = baton;
3005
3006   SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rrb", &rev, &low_water_mark,
3007                                  &send_deltas));
3008
3009   SVN_ERR(trivial_auth_request(conn, pool, b));
3010
3011   SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark,
3012                               send_deltas, pool));
3013
3014   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
3015
3016   return SVN_NO_ERROR;
3017 }
3018
3019 static svn_error_t *replay_range(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
3020                                  apr_array_header_t *params, void *baton)
3021 {
3022   svn_revnum_t start_rev, end_rev, rev, low_water_mark;
3023   svn_boolean_t send_deltas;
3024   server_baton_t *b = baton;
3025   apr_pool_t *iterpool;
3026   authz_baton_t ab;
3027
3028   ab.server = b;
3029   ab.conn = conn;
3030
3031   SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rrrb", &start_rev,
3032                                  &end_rev, &low_water_mark,
3033                                  &send_deltas));
3034
3035   SVN_ERR(trivial_auth_request(conn, pool, b));
3036
3037   iterpool = svn_pool_create(pool);
3038   for (rev = start_rev; rev <= end_rev; rev++)
3039     {
3040       apr_hash_t *props;
3041
3042       svn_pool_clear(iterpool);
3043
3044       SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repos, rev,
3045                                                  authz_check_access_cb_func(b),
3046                                                  &ab,
3047                                                  iterpool));
3048       SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "w(!", "revprops"));
3049       SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, props));
3050       SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!)"));
3051
3052       SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark,
3053                                   send_deltas, iterpool));
3054
3055     }
3056   svn_pool_destroy(iterpool);
3057
3058   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
3059
3060   return SVN_NO_ERROR;
3061 }
3062
3063 static svn_error_t *
3064 get_deleted_rev(svn_ra_svn_conn_t *conn,
3065                 apr_pool_t *pool,
3066                 apr_array_header_t *params,
3067                 void *baton)
3068 {
3069   server_baton_t *b = baton;
3070   const char *path, *full_path;
3071   svn_revnum_t peg_revision;
3072   svn_revnum_t end_revision;
3073   svn_revnum_t revision_deleted;
3074
3075   SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "crr",
3076                                  &path, &peg_revision, &end_revision));
3077   full_path = svn_fspath__join(b->fs_path->data,
3078                                svn_relpath_canonicalize(path, pool), pool);
3079   SVN_ERR(log_command(b, conn, pool, "get-deleted-rev"));
3080   SVN_ERR(trivial_auth_request(conn, pool, b));
3081   SVN_ERR(svn_repos_deleted_rev(b->fs, full_path, peg_revision, end_revision,
3082                                 &revision_deleted, pool));
3083   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", revision_deleted));
3084   return SVN_NO_ERROR;
3085 }
3086
3087 static svn_error_t *
3088 get_inherited_props(svn_ra_svn_conn_t *conn,
3089                     apr_pool_t *pool,
3090                     apr_array_header_t *params,
3091                     void *baton)
3092 {
3093   server_baton_t *b = baton;
3094   const char *path, *full_path;
3095   svn_revnum_t rev;
3096   svn_fs_root_t *root;
3097   apr_array_header_t *inherited_props;
3098   int i;
3099   apr_pool_t *iterpool = svn_pool_create(pool);
3100   authz_baton_t ab;
3101
3102   ab.server = b;
3103   ab.conn = conn;
3104
3105   /* Parse arguments. */
3106   SVN_ERR(svn_ra_svn__parse_tuple(params, iterpool, "c(?r)", &path, &rev));
3107
3108   full_path = svn_fspath__join(b->fs_path->data,
3109                                svn_relpath_canonicalize(path, iterpool),
3110                                pool);
3111
3112   /* Check authorizations */
3113   SVN_ERR(must_have_access(conn, iterpool, b, svn_authz_read,
3114                            full_path, FALSE));
3115
3116   if (!SVN_IS_VALID_REVNUM(rev))
3117     SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
3118
3119   SVN_ERR(log_command(b, conn, pool, "%s",
3120                       svn_log__get_inherited_props(full_path, rev,
3121                                                    iterpool)));
3122
3123   /* Fetch the properties and a stream for the contents. */
3124   SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, iterpool));
3125   SVN_CMD_ERR(get_props(NULL, &inherited_props, &ab, root, full_path, pool));
3126
3127   /* Send successful command response with revision and props. */
3128   SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "w(!", "success"));
3129
3130   SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(?!"));
3131
3132   for (i = 0; i < inherited_props->nelts; i++)
3133     {
3134       svn_prop_inherited_item_t *iprop =
3135         APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
3136
3137       svn_pool_clear(iterpool);
3138       SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!",
3139                                       iprop->path_or_url));
3140       SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash));
3141       SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!",
3142                                       iprop->path_or_url));
3143     }
3144
3145   SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))"));
3146   svn_pool_destroy(iterpool);
3147   return SVN_NO_ERROR;
3148 }
3149
3150 static const svn_ra_svn_cmd_entry_t main_commands[] = {
3151   { "reparent",        reparent },
3152   { "get-latest-rev",  get_latest_rev },
3153   { "get-dated-rev",   get_dated_rev },
3154   { "change-rev-prop", change_rev_prop },
3155   { "change-rev-prop2",change_rev_prop2 },
3156   { "rev-proplist",    rev_proplist },
3157   { "rev-prop",        rev_prop },
3158   { "commit",          commit },
3159   { "get-file",        get_file },
3160   { "get-dir",         get_dir },
3161   { "update",          update },
3162   { "switch",          switch_cmd },
3163   { "status",          status },
3164   { "diff",            diff },
3165   { "get-mergeinfo",   get_mergeinfo },
3166   { "log",             log_cmd },
3167   { "check-path",      check_path },
3168   { "stat",            stat_cmd },
3169   { "get-locations",   get_locations },
3170   { "get-location-segments",   get_location_segments },
3171   { "get-file-revs",   get_file_revs },
3172   { "lock",            lock },
3173   { "lock-many",       lock_many },
3174   { "unlock",          unlock },
3175   { "unlock-many",     unlock_many },
3176   { "get-lock",        get_lock },
3177   { "get-locks",       get_locks },
3178   { "replay",          replay },
3179   { "replay-range",    replay_range },
3180   { "get-deleted-rev", get_deleted_rev },
3181   { "get-iprops",      get_inherited_props },
3182   { NULL }
3183 };
3184
3185 /* Skip past the scheme part of a URL, including the tunnel specification
3186  * if present.  Return NULL if the scheme part is invalid for ra_svn. */
3187 static const char *skip_scheme_part(const char *url)
3188 {
3189   if (strncmp(url, "svn", 3) != 0)
3190     return NULL;
3191   url += 3;
3192   if (*url == '+')
3193     url += strcspn(url, ":");
3194   if (strncmp(url, "://", 3) != 0)
3195     return NULL;
3196   return url + 3;
3197 }
3198
3199 /* Check that PATH is a valid repository path, meaning it doesn't contain any
3200    '..' path segments.
3201    NOTE: This is similar to svn_path_is_backpath_present, but that function
3202    assumes the path separator is '/'.  This function also checks for
3203    segments delimited by the local path separator. */
3204 static svn_boolean_t
3205 repos_path_valid(const char *path)
3206 {
3207   const char *s = path;
3208
3209   while (*s)
3210     {
3211       /* Scan for the end of the segment. */
3212       while (*path && *path != '/' && *path != SVN_PATH_LOCAL_SEPARATOR)
3213         ++path;
3214
3215       /* Check for '..'. */
3216 #ifdef WIN32
3217       /* On Windows, don't allow sequences of more than one character
3218          consisting of just dots and spaces.  Win32 functions treat
3219          paths such as ".. " and "......." inconsistently.  Make sure
3220          no one can escape out of the root. */
3221       if (path - s >= 2 && strspn(s, ". ") == (size_t)(path - s))
3222         return FALSE;
3223 #else  /* ! WIN32 */
3224       if (path - s == 2 && s[0] == '.' && s[1] == '.')
3225         return FALSE;
3226 #endif
3227
3228       /* Skip all separators. */
3229       while (*path && (*path == '/' || *path == SVN_PATH_LOCAL_SEPARATOR))
3230         ++path;
3231       s = path;
3232     }
3233
3234   return TRUE;
3235 }
3236
3237 /* Look for the repository given by URL, using ROOT as the virtual
3238  * repository root.  If we find one, fill in the repos, fs, cfg,
3239  * repos_url, and fs_path fields of B.  Set B->repos's client
3240  * capabilities to CAPABILITIES, which must be at least as long-lived
3241  * as POOL, and whose elements are SVN_RA_CAPABILITY_*.
3242  */
3243 static svn_error_t *find_repos(const char *url, const char *root,
3244                                server_baton_t *b,
3245                                svn_ra_svn_conn_t *conn,
3246                                const apr_array_header_t *capabilities,
3247                                apr_pool_t *pool)
3248 {
3249   const char *path, *full_path, *repos_root, *fs_path, *hooks_env;
3250   svn_stringbuf_t *url_buf;
3251
3252   /* Skip past the scheme and authority part. */
3253   path = skip_scheme_part(url);
3254   if (path == NULL)
3255     return svn_error_createf(SVN_ERR_BAD_URL, NULL,
3256                              "Non-svn URL passed to svn server: '%s'", url);
3257
3258   if (! b->vhost)
3259     {
3260       path = strchr(path, '/');
3261       if (path == NULL)
3262         path = "";
3263     }
3264   path = svn_relpath_canonicalize(path, pool);
3265   path = svn_path_uri_decode(path, pool);
3266
3267   /* Ensure that it isn't possible to escape the root by disallowing
3268      '..' segments. */
3269   if (!repos_path_valid(path))
3270     return svn_error_create(SVN_ERR_BAD_FILENAME, NULL,
3271                             "Couldn't determine repository path");
3272
3273   /* Join the server-configured root with the client path. */
3274   full_path = svn_dirent_join(svn_dirent_canonicalize(root, pool),
3275                               path, pool);
3276
3277   /* Search for a repository in the full path. */
3278   repos_root = svn_repos_find_root_path(full_path, pool);
3279   if (!repos_root)
3280     return svn_error_createf(SVN_ERR_RA_SVN_REPOS_NOT_FOUND, NULL,
3281                              "No repository found in '%s'", url);
3282
3283   /* Open the repository and fill in b with the resulting information. */
3284   SVN_ERR(svn_repos_open2(&b->repos, repos_root, b->fs_config, pool));
3285   SVN_ERR(svn_repos_remember_client_capabilities(b->repos, capabilities));
3286   b->fs = svn_repos_fs(b->repos);
3287   fs_path = full_path + strlen(repos_root);
3288   b->fs_path = svn_stringbuf_create(*fs_path ? fs_path : "/", pool);
3289   url_buf = svn_stringbuf_create(url, pool);
3290   svn_path_remove_components(url_buf,
3291                              svn_path_component_count(b->fs_path->data));
3292   b->repos_url = url_buf->data;
3293   b->authz_repos_name = svn_dirent_is_child(root, repos_root, pool);
3294   if (b->authz_repos_name == NULL)
3295     b->repos_name = svn_dirent_basename(repos_root, pool);
3296   else
3297     b->repos_name = b->authz_repos_name;
3298   b->repos_name = svn_path_uri_encode(b->repos_name, pool);
3299
3300   /* If the svnserve configuration has not been loaded then load it from the
3301    * repository. */
3302   if (NULL == b->cfg)
3303     {
3304       b->base = svn_repos_conf_dir(b->repos, pool);
3305
3306       SVN_ERR(svn_config_read3(&b->cfg, svn_repos_svnserve_conf(b->repos, pool),
3307                                FALSE, /* must_exist */
3308                                FALSE, /* section_names_case_sensitive */
3309                                FALSE, /* option_names_case_sensitive */
3310                                pool));
3311       SVN_ERR(load_pwdb_config(b, conn, pool));
3312       SVN_ERR(load_authz_config(b, conn, repos_root, pool));
3313     }
3314   /* svnserve.conf has been loaded via the --config-file option so need
3315    * to load pwdb and authz. */
3316   else
3317     {
3318       SVN_ERR(load_pwdb_config(b, conn, pool));
3319       SVN_ERR(load_authz_config(b, conn, repos_root, pool));
3320     }
3321
3322 #ifdef SVN_HAVE_SASL
3323   /* Should we use Cyrus SASL? */
3324   SVN_ERR(svn_config_get_bool(b->cfg, &b->use_sasl, SVN_CONFIG_SECTION_SASL,
3325                               SVN_CONFIG_OPTION_USE_SASL, FALSE));
3326 #endif
3327
3328   /* Use the repository UUID as the default realm. */
3329   SVN_ERR(svn_fs_get_uuid(b->fs, &b->realm, pool));
3330   svn_config_get(b->cfg, &b->realm, SVN_CONFIG_SECTION_GENERAL,
3331                  SVN_CONFIG_OPTION_REALM, b->realm);
3332
3333   /* Make sure it's possible for the client to authenticate.  Note
3334      that this doesn't take into account any authz configuration read
3335      above, because we can't know about access it grants until paths
3336      are given by the client. */
3337   if (get_access(b, UNAUTHENTICATED) == NO_ACCESS
3338       && (get_access(b, AUTHENTICATED) == NO_ACCESS
3339           || (!b->tunnel_user && !b->pwdb && !b->use_sasl)))
3340     return error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
3341                                  "No access allowed to this repository",
3342                                  b, conn, pool);
3343
3344   /* Configure hook script environment variables. */
3345   svn_config_get(b->cfg, &hooks_env, SVN_CONFIG_SECTION_GENERAL,
3346                  SVN_CONFIG_OPTION_HOOKS_ENV, NULL);
3347   if (hooks_env)
3348     hooks_env = svn_dirent_internal_style(hooks_env, pool);
3349   SVN_ERR(svn_repos_hooks_setenv(b->repos, hooks_env, pool));
3350
3351   return SVN_NO_ERROR;
3352 }
3353
3354 /* Compute the authentication name EXTERNAL should be able to get, if any. */
3355 static const char *get_tunnel_user(serve_params_t *params, apr_pool_t *pool)
3356 {
3357   /* Only offer EXTERNAL for connections tunneled over a login agent. */
3358   if (!params->tunnel)
3359     return NULL;
3360
3361   /* If a tunnel user was provided on the command line, use that. */
3362   if (params->tunnel_user)
3363     return params->tunnel_user;
3364
3365   return svn_user_get_name(pool);
3366 }
3367
3368 static void
3369 fs_warning_func(void *baton, svn_error_t *err)
3370 {
3371   fs_warning_baton_t *b = baton;
3372   log_server_error(err, b->server, b->conn, b->pool);
3373   /* TODO: Keep log_pool in the server baton, cleared after every log? */
3374   svn_pool_clear(b->pool);
3375 }
3376
3377 /* Return the normalized repository-relative path for the given PATH
3378  * (may be a URL, full path or relative path) and fs contained in the
3379  * server baton BATON. Allocate the result in POOL.
3380  */
3381 static const char *
3382 get_normalized_repo_rel_path(void *baton,
3383                              const char *path,
3384                              apr_pool_t *pool)
3385 {
3386   server_baton_t *sb = baton;
3387
3388   if (svn_path_is_url(path))
3389     {
3390       /* This is a copyfrom URL. */
3391       path = svn_uri_skip_ancestor(sb->repos_url, path, pool);
3392       path = svn_fspath__canonicalize(path, pool);
3393     }
3394   else
3395     {
3396       /* This is a base-relative path. */
3397       if ((path)[0] != '/')
3398         /* Get an absolute path for use in the FS. */
3399         path = svn_fspath__join(sb->fs_path->data, path, pool);
3400     }
3401
3402   return path;
3403 }
3404
3405 /* Get the revision root for REVISION in fs given by server baton BATON
3406  * and return it in *FS_ROOT. Use HEAD if REVISION is SVN_INVALID_REVNUM.
3407  * Use POOL for allocations.
3408  */
3409 static svn_error_t *
3410 get_revision_root(svn_fs_root_t **fs_root,
3411                   void *baton,
3412                   svn_revnum_t revision,
3413                   apr_pool_t *pool)
3414 {
3415   server_baton_t *sb = baton;
3416
3417   if (!SVN_IS_VALID_REVNUM(revision))
3418     SVN_ERR(svn_fs_youngest_rev(&revision, sb->fs, pool));
3419
3420   SVN_ERR(svn_fs_revision_root(fs_root, sb->fs, revision, pool));
3421
3422   return SVN_NO_ERROR;
3423 }
3424
3425 static svn_error_t *
3426 fetch_props_func(apr_hash_t **props,
3427                  void *baton,
3428                  const char *path,
3429                  svn_revnum_t base_revision,
3430                  apr_pool_t *result_pool,
3431                  apr_pool_t *scratch_pool)
3432 {
3433   svn_fs_root_t *fs_root;
3434   svn_error_t *err;
3435
3436   path = get_normalized_repo_rel_path(baton, path, scratch_pool);
3437   SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
3438
3439   err = svn_fs_node_proplist(props, fs_root, path, result_pool);
3440   if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
3441     {
3442       svn_error_clear(err);
3443       *props = apr_hash_make(result_pool);
3444       return SVN_NO_ERROR;
3445     }
3446   else if (err)
3447     return svn_error_trace(err);
3448
3449   return SVN_NO_ERROR;
3450 }
3451
3452 static svn_error_t *
3453 fetch_kind_func(svn_node_kind_t *kind,
3454                 void *baton,
3455                 const char *path,
3456                 svn_revnum_t base_revision,
3457                 apr_pool_t *scratch_pool)
3458 {
3459   svn_fs_root_t *fs_root;
3460
3461   path = get_normalized_repo_rel_path(baton, path, scratch_pool);
3462   SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
3463
3464   SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
3465
3466   return SVN_NO_ERROR;
3467 }
3468
3469 static svn_error_t *
3470 fetch_base_func(const char **filename,
3471                 void *baton,
3472                 const char *path,
3473                 svn_revnum_t base_revision,
3474                 apr_pool_t *result_pool,
3475                 apr_pool_t *scratch_pool)
3476 {
3477   svn_stream_t *contents;
3478   svn_stream_t *file_stream;
3479   const char *tmp_filename;
3480   svn_fs_root_t *fs_root;
3481   svn_error_t *err;
3482
3483   path = get_normalized_repo_rel_path(baton, path, scratch_pool);
3484   SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
3485
3486   err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
3487   if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
3488     {
3489       svn_error_clear(err);
3490       *filename = NULL;
3491       return SVN_NO_ERROR;
3492     }
3493   else if (err)
3494     return svn_error_trace(err);
3495   SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
3496                                  svn_io_file_del_on_pool_cleanup,
3497                                  scratch_pool, scratch_pool));
3498   SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
3499
3500   *filename = apr_pstrdup(result_pool, tmp_filename);
3501
3502   return SVN_NO_ERROR;
3503 }
3504
3505 svn_error_t *serve(svn_ra_svn_conn_t *conn, serve_params_t *params,
3506                    apr_pool_t *pool)
3507 {
3508   svn_error_t *err, *io_err;
3509   apr_uint64_t ver;
3510   const char *uuid, *client_url, *ra_client_string, *client_string;
3511   apr_array_header_t *caplist, *cap_words;
3512   server_baton_t b;
3513   fs_warning_baton_t warn_baton;
3514   svn_stringbuf_t *cap_log = svn_stringbuf_create_empty(pool);
3515
3516   b.tunnel = params->tunnel;
3517   b.tunnel_user = get_tunnel_user(params, pool);
3518   b.read_only = params->read_only;
3519   b.user = NULL;
3520   b.username_case = params->username_case;
3521   b.authz_user = NULL;
3522   b.base = params->base;
3523   b.cfg = params->cfg;
3524   b.pwdb = NULL;
3525   b.authzdb = NULL;
3526   b.realm = NULL;
3527   b.log_file = params->log_file;
3528   b.pool = pool;
3529   b.use_sasl = FALSE;
3530   b.vhost = params->vhost;
3531
3532   /* construct FS configuration parameters */
3533   b.fs_config = apr_hash_make(pool);
3534   svn_hash_sets(b.fs_config, SVN_FS_CONFIG_FSFS_CACHE_DELTAS,
3535                 params->cache_txdeltas ? "1" :"0");
3536   svn_hash_sets(b.fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS,
3537                 params->cache_fulltexts ? "1" :"0");
3538   svn_hash_sets(b.fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS,
3539                 params->cache_revprops ? "1" :"0");
3540
3541   /* Send greeting.  We don't support version 1 any more, so we can
3542    * send an empty mechlist. */
3543   if (params->compression_level > 0)
3544     SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "nn()(wwwwwwwwwww)",
3545                                            (apr_uint64_t) 2, (apr_uint64_t) 2,
3546                                            SVN_RA_SVN_CAP_EDIT_PIPELINE,
3547                                            SVN_RA_SVN_CAP_SVNDIFF1,
3548                                            SVN_RA_SVN_CAP_ABSENT_ENTRIES,
3549                                            SVN_RA_SVN_CAP_COMMIT_REVPROPS,
3550                                            SVN_RA_SVN_CAP_DEPTH,
3551                                            SVN_RA_SVN_CAP_LOG_REVPROPS,
3552                                            SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
3553                                            SVN_RA_SVN_CAP_PARTIAL_REPLAY,
3554                                            SVN_RA_SVN_CAP_INHERITED_PROPS,
3555                                            SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS,
3556                                            SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE
3557                                            ));
3558   else
3559     SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "nn()(wwwwwwwwww)",
3560                                            (apr_uint64_t) 2, (apr_uint64_t) 2,
3561                                            SVN_RA_SVN_CAP_EDIT_PIPELINE,
3562                                            SVN_RA_SVN_CAP_ABSENT_ENTRIES,
3563                                            SVN_RA_SVN_CAP_COMMIT_REVPROPS,
3564                                            SVN_RA_SVN_CAP_DEPTH,
3565                                            SVN_RA_SVN_CAP_LOG_REVPROPS,
3566                                            SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
3567                                            SVN_RA_SVN_CAP_PARTIAL_REPLAY,
3568                                            SVN_RA_SVN_CAP_INHERITED_PROPS,
3569                                            SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS,
3570                                            SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE
3571                                            ));
3572
3573   /* Read client response, which we assume to be in version 2 format:
3574    * version, capability list, and client URL; then we do an auth
3575    * request. */
3576   SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "nlc?c(?c)",
3577                                  &ver, &caplist, &client_url,
3578                                  &ra_client_string,
3579                                  &client_string));
3580   if (ver != 2)
3581     return SVN_NO_ERROR;
3582
3583   client_url = svn_uri_canonicalize(client_url, pool);
3584   SVN_ERR(svn_ra_svn_set_capabilities(conn, caplist));
3585
3586   /* All released versions of Subversion support edit-pipeline,
3587    * so we do not accept connections from clients that do not. */
3588   if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE))
3589     return SVN_NO_ERROR;
3590
3591   /* find_repos needs the capabilities as a list of words (eventually
3592      they get handed to the start-commit hook).  While we could add a
3593      new interface to re-retrieve them from conn and convert the
3594      result to a list, it's simpler to just convert caplist by hand
3595      here, since we already have it and turning 'svn_ra_svn_item_t's
3596      into 'const char *'s is pretty easy.
3597
3598      We only record capabilities we care about.  The client may report
3599      more (because it doesn't know what the server cares about). */
3600   {
3601     int i;
3602     svn_ra_svn_item_t *item;
3603
3604     cap_words = apr_array_make(pool, 1, sizeof(const char *));
3605     for (i = 0; i < caplist->nelts; i++)
3606       {
3607         item = &APR_ARRAY_IDX(caplist, i, svn_ra_svn_item_t);
3608         /* ra_svn_set_capabilities() already type-checked for us */
3609         if (strcmp(item->u.word, SVN_RA_SVN_CAP_MERGEINFO) == 0)
3610           {
3611             APR_ARRAY_PUSH(cap_words, const char *)
3612               = SVN_RA_CAPABILITY_MERGEINFO;
3613           }
3614         /* Save for operational log. */
3615         if (cap_log->len > 0)
3616           svn_stringbuf_appendcstr(cap_log, " ");
3617         svn_stringbuf_appendcstr(cap_log, item->u.word);
3618       }
3619   }
3620
3621   err = find_repos(client_url, params->root, &b, conn, cap_words, pool);
3622   if (!err)
3623     {
3624       SVN_ERR(auth_request(conn, pool, &b, READ_ACCESS, FALSE));
3625       if (current_access(&b) == NO_ACCESS)
3626         err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
3627                                    "Not authorized for access",
3628                                    &b, conn, pool);
3629     }
3630   if (err)
3631     {
3632       log_error(err, b.log_file, svn_ra_svn_conn_remote_host(conn),
3633                 b.user, NULL, pool);
3634       io_err = svn_ra_svn__write_cmd_failure(conn, pool, err);
3635       svn_error_clear(err);
3636       SVN_ERR(io_err);
3637       return svn_ra_svn__flush(conn, pool);
3638     }
3639
3640   /* Log the open. */
3641   if (ra_client_string == NULL || ra_client_string[0] == '\0')
3642     ra_client_string = "-";
3643   else
3644     ra_client_string = svn_path_uri_encode(ra_client_string, pool);
3645   if (client_string == NULL || client_string[0] == '\0')
3646     client_string = "-";
3647   else
3648     client_string = svn_path_uri_encode(client_string, pool);
3649   SVN_ERR(log_command(&b, conn, pool,
3650                       "open %" APR_UINT64_T_FMT " cap=(%s) %s %s %s",
3651                       ver, cap_log->data,
3652                       svn_path_uri_encode(b.fs_path->data, pool),
3653                       ra_client_string, client_string));
3654
3655   warn_baton.server = &b;
3656   warn_baton.conn = conn;
3657   warn_baton.pool = svn_pool_create(pool);
3658   svn_fs_set_warning_func(b.fs, fs_warning_func, &warn_baton);
3659
3660   SVN_ERR(svn_fs_get_uuid(b.fs, &uuid, pool));
3661
3662   /* We can't claim mergeinfo capability until we know whether the
3663      repository supports mergeinfo (i.e., is not a 1.4 repository),
3664      but we don't get the repository url from the client until after
3665      we've already sent the initial list of server capabilities.  So
3666      we list repository capabilities here, in our first response after
3667      the client has sent the url. */
3668   {
3669     svn_boolean_t supports_mergeinfo;
3670     SVN_ERR(svn_repos_has_capability(b.repos, &supports_mergeinfo,
3671                                      SVN_REPOS_CAPABILITY_MERGEINFO, pool));
3672
3673     SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(cc(!",
3674                                     "success", uuid, b.repos_url));
3675     if (supports_mergeinfo)
3676       SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_CAP_MERGEINFO));
3677     SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
3678   }
3679
3680   /* Set up editor shims. */
3681   {
3682     svn_delta_shim_callbacks_t *callbacks =
3683                                 svn_delta_shim_callbacks_default(pool);
3684
3685     callbacks->fetch_base_func = fetch_base_func;
3686     callbacks->fetch_props_func = fetch_props_func;
3687     callbacks->fetch_kind_func = fetch_kind_func;
3688     callbacks->fetch_baton = &b;
3689
3690     SVN_ERR(svn_ra_svn__set_shim_callbacks(conn, callbacks));
3691   }
3692
3693   return svn_ra_svn__handle_commands2(conn, pool, main_commands, &b, FALSE);
3694 }