Follow-up to r29231, making sure that every svn_ra_neon__request_t is
[svn.git] / subversion / libsvn_ra_neon / session.c
blob39cb392c4918d6b8d4f6d8dddba9674325215db7
1 /*
2 * session.c : routines for maintaining sessions state (to the DAV server)
4 * ====================================================================
5 * Copyright (c) 2000-2006 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
21 #include <assert.h>
22 #include <ctype.h>
24 #define APR_WANT_STRFUNC
25 #include <apr_want.h>
26 #include <apr_general.h>
27 #include <apr_xml.h>
29 #include <ne_auth.h>
31 #include "svn_error.h"
32 #include "svn_pools.h"
33 #include "svn_ra.h"
34 #include "../libsvn_ra/ra_loader.h"
35 #include "svn_config.h"
36 #include "svn_delta.h"
37 #include "svn_version.h"
38 #include "svn_path.h"
39 #include "svn_time.h"
40 #include "svn_xml.h"
41 #include "svn_private_config.h"
43 #include "ra_neon.h"
45 #define DEFAULT_HTTP_TIMEOUT 3600
48 /* a cleanup routine attached to the pool that contains the RA session
49 baton. */
50 static apr_status_t cleanup_session(void *sess)
52 ne_session_destroy(sess);
53 return APR_SUCCESS;
56 /* a cleanup routine attached to the pool that contains the RA session
57 root URI. */
58 static apr_status_t cleanup_uri(void *uri)
60 ne_uri_free(uri);
61 return APR_SUCCESS;
64 /* A neon-session callback to 'pull' authentication data when
65 challenged. In turn, this routine 'pulls' the data from the client
66 callbacks if needed. */
67 static int request_auth(void *userdata, const char *realm, int attempt,
68 char *username, char *password)
70 svn_error_t *err;
71 svn_ra_neon__session_t *ras = userdata;
72 void *creds;
73 svn_auth_cred_simple_t *simple_creds;
75 /* Start by clearing the cache of any previously-fetched username. */
76 ras->auth_username = NULL;
78 /* No auth_baton? Give up. */
79 if (! ras->callbacks->auth_baton)
80 return -1;
82 /* Neon automatically tries some auth protocols and bumps the attempt
83 count without using Subversion's callbacks, so we can't depend
84 on attempt == 0 the first time we are called -- we need to check
85 if the auth state has been initted as well. */
86 if (attempt == 0 || ras->auth_iterstate == NULL)
88 const char *realmstring;
90 /* <https://svn.collab.net:80> Subversion repository */
91 realmstring = apr_psprintf(ras->pool, "<%s://%s:%d> %s",
92 ras->root.scheme, ras->root.host,
93 ras->root.port, realm);
95 err = svn_auth_first_credentials(&creds,
96 &(ras->auth_iterstate),
97 SVN_AUTH_CRED_SIMPLE,
98 realmstring,
99 ras->callbacks->auth_baton,
100 ras->pool);
103 else /* attempt > 0 */
104 /* ### TODO: if the http realm changed this time around, we
105 should be calling first_creds(), not next_creds(). */
106 err = svn_auth_next_credentials(&creds,
107 ras->auth_iterstate,
108 ras->pool);
109 if (err || ! creds)
111 svn_error_clear(err);
112 return -1;
114 simple_creds = creds;
116 /* ### silently truncates username/password to 256 chars. */
117 apr_cpystrn(username, simple_creds->username, NE_ABUFSIZ);
118 apr_cpystrn(password, simple_creds->password, NE_ABUFSIZ);
120 /* Cache the fetched username in ra_session. */
121 ras->auth_username = apr_pstrdup(ras->pool, simple_creds->username);
123 return 0;
127 static const apr_uint32_t neon_failure_map[][2] =
129 { NE_SSL_NOTYETVALID, SVN_AUTH_SSL_NOTYETVALID },
130 { NE_SSL_EXPIRED, SVN_AUTH_SSL_EXPIRED },
131 { NE_SSL_IDMISMATCH, SVN_AUTH_SSL_CNMISMATCH },
132 { NE_SSL_UNTRUSTED, SVN_AUTH_SSL_UNKNOWNCA }
135 /* Convert neon's SSL failure mask to our own failure mask. */
136 static apr_uint32_t
137 convert_neon_failures(int neon_failures)
139 apr_uint32_t svn_failures = 0;
140 apr_size_t i;
142 for (i = 0; i < sizeof(neon_failure_map) / (2 * sizeof(int)); ++i)
144 if (neon_failures & neon_failure_map[i][0])
146 svn_failures |= neon_failure_map[i][1];
147 neon_failures &= ~neon_failure_map[i][0];
151 /* Map any remaining neon failure bits to our OTHER bit. */
152 if (neon_failures)
154 svn_failures |= SVN_AUTH_SSL_OTHER;
157 return svn_failures;
160 /* A neon-session callback to validate the SSL certificate when the CA
161 is unknown (e.g. a self-signed cert), or there are other SSL
162 certificate problems. */
163 static int
164 server_ssl_callback(void *userdata,
165 int failures,
166 const ne_ssl_certificate *cert)
168 svn_ra_neon__session_t *ras = userdata;
169 svn_auth_cred_ssl_server_trust_t *server_creds = NULL;
170 void *creds;
171 svn_auth_iterstate_t *state;
172 apr_pool_t *pool;
173 svn_error_t *error;
174 char *ascii_cert = ne_ssl_cert_export(cert);
175 char *issuer_dname = ne_ssl_readable_dname(ne_ssl_cert_issuer(cert));
176 svn_auth_ssl_server_cert_info_t cert_info;
177 char fingerprint[NE_SSL_DIGESTLEN];
178 char valid_from[NE_SSL_VDATELEN], valid_until[NE_SSL_VDATELEN];
179 const char *realmstring;
180 apr_uint32_t *svn_failures = apr_palloc(ras->pool, sizeof(*svn_failures));
182 /* Construct the realmstring, e.g. https://svn.collab.net:80 */
183 realmstring = apr_psprintf(ras->pool, "%s://%s:%d", ras->root.scheme,
184 ras->root.host, ras->root.port);
186 *svn_failures = convert_neon_failures(failures);
187 svn_auth_set_parameter(ras->callbacks->auth_baton,
188 SVN_AUTH_PARAM_SSL_SERVER_FAILURES,
189 svn_failures);
191 /* Extract the info from the certificate */
192 cert_info.hostname = ne_ssl_cert_identity(cert);
193 if (ne_ssl_cert_digest(cert, fingerprint) != 0)
195 strcpy(fingerprint, "<unknown>");
197 cert_info.fingerprint = fingerprint;
198 ne_ssl_cert_validity(cert, valid_from, valid_until);
199 cert_info.valid_from = valid_from;
200 cert_info.valid_until = valid_until;
201 cert_info.issuer_dname = issuer_dname;
202 cert_info.ascii_cert = ascii_cert;
204 svn_auth_set_parameter(ras->callbacks->auth_baton,
205 SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO,
206 &cert_info);
208 apr_pool_create(&pool, ras->pool);
209 error = svn_auth_first_credentials(&creds, &state,
210 SVN_AUTH_CRED_SSL_SERVER_TRUST,
211 realmstring,
212 ras->callbacks->auth_baton,
213 pool);
214 if (error || ! creds)
216 svn_error_clear(error);
218 else
220 server_creds = creds;
221 error = svn_auth_save_credentials(state, pool);
222 if (error)
224 /* It would be nice to show the error to the user somehow... */
225 svn_error_clear(error);
229 free(issuer_dname);
230 free(ascii_cert);
231 svn_auth_set_parameter(ras->callbacks->auth_baton,
232 SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL);
234 svn_pool_destroy(pool);
235 return ! server_creds;
238 static svn_boolean_t
239 client_ssl_decrypt_cert(svn_ra_neon__session_t *ras,
240 const char *cert_file,
241 ne_ssl_client_cert *clicert)
243 svn_auth_iterstate_t *state;
244 svn_error_t *error;
245 apr_pool_t *pool;
246 svn_boolean_t ok = FALSE;
247 void *creds;
248 int try;
250 apr_pool_create(&pool, ras->pool);
251 for (try = 0; TRUE; ++try)
253 if (try == 0)
255 error = svn_auth_first_credentials(&creds, &state,
256 SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
257 cert_file,
258 ras->callbacks->auth_baton,
259 pool);
261 else
263 error = svn_auth_next_credentials(&creds, state, pool);
266 if (error || ! creds)
268 /* Failure or too many attempts */
269 svn_error_clear(error);
270 break;
272 else
274 svn_auth_cred_ssl_client_cert_pw_t *pw_creds = creds;
276 if (ne_ssl_clicert_decrypt(clicert, pw_creds->password) == 0)
278 /* Success */
279 ok = TRUE;
280 break;
284 svn_pool_destroy(pool);
286 return ok;
290 static void
291 client_ssl_callback(void *userdata, ne_session *sess,
292 const ne_ssl_dname *const *dnames,
293 int dncount)
295 svn_ra_neon__session_t *ras = userdata;
296 ne_ssl_client_cert *clicert = NULL;
297 void *creds;
298 svn_auth_iterstate_t *state;
299 const char *realmstring;
300 apr_pool_t *pool;
301 svn_error_t *error;
302 int try;
304 apr_pool_create(&pool, ras->pool);
306 realmstring = apr_psprintf(pool, "%s://%s:%d", ras->root.scheme,
307 ras->root.host, ras->root.port);
309 for (try = 0; TRUE; ++try)
311 if (try == 0)
313 error = svn_auth_first_credentials(&creds, &state,
314 SVN_AUTH_CRED_SSL_CLIENT_CERT,
315 realmstring,
316 ras->callbacks->auth_baton,
317 pool);
319 else
321 error = svn_auth_next_credentials(&creds, state, pool);
324 if (error || ! creds)
326 /* Failure or too many attempts */
327 svn_error_clear(error);
328 break;
330 else
332 svn_auth_cred_ssl_client_cert_t *client_creds = creds;
334 clicert = ne_ssl_clicert_read(client_creds->cert_file);
335 if (clicert)
337 if (! ne_ssl_clicert_encrypted(clicert) ||
338 client_ssl_decrypt_cert(ras, client_creds->cert_file,
339 clicert))
341 ne_ssl_set_clicert(sess, clicert);
343 break;
348 svn_pool_destroy(pool);
351 /* Set *PROXY_HOST, *PROXY_PORT, *PROXY_USERNAME, *PROXY_PASSWORD,
352 * *TIMEOUT_SECONDS, *NEON_DEBUG, *COMPRESSION, and *NEON_AUTO_PROTOCOLS
353 * to the information for REQUESTED_HOST, allocated in POOL, if there is
354 * any applicable information. If there is no applicable information or
355 * if there is an error, then set *PROXY_PORT to (unsigned int) -1,
356 * *TIMEOUT_SECONDS and *NEON_DEBUG to zero, *COMPRESSION to TRUE,
357 * *NEON_AUTH_TYPES is left untouched, and the rest are set to NULL.
358 * This function can return an error, so before examining any values,
359 * check the error return value.
361 static svn_error_t *get_server_settings(const char **proxy_host,
362 unsigned int *proxy_port,
363 const char **proxy_username,
364 const char **proxy_password,
365 int *timeout_seconds,
366 int *neon_debug,
367 svn_boolean_t *compression,
368 unsigned int *neon_auth_types,
369 svn_config_t *cfg,
370 const char *requested_host,
371 apr_pool_t *pool)
373 const char *exceptions, *port_str, *timeout_str, *server_group;
374 const char *debug_str, *http_auth_types;
375 svn_boolean_t is_exception = FALSE;
376 /* If we find nothing, default to nulls. */
377 *proxy_host = NULL;
378 *proxy_port = (unsigned int) -1;
379 *proxy_username = NULL;
380 *proxy_password = NULL;
381 port_str = NULL;
382 timeout_str = NULL;
383 debug_str = NULL;
384 http_auth_types = NULL;
386 /* If there are defaults, use them, but only if the requested host
387 is not one of the exceptions to the defaults. */
388 svn_config_get(cfg, &exceptions, SVN_CONFIG_SECTION_GLOBAL,
389 SVN_CONFIG_OPTION_HTTP_PROXY_EXCEPTIONS, NULL);
390 if (exceptions)
392 apr_array_header_t *l = svn_cstring_split(exceptions, ",", TRUE, pool);
393 is_exception = svn_cstring_match_glob_list(requested_host, l);
395 if (! is_exception)
397 svn_config_get(cfg, proxy_host, SVN_CONFIG_SECTION_GLOBAL,
398 SVN_CONFIG_OPTION_HTTP_PROXY_HOST, NULL);
399 svn_config_get(cfg, &port_str, SVN_CONFIG_SECTION_GLOBAL,
400 SVN_CONFIG_OPTION_HTTP_PROXY_PORT, NULL);
401 svn_config_get(cfg, proxy_username, SVN_CONFIG_SECTION_GLOBAL,
402 SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME, NULL);
403 svn_config_get(cfg, proxy_password, SVN_CONFIG_SECTION_GLOBAL,
404 SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD, NULL);
405 svn_config_get(cfg, &timeout_str, SVN_CONFIG_SECTION_GLOBAL,
406 SVN_CONFIG_OPTION_HTTP_TIMEOUT, NULL);
407 SVN_ERR(svn_config_get_bool(cfg, compression, SVN_CONFIG_SECTION_GLOBAL,
408 SVN_CONFIG_OPTION_HTTP_COMPRESSION, TRUE));
409 svn_config_get(cfg, &debug_str, SVN_CONFIG_SECTION_GLOBAL,
410 SVN_CONFIG_OPTION_NEON_DEBUG_MASK, NULL);
411 #ifdef SVN_NEON_0_26
412 svn_config_get(cfg, &http_auth_types, SVN_CONFIG_SECTION_GLOBAL,
413 SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, NULL);
414 #endif
417 if (cfg)
418 server_group = svn_config_find_group(cfg, requested_host,
419 SVN_CONFIG_SECTION_GROUPS, pool);
420 else
421 server_group = NULL;
423 if (server_group)
425 svn_config_get(cfg, proxy_host, server_group,
426 SVN_CONFIG_OPTION_HTTP_PROXY_HOST, *proxy_host);
427 svn_config_get(cfg, &port_str, server_group,
428 SVN_CONFIG_OPTION_HTTP_PROXY_PORT, port_str);
429 svn_config_get(cfg, proxy_username, server_group,
430 SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME, *proxy_username);
431 svn_config_get(cfg, proxy_password, server_group,
432 SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD, *proxy_password);
433 svn_config_get(cfg, &timeout_str, server_group,
434 SVN_CONFIG_OPTION_HTTP_TIMEOUT, timeout_str);
435 SVN_ERR(svn_config_get_bool(cfg, compression, server_group,
436 SVN_CONFIG_OPTION_HTTP_COMPRESSION,
437 *compression));
438 svn_config_get(cfg, &debug_str, server_group,
439 SVN_CONFIG_OPTION_NEON_DEBUG_MASK, debug_str);
440 #ifdef SVN_NEON_0_26
441 svn_config_get(cfg, &http_auth_types, SVN_CONFIG_SECTION_GLOBAL,
442 SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, NULL);
443 #endif
446 /* Special case: convert the port value, if any. */
447 if (port_str)
449 char *endstr;
450 const long int port = strtol(port_str, &endstr, 10);
452 if (*endstr)
453 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
454 _("Invalid URL: illegal character in proxy "
455 "port number"));
456 if (port < 0)
457 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
458 _("Invalid URL: negative proxy port number"));
459 if (port > 65535)
460 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
461 _("Invalid URL: proxy port number greater "
462 "than maximum TCP port number 65535"));
463 *proxy_port = port;
465 else
466 *proxy_port = 80;
468 if (timeout_str)
470 char *endstr;
471 const long int timeout = strtol(timeout_str, &endstr, 10);
473 if (*endstr)
474 return svn_error_create(SVN_ERR_RA_DAV_INVALID_CONFIG_VALUE, NULL,
475 _("Invalid config: illegal character in "
476 "timeout value"));
477 if (timeout < 0)
478 return svn_error_create(SVN_ERR_RA_DAV_INVALID_CONFIG_VALUE, NULL,
479 _("Invalid config: negative timeout value"));
480 *timeout_seconds = timeout;
482 else
483 *timeout_seconds = 0;
485 if (debug_str)
487 char *endstr;
488 const long int debug = strtol(debug_str, &endstr, 10);
490 if (*endstr)
491 return svn_error_create(SVN_ERR_RA_DAV_INVALID_CONFIG_VALUE, NULL,
492 _("Invalid config: illegal character in "
493 "debug mask value"));
495 *neon_debug = debug;
497 else
498 *neon_debug = 0;
500 #ifdef SVN_NEON_0_26
501 if (http_auth_types)
503 char *token, *last;
504 char *auth_types_list = apr_palloc(pool, strlen(http_auth_types) + 1);
505 apr_collapse_spaces(auth_types_list, http_auth_types);
506 while ((token = apr_strtok(auth_types_list, ";", &last)) != NULL)
508 auth_types_list = NULL;
509 if (svn_cstring_casecmp("basic", token) == 0)
510 *neon_auth_types |= NE_AUTH_BASIC;
511 else if (svn_cstring_casecmp("digest", token) == 0)
512 *neon_auth_types |= NE_AUTH_DIGEST;
513 else if (svn_cstring_casecmp("negotiate", token) == 0)
514 *neon_auth_types |= NE_AUTH_NEGOTIATE;
515 else
516 return svn_error_createf(SVN_ERR_RA_DAV_INVALID_CONFIG_VALUE, NULL,
517 _("Invalid config: unknown http auth"
518 "type '%s'"), token);
521 #endif /* SVN_NEON_0_26 */
523 return SVN_NO_ERROR;
527 /* Userdata for the `proxy_auth' function. */
528 struct proxy_auth_baton
530 const char *username; /* Cannot be NULL, but "" is okay. */
531 const char *password; /* Cannot be NULL, but "" is okay. */
535 /* An `ne_request_auth' callback, see ne_auth.h. USERDATA is a
536 * `struct proxy_auth_baton *'.
538 * If ATTEMPT < 10, copy USERDATA->username and USERDATA->password
539 * into USERNAME and PASSWORD respectively (but do not copy more than
540 * NE_ABUFSIZ bytes of either), and return zero to indicate to Neon
541 * that authentication should be attempted.
543 * If ATTEMPT >= 10, copy nothing into USERNAME and PASSWORD and
544 * return 1, to cancel further authentication attempts.
546 * Ignore REALM.
548 * ### Note: There is no particularly good reason for the 10-attempt
549 * limit. Perhaps there should only be one attempt, and if it fails,
550 * we just cancel any further attempts. I used 10 just in case the
551 * proxy tries various times with various realms, since we ignore
552 * REALM. And why do we ignore REALM? Because we currently don't
553 * have any way to specify different auth information for different
554 * realms. (I'm assuming that REALM would be a realm on the proxy
555 * server, not on the Subversion repository server that is the real
556 * destination.) Do we have any need to support proxy realms?
558 static int proxy_auth(void *userdata,
559 const char *realm,
560 int attempt,
561 char *username,
562 char *password)
564 struct proxy_auth_baton *pab = userdata;
566 if (attempt >= 10)
567 return 1;
569 /* Else. */
571 apr_cpystrn(username, pab->username, NE_ABUFSIZ);
572 apr_cpystrn(password, pab->password, NE_ABUFSIZ);
574 return 0;
577 #define RA_NEON_DESCRIPTION \
578 N_("Module for accessing a repository via WebDAV protocol using Neon.")
580 static const char *
581 ra_neon_get_description(void)
583 return _(RA_NEON_DESCRIPTION);
586 static const char * const *
587 ra_neon_get_schemes(apr_pool_t *pool)
589 static const char *schemes_no_ssl[] = { "http", NULL };
590 static const char *schemes_ssl[] = { "http", "https", NULL };
592 return ne_has_support(NE_FEATURE_SSL) ? schemes_ssl : schemes_no_ssl;
595 typedef struct neonprogress_baton_t
597 svn_ra_progress_notify_func_t progress_func;
598 void *progress_baton;
599 apr_pool_t *pool;
600 } neonprogress_baton_t;
602 static void
603 #ifdef SVN_NEON_0_27
604 ra_neon_neonprogress(void *baton, ne_off_t progress, ne_off_t total)
605 #else
606 ra_neon_neonprogress(void *baton, off_t progress, off_t total)
607 #endif /* SVN_NEON_0_27 */
609 const neonprogress_baton_t *neonprogress_baton = baton;
610 if (neonprogress_baton->progress_func)
612 neonprogress_baton->progress_func(progress, total,
613 neonprogress_baton->progress_baton,
614 neonprogress_baton->pool);
620 /** Capabilities exchange. */
622 /* The only two possible values for a capability. */
623 static const char *capability_yes = "yes";
624 static const char *capability_no = "no";
627 /* Store in RAS the capabilities discovered from REQ's headers.
628 Use POOL for temporary allocation only. */
629 static void
630 parse_capabilities(ne_request *req,
631 svn_ra_neon__session_t *ras,
632 apr_pool_t *pool)
634 void *ne_header_cursor = NULL;
636 /* Start out assuming all capabilities are unsupported. */
637 apr_hash_set(ras->capabilities, SVN_RA_CAPABILITY_DEPTH,
638 APR_HASH_KEY_STRING, capability_no);
639 apr_hash_set(ras->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
640 APR_HASH_KEY_STRING, capability_no);
641 apr_hash_set(ras->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS,
642 APR_HASH_KEY_STRING, capability_no);
644 /* Then find out which ones are supported. */
645 do {
646 const char *header_name;
647 const char *header_value;
648 ne_header_cursor = ne_response_header_iterate(req,
649 ne_header_cursor,
650 &header_name,
651 &header_value);
652 if (ne_header_cursor && svn_cstring_casecmp(header_name, "dav") == 0)
654 /* By the time we get the headers, Neon has downcased them and
655 merged them together -- merged in the sense that if a
656 header "foo" appears multiple times, all the values will be
657 concatenated together, with spaces at the splice points.
658 For example, if the server sent:
660 DAV: version-control,checkout,working-resource
661 DAV: merge,baseline,activity,version-controlled-collection
662 DAV: http://subversion.tigris.org/xmlns/dav/svn/depth
664 Here we might see:
666 header_name == "dav"
667 header_value == "1,2, version-control,checkout,working-resource, merge,baseline,activity,version-controlled-collection, http://subversion.tigris.org/xmlns/dav/svn/depth, <http://apache.org/dav/propset/fs/1>"
669 (Deliberately not line-wrapping that, so you can see what
670 we're about to parse.)
673 apr_array_header_t *vals =
674 svn_cstring_split(header_value, ",", TRUE, pool);
676 /* Right now we only have a few capabilities to detect, so
677 just seek for them directly. This could be written
678 slightly more efficiently, but that wouldn't be worth it
679 until we have many more capabilities. */
681 if (svn_cstring_match_glob_list(SVN_DAV_NS_DAV_SVN_DEPTH, vals))
682 apr_hash_set(ras->capabilities, SVN_RA_CAPABILITY_DEPTH,
683 APR_HASH_KEY_STRING, capability_yes);
685 if (svn_cstring_match_glob_list(SVN_DAV_NS_DAV_SVN_MERGEINFO, vals))
686 apr_hash_set(ras->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
687 APR_HASH_KEY_STRING, capability_yes);
689 if (svn_cstring_match_glob_list(SVN_DAV_NS_DAV_SVN_LOG_REVPROPS, vals))
690 apr_hash_set(ras->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS,
691 APR_HASH_KEY_STRING, capability_yes);
693 if (svn_cstring_match_glob_list(SVN_DAV_NS_DAV_SVN_PARTIAL_REPLAY,
694 vals))
695 apr_hash_set(ras->capabilities, SVN_RA_CAPABILITY_PARTIAL_REPLAY,
696 APR_HASH_KEY_STRING, capability_yes);
698 } while (ne_header_cursor);
702 /* Exchange capabilities with the server, by sending an OPTIONS
703 request announcing the client's capabilities, and by filling
704 RAS->capabilities with the server's capabilities as read from the
705 response headers. Use POOL only for temporary allocation. */
706 static svn_error_t *
707 exchange_capabilities(svn_ra_neon__session_t *ras, apr_pool_t *pool)
709 int http_ret_code;
710 svn_ra_neon__request_t *rar;
711 svn_error_t *err = SVN_NO_ERROR;
713 rar = svn_ra_neon__request_create(ras, "OPTIONS", ras->url->data, pool);
715 ne_add_request_header(rar->ne_req, "DAV", SVN_DAV_NS_DAV_SVN_DEPTH);
716 ne_add_request_header(rar->ne_req, "DAV", SVN_DAV_NS_DAV_SVN_MERGEINFO);
717 ne_add_request_header(rar->ne_req, "DAV", SVN_DAV_NS_DAV_SVN_LOG_REVPROPS);
719 err = svn_ra_neon__request_dispatch(&http_ret_code, rar,
720 NULL, NULL, 200, 0, pool);
721 if (err)
722 goto cleanup;
724 if (http_ret_code == 200)
726 parse_capabilities(rar->ne_req, ras, pool);
728 else
730 /* "can't happen", because svn_ra_neon__request_dispatch()
731 itself should have returned error if response code != 200. */
732 return svn_error_createf
733 (SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
734 _("OPTIONS request (for capabilities) got HTTP response code %d"),
735 http_ret_code);
738 cleanup:
739 svn_ra_neon__request_destroy(rar);
741 return err;
745 svn_error_t *
746 svn_ra_neon__has_capability(svn_ra_session_t *session,
747 svn_boolean_t *has,
748 const char *capability,
749 apr_pool_t *pool)
751 svn_ra_neon__session_t *ras = session->priv;
752 const char *cap_result = apr_hash_get(ras->capabilities,
753 capability,
754 APR_HASH_KEY_STRING);
756 /* If any capability is unknown, they're all unknown, so ask. */
757 if (cap_result == NULL)
758 SVN_ERR(exchange_capabilities(ras, pool));
761 /* Try again, now that we've fetched the capabilities. */
762 cap_result = apr_hash_get(ras->capabilities,
763 capability, APR_HASH_KEY_STRING);
765 if (cap_result == capability_yes)
767 *has = TRUE;
769 else if (cap_result == capability_no)
771 *has = FALSE;
773 else if (cap_result == NULL)
775 return svn_error_createf
776 (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
777 _("Don't know anything about capability '%s'"), capability);
779 else /* "can't happen" */
781 /* Well, let's hope it's a string. */
782 return svn_error_createf
783 (SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
784 _("Attempt to fetch capability '%s' resulted in '%s'"),
785 capability, cap_result);
788 return SVN_NO_ERROR;
793 /* ### need an ne_session_dup to avoid the second gethostbyname
794 * call and make this halfway sane. */
797 /* Parse URL into *URI, doing some sanity checking and initializing the port
798 to a default value if it wasn't specified in URL. */
799 static svn_error_t *
800 parse_url(ne_uri *uri, const char *url)
802 if (ne_uri_parse(url, uri)
803 || uri->host == NULL || uri->path == NULL || uri->scheme == NULL)
805 ne_uri_free(uri);
806 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
807 _("Malformed URL for repository"));
809 if (uri->port == 0)
810 uri->port = ne_uri_defaultport(uri->scheme);
812 return SVN_NO_ERROR;
815 static svn_error_t *
816 svn_ra_neon__open(svn_ra_session_t *session,
817 const char *repos_URL,
818 const svn_ra_callbacks2_t *callbacks,
819 void *callback_baton,
820 apr_hash_t *config,
821 apr_pool_t *pool)
823 apr_size_t len;
824 ne_session *sess, *sess2;
825 ne_uri *uri = apr_pcalloc(pool, sizeof(*uri));
826 svn_ra_neon__session_t *ras;
827 int is_ssl_session;
828 svn_boolean_t compression;
829 svn_config_t *cfg;
830 const char *server_group;
831 char *itr;
832 unsigned int neon_auth_types = 0;
833 neonprogress_baton_t *neonprogress_baton =
834 apr_pcalloc(pool, sizeof(*neonprogress_baton));
835 const char *useragent = NULL;
836 const char *client_string = NULL;
838 if (callbacks->get_client_string)
839 callbacks->get_client_string(callback_baton, &client_string, pool);
841 if (client_string)
842 useragent = apr_pstrcat(pool, "SVN/" SVN_VERSION "/", client_string, NULL);
843 else
844 useragent = "SVN/" SVN_VERSION;
846 /* Sanity check the URI */
847 SVN_ERR(parse_url(uri, repos_URL));
849 /* make sure we eventually destroy the uri */
850 apr_pool_cleanup_register(pool, uri, cleanup_uri, apr_pool_cleanup_null);
853 /* Can we initialize network? */
854 if (ne_sock_init() != 0)
855 return svn_error_create(SVN_ERR_RA_DAV_SOCK_INIT, NULL,
856 _("Network socket initialization failed"));
858 /* we want to know if the repository is actually somewhere else */
859 /* ### not yet: http_redirect_register(sess, ... ); */
861 /* HACK! Neon uses strcmp when checking for https, but RFC 2396 says
862 * we should be using case-insensitive comparisons when checking for
863 * URI schemes. To allow our users to use WeIrd CasE HttPS we force
864 * the scheme to lower case before we pass it on to Neon, otherwise we
865 * would crash later on when we assume Neon has set up its https stuff
866 * but it really didn't. */
867 for (itr = uri->scheme; *itr; ++itr)
868 *itr = tolower(*itr);
870 is_ssl_session = (svn_cstring_casecmp(uri->scheme, "https") == 0);
871 if (is_ssl_session)
873 if (ne_has_support(NE_FEATURE_SSL) == 0)
874 return svn_error_create(SVN_ERR_RA_DAV_SOCK_INIT, NULL,
875 _("SSL is not supported"));
877 /* Create two neon session objects, and set their properties... */
878 sess = ne_session_create(uri->scheme, uri->host, uri->port);
879 sess2 = ne_session_create(uri->scheme, uri->host, uri->port);
880 /* make sure we will eventually destroy the session */
881 apr_pool_cleanup_register(pool, sess, cleanup_session, apr_pool_cleanup_null);
882 apr_pool_cleanup_register(pool, sess2, cleanup_session,
883 apr_pool_cleanup_null);
885 cfg = config ? apr_hash_get(config,
886 SVN_CONFIG_CATEGORY_SERVERS,
887 APR_HASH_KEY_STRING) : NULL;
888 if (cfg)
889 server_group = svn_config_find_group(cfg, uri->host,
890 SVN_CONFIG_SECTION_GROUPS, pool);
891 else
892 server_group = NULL;
894 /* If there's a timeout or proxy for this URL, use it. */
896 const char *proxy_host;
897 unsigned int proxy_port;
898 const char *proxy_username;
899 const char *proxy_password;
900 int timeout;
901 int debug;
903 SVN_ERR(get_server_settings(&proxy_host,
904 &proxy_port,
905 &proxy_username,
906 &proxy_password,
907 &timeout,
908 &debug,
909 &compression,
910 &neon_auth_types,
911 cfg,
912 uri->host,
913 pool));
915 #ifdef SVN_NEON_0_26
916 if (neon_auth_types == 0)
918 /* If there were no auth types specified in the configuration
919 file, provide the appropriate defaults. */
920 neon_auth_types = NE_AUTH_BASIC | NE_AUTH_DIGEST;
921 if (is_ssl_session)
922 neon_auth_types |= NE_AUTH_NEGOTIATE;
924 #endif
926 if (debug)
927 ne_debug_init(stderr, debug);
929 if (proxy_host)
931 ne_session_proxy(sess, proxy_host, proxy_port);
932 ne_session_proxy(sess2, proxy_host, proxy_port);
934 if (proxy_username)
936 /* Allocate the baton in pool, not on stack, so it will
937 last till whenever Neon needs it. */
938 struct proxy_auth_baton *pab = apr_palloc(pool, sizeof(*pab));
940 pab->username = proxy_username;
941 pab->password = proxy_password ? proxy_password : "";
943 ne_set_proxy_auth(sess, proxy_auth, pab);
944 ne_set_proxy_auth(sess2, proxy_auth, pab);
948 if (!timeout)
949 timeout = DEFAULT_HTTP_TIMEOUT;
950 ne_set_read_timeout(sess, timeout);
951 ne_set_read_timeout(sess2, timeout);
954 if (useragent)
956 ne_set_useragent(sess, useragent);
957 ne_set_useragent(sess2, useragent);
959 else
961 ne_set_useragent(sess, "SVN/" SVN_VERSION);
962 ne_set_useragent(sess2, "SVN/" SVN_VERSION);
965 /* clean up trailing slashes from the URL */
966 len = strlen(uri->path);
967 if (len > 1 && (uri->path)[len - 1] == '/')
968 (uri->path)[len - 1] = '\0';
970 /* Create and fill a session_baton. */
971 ras = apr_pcalloc(pool, sizeof(*ras));
972 ras->pool = pool;
973 ras->url = svn_stringbuf_create(repos_URL, pool);
974 /* copies uri pointer members, they get free'd in __close. */
975 ras->root = *uri;
976 ras->ne_sess = sess;
977 ras->ne_sess2 = sess2;
978 ras->callbacks = callbacks;
979 ras->callback_baton = callback_baton;
980 ras->compression = compression;
981 ras->progress_baton = callbacks->progress_baton;
982 ras->progress_func = callbacks->progress_func;
983 ras->capabilities = apr_hash_make(ras->pool);
984 /* save config and server group in the auth parameter hash */
985 svn_auth_set_parameter(ras->callbacks->auth_baton,
986 SVN_AUTH_PARAM_CONFIG, cfg);
987 svn_auth_set_parameter(ras->callbacks->auth_baton,
988 SVN_AUTH_PARAM_SERVER_GROUP, server_group);
990 /* note that ras->username and ras->password are still NULL at this
991 point. */
993 /* Register an authentication 'pull' callback with the neon sessions */
994 #ifdef SVN_NEON_0_26
995 ne_add_server_auth(sess, neon_auth_types, request_auth, ras);
996 ne_add_server_auth(sess2, neon_auth_types, request_auth, ras);
997 #else
998 ne_set_server_auth(sess, request_auth, ras);
999 ne_set_server_auth(sess2, request_auth, ras);
1000 #endif
1002 if (is_ssl_session)
1004 const char *authorities, *trust_default_ca;
1005 authorities = svn_config_get_server_setting(
1006 cfg, server_group,
1007 SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES,
1008 NULL);
1010 if (authorities != NULL)
1012 char *files, *file, *last;
1013 files = apr_pstrdup(pool, authorities);
1015 while ((file = apr_strtok(files, ";", &last)) != NULL)
1017 ne_ssl_certificate *ca_cert;
1018 files = NULL;
1019 ca_cert = ne_ssl_cert_read(file);
1020 if (ca_cert == NULL)
1022 return svn_error_createf
1023 (SVN_ERR_RA_DAV_INVALID_CONFIG_VALUE, NULL,
1024 _("Invalid config: unable to load certificate file '%s'"),
1025 svn_path_local_style(file, pool));
1027 ne_ssl_trust_cert(sess, ca_cert);
1028 ne_ssl_trust_cert(sess2, ca_cert);
1032 /* When the CA certificate or server certificate has
1033 verification problems, neon will call our verify function before
1034 outright rejection of the connection.*/
1035 ne_ssl_set_verify(sess, server_ssl_callback, ras);
1036 ne_ssl_set_verify(sess2, server_ssl_callback, ras);
1037 /* For client connections, we register a callback for if the server
1038 wants to authenticate the client via client certificate. */
1040 ne_ssl_provide_clicert(sess, client_ssl_callback, ras);
1041 ne_ssl_provide_clicert(sess2, client_ssl_callback, ras);
1043 /* See if the user wants us to trust "default" openssl CAs. */
1044 trust_default_ca = svn_config_get_server_setting(
1045 cfg, server_group,
1046 SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA,
1047 "true");
1049 if (svn_cstring_casecmp(trust_default_ca, "true") == 0)
1051 ne_ssl_trust_default_ca(sess);
1052 ne_ssl_trust_default_ca(sess2);
1055 neonprogress_baton->pool = pool;
1056 neonprogress_baton->progress_baton = callbacks->progress_baton;
1057 neonprogress_baton->progress_func = callbacks->progress_func;
1058 ne_set_progress(sess, ra_neon_neonprogress, neonprogress_baton);
1059 ne_set_progress(sess2, ra_neon_neonprogress, neonprogress_baton);
1060 session->priv = ras;
1062 SVN_ERR(exchange_capabilities(ras, pool));
1064 return SVN_NO_ERROR;
1068 static svn_error_t *svn_ra_neon__reparent(svn_ra_session_t *session,
1069 const char *url,
1070 apr_pool_t *pool)
1072 svn_ra_neon__session_t *ras = session->priv;
1073 ne_uri *uri = apr_pcalloc(session->pool, sizeof(*uri));
1075 SVN_ERR(parse_url(uri, url));
1076 apr_pool_cleanup_register(session->pool, uri, cleanup_uri,
1077 apr_pool_cleanup_null);
1079 ras->root = *uri;
1080 svn_stringbuf_set(ras->url, url);
1081 return SVN_NO_ERROR;
1084 static svn_error_t *svn_ra_neon__get_session_url(svn_ra_session_t *session,
1085 const char **url,
1086 apr_pool_t *pool)
1088 svn_ra_neon__session_t *ras = session->priv;
1089 *url = apr_pstrmemdup(pool, ras->url->data, ras->url->len);
1090 return SVN_NO_ERROR;
1093 static svn_error_t *svn_ra_neon__get_repos_root(svn_ra_session_t *session,
1094 const char **url,
1095 apr_pool_t *pool)
1097 svn_ra_neon__session_t *ras = session->priv;
1099 if (! ras->repos_root)
1101 svn_string_t bc_relative;
1102 svn_stringbuf_t *url_buf;
1104 SVN_ERR(svn_ra_neon__get_baseline_info(NULL, NULL, &bc_relative,
1105 NULL, ras, ras->url->data,
1106 SVN_INVALID_REVNUM, pool));
1108 /* Remove as many path components from the URL as there are components
1109 in bc_relative. */
1110 url_buf = svn_stringbuf_dup(ras->url, pool);
1111 svn_path_remove_components
1112 (url_buf, svn_path_component_count(bc_relative.data));
1113 ras->repos_root = apr_pstrdup(ras->pool, url_buf->data);
1116 *url = ras->repos_root;
1117 return SVN_NO_ERROR;
1121 static svn_error_t *svn_ra_neon__do_get_uuid(svn_ra_session_t *session,
1122 const char **uuid,
1123 apr_pool_t *pool)
1125 svn_ra_neon__session_t *ras = session->priv;
1127 if (! ras->uuid)
1129 svn_ra_neon__resource_t *rsrc;
1130 const char *lopped_path;
1131 const svn_string_t *uuid_propval;
1133 SVN_ERR(svn_ra_neon__search_for_starting_props(&rsrc, &lopped_path,
1134 ras, ras->url->data,
1135 pool));
1136 SVN_ERR(svn_ra_neon__maybe_store_auth_info(ras, pool));
1138 uuid_propval = apr_hash_get(rsrc->propset,
1139 SVN_RA_NEON__PROP_REPOSITORY_UUID,
1140 APR_HASH_KEY_STRING);
1141 if (uuid_propval == NULL)
1142 /* ### better error reporting... */
1143 return svn_error_create(APR_EGENERAL, NULL,
1144 _("The UUID property was not found on the "
1145 "resource or any of its parents"));
1147 if (uuid_propval && (uuid_propval->len > 0))
1148 ras->uuid = apr_pstrdup(ras->pool, uuid_propval->data); /* cache */
1149 else
1150 return svn_error_create
1151 (SVN_ERR_RA_NO_REPOS_UUID, NULL,
1152 _("Please upgrade the server to 0.19 or later"));
1155 *uuid = ras->uuid;
1156 return SVN_NO_ERROR;
1160 static const svn_version_t *
1161 ra_neon_version(void)
1163 SVN_VERSION_BODY;
1166 static const svn_ra__vtable_t neon_vtable = {
1167 ra_neon_version,
1168 ra_neon_get_description,
1169 ra_neon_get_schemes,
1170 svn_ra_neon__open,
1171 svn_ra_neon__reparent,
1172 svn_ra_neon__get_session_url,
1173 svn_ra_neon__get_latest_revnum,
1174 svn_ra_neon__get_dated_revision,
1175 svn_ra_neon__change_rev_prop,
1176 svn_ra_neon__rev_proplist,
1177 svn_ra_neon__rev_prop,
1178 svn_ra_neon__get_commit_editor,
1179 svn_ra_neon__get_file,
1180 svn_ra_neon__get_dir,
1181 svn_ra_neon__get_mergeinfo,
1182 svn_ra_neon__do_update,
1183 svn_ra_neon__do_switch,
1184 svn_ra_neon__do_status,
1185 svn_ra_neon__do_diff,
1186 svn_ra_neon__get_log,
1187 svn_ra_neon__do_check_path,
1188 svn_ra_neon__do_stat,
1189 svn_ra_neon__do_get_uuid,
1190 svn_ra_neon__get_repos_root,
1191 svn_ra_neon__get_locations,
1192 svn_ra_neon__get_location_segments,
1193 svn_ra_neon__get_file_revs,
1194 svn_ra_neon__lock,
1195 svn_ra_neon__unlock,
1196 svn_ra_neon__get_lock,
1197 svn_ra_neon__get_locks,
1198 svn_ra_neon__replay,
1199 svn_ra_neon__has_capability,
1200 svn_ra_neon__replay_range
1203 svn_error_t *
1204 svn_ra_neon__init(const svn_version_t *loader_version,
1205 const svn_ra__vtable_t **vtable,
1206 apr_pool_t *pool)
1208 static const svn_version_checklist_t checklist[] =
1210 { "svn_subr", svn_subr_version },
1211 { "svn_delta", svn_delta_version },
1212 { NULL, NULL }
1215 SVN_ERR(svn_ver_check_list(ra_neon_version(), checklist));
1217 /* Simplified version check to make sure we can safely use the
1218 VTABLE parameter. The RA loader does a more exhaustive check. */
1219 if (loader_version->major != SVN_VER_MAJOR)
1221 return svn_error_createf
1222 (SVN_ERR_VERSION_MISMATCH, NULL,
1223 _("Unsupported RA loader version (%d) for ra_neon"),
1224 loader_version->major);
1227 *vtable = &neon_vtable;
1229 return SVN_NO_ERROR;
1232 /* Compatibility wrapper for the 1.1 and before API. */
1233 #define NAME "ra_neon"
1234 #define DESCRIPTION RA_NEON_DESCRIPTION
1235 #define VTBL neon_vtable
1236 #define INITFUNC svn_ra_neon__init
1237 #define COMPAT_INITFUNC svn_ra_dav_init
1238 #include "../libsvn_ra/wrapper_template.h"