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