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