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 * ====================================================================
24 #define APR_WANT_STRFUNC
26 #include <apr_general.h>
31 #include "svn_error.h"
32 #include "svn_pools.h"
34 #include "../libsvn_ra/ra_loader.h"
35 #include "svn_config.h"
36 #include "svn_delta.h"
37 #include "svn_version.h"
41 #include "svn_private_config.h"
45 #define DEFAULT_HTTP_TIMEOUT 3600
48 /* a cleanup routine attached to the pool that contains the RA session
50 static apr_status_t
cleanup_session(void *sess
)
52 ne_session_destroy(sess
);
56 /* a cleanup routine attached to the pool that contains the RA session
58 static apr_status_t
cleanup_uri(void *uri
)
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
)
71 svn_ra_neon__session_t
*ras
= userdata
;
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
)
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
),
99 ras
->callbacks
->auth_baton
,
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
,
111 svn_error_clear(err
);
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
);
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. */
137 convert_neon_failures(int neon_failures
)
139 apr_uint32_t svn_failures
= 0;
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. */
154 svn_failures
|= SVN_AUTH_SSL_OTHER
;
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. */
164 server_ssl_callback(void *userdata
,
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
;
171 svn_auth_iterstate_t
*state
;
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
,
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
,
208 apr_pool_create(&pool
, ras
->pool
);
209 error
= svn_auth_first_credentials(&creds
, &state
,
210 SVN_AUTH_CRED_SSL_SERVER_TRUST
,
212 ras
->callbacks
->auth_baton
,
214 if (error
|| ! creds
)
216 svn_error_clear(error
);
220 server_creds
= creds
;
221 error
= svn_auth_save_credentials(state
, pool
);
224 /* It would be nice to show the error to the user somehow... */
225 svn_error_clear(error
);
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
;
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
;
246 svn_boolean_t ok
= FALSE
;
250 apr_pool_create(&pool
, ras
->pool
);
251 for (try = 0; TRUE
; ++try)
255 error
= svn_auth_first_credentials(&creds
, &state
,
256 SVN_AUTH_CRED_SSL_CLIENT_CERT_PW
,
258 ras
->callbacks
->auth_baton
,
263 error
= svn_auth_next_credentials(&creds
, state
, pool
);
266 if (error
|| ! creds
)
268 /* Failure or too many attempts */
269 svn_error_clear(error
);
274 svn_auth_cred_ssl_client_cert_pw_t
*pw_creds
= creds
;
276 if (ne_ssl_clicert_decrypt(clicert
, pw_creds
->password
) == 0)
284 svn_pool_destroy(pool
);
291 client_ssl_callback(void *userdata
, ne_session
*sess
,
292 const ne_ssl_dname
*const *dnames
,
295 svn_ra_neon__session_t
*ras
= userdata
;
296 ne_ssl_client_cert
*clicert
= NULL
;
298 svn_auth_iterstate_t
*state
;
299 const char *realmstring
;
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)
313 error
= svn_auth_first_credentials(&creds
, &state
,
314 SVN_AUTH_CRED_SSL_CLIENT_CERT
,
316 ras
->callbacks
->auth_baton
,
321 error
= svn_auth_next_credentials(&creds
, state
, pool
);
324 if (error
|| ! creds
)
326 /* Failure or too many attempts */
327 svn_error_clear(error
);
332 svn_auth_cred_ssl_client_cert_t
*client_creds
= creds
;
334 clicert
= ne_ssl_clicert_read(client_creds
->cert_file
);
337 if (! ne_ssl_clicert_encrypted(clicert
) ||
338 client_ssl_decrypt_cert(ras
, client_creds
->cert_file
,
341 ne_ssl_set_clicert(sess
, clicert
);
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
,
367 svn_boolean_t
*compression
,
368 unsigned int *neon_auth_types
,
370 const char *requested_host
,
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. */
378 *proxy_port
= (unsigned int) -1;
379 *proxy_username
= NULL
;
380 *proxy_password
= 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
);
392 apr_array_header_t
*l
= svn_cstring_split(exceptions
, ",", TRUE
, pool
);
393 is_exception
= svn_cstring_match_glob_list(requested_host
, l
);
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
);
412 svn_config_get(cfg
, &http_auth_types
, SVN_CONFIG_SECTION_GLOBAL
,
413 SVN_CONFIG_OPTION_HTTP_AUTH_TYPES
, NULL
);
418 server_group
= svn_config_find_group(cfg
, requested_host
,
419 SVN_CONFIG_SECTION_GROUPS
, pool
);
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
,
438 svn_config_get(cfg
, &debug_str
, server_group
,
439 SVN_CONFIG_OPTION_NEON_DEBUG_MASK
, debug_str
);
441 svn_config_get(cfg
, &http_auth_types
, SVN_CONFIG_SECTION_GLOBAL
,
442 SVN_CONFIG_OPTION_HTTP_AUTH_TYPES
, NULL
);
446 /* Special case: convert the port value, if any. */
450 const long int port
= strtol(port_str
, &endstr
, 10);
453 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL
, NULL
,
454 _("Invalid URL: illegal character in proxy "
457 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL
, NULL
,
458 _("Invalid URL: negative proxy port number"));
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"));
471 const long int timeout
= strtol(timeout_str
, &endstr
, 10);
474 return svn_error_create(SVN_ERR_RA_DAV_INVALID_CONFIG_VALUE
, NULL
,
475 _("Invalid config: illegal character in "
478 return svn_error_create(SVN_ERR_RA_DAV_INVALID_CONFIG_VALUE
, NULL
,
479 _("Invalid config: negative timeout value"));
480 *timeout_seconds
= timeout
;
483 *timeout_seconds
= 0;
488 const long int debug
= strtol(debug_str
, &endstr
, 10);
491 return svn_error_create(SVN_ERR_RA_DAV_INVALID_CONFIG_VALUE
, NULL
,
492 _("Invalid config: illegal character in "
493 "debug mask value"));
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
;
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 */
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.
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
,
564 struct proxy_auth_baton
*pab
= userdata
;
571 apr_cpystrn(username
, pab
->username
, NE_ABUFSIZ
);
572 apr_cpystrn(password
, pab
->password
, NE_ABUFSIZ
);
577 #define RA_NEON_DESCRIPTION \
578 N_("Module for accessing a repository via WebDAV protocol using Neon.")
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
;
600 } neonprogress_baton_t
;
604 ra_neon_neonprogress(void *baton
, ne_off_t progress
, ne_off_t total
)
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. */
630 parse_capabilities(ne_request
*req
,
631 svn_ra_neon__session_t
*ras
,
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. */
646 const char *header_name
;
647 const char *header_value
;
648 ne_header_cursor
= ne_response_header_iterate(req
,
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
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
,
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. */
707 exchange_capabilities(svn_ra_neon__session_t
*ras
, apr_pool_t
*pool
)
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
);
724 if (http_ret_code
== 200)
726 parse_capabilities(rar
->ne_req
, ras
, pool
);
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"),
739 svn_ra_neon__request_destroy(rar
);
746 svn_ra_neon__has_capability(svn_ra_session_t
*session
,
748 const char *capability
,
751 svn_ra_neon__session_t
*ras
= session
->priv
;
752 const char *cap_result
= apr_hash_get(ras
->capabilities
,
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
)
769 else if (cap_result
== capability_no
)
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
);
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. */
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
)
806 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL
, NULL
,
807 _("Malformed URL for repository"));
810 uri
->port
= ne_uri_defaultport(uri
->scheme
);
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
,
824 ne_session
*sess
, *sess2
;
825 ne_uri
*uri
= apr_pcalloc(pool
, sizeof(*uri
));
826 svn_ra_neon__session_t
*ras
;
828 svn_boolean_t compression
;
830 const char *server_group
;
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
);
842 useragent
= apr_pstrcat(pool
, "SVN/" SVN_VERSION
"/", client_string
, NULL
);
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);
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
;
889 server_group
= svn_config_find_group(cfg
, uri
->host
,
890 SVN_CONFIG_SECTION_GROUPS
, pool
);
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
;
903 SVN_ERR(get_server_settings(&proxy_host
,
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
;
922 neon_auth_types
|= NE_AUTH_NEGOTIATE
;
927 ne_debug_init(stderr
, debug
);
931 ne_session_proxy(sess
, proxy_host
, proxy_port
);
932 ne_session_proxy(sess2
, proxy_host
, proxy_port
);
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
);
949 timeout
= DEFAULT_HTTP_TIMEOUT
;
950 ne_set_read_timeout(sess
, timeout
);
951 ne_set_read_timeout(sess2
, timeout
);
956 ne_set_useragent(sess
, useragent
);
957 ne_set_useragent(sess2
, useragent
);
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
));
973 ras
->url
= svn_stringbuf_create(repos_URL
, pool
);
974 /* copies uri pointer members, they get free'd in __close. */
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
993 /* Register an authentication 'pull' callback with the neon sessions */
995 ne_add_server_auth(sess
, neon_auth_types
, request_auth
, ras
);
996 ne_add_server_auth(sess2
, neon_auth_types
, request_auth
, ras
);
998 ne_set_server_auth(sess
, request_auth
, ras
);
999 ne_set_server_auth(sess2
, request_auth
, ras
);
1004 const char *authorities
, *trust_default_ca
;
1005 authorities
= svn_config_get_server_setting(
1007 SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES
,
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
;
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(
1046 SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA
,
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
,
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
);
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
,
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
,
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
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
,
1125 svn_ra_neon__session_t
*ras
= session
->priv
;
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
,
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 */
1150 return svn_error_create
1151 (SVN_ERR_RA_NO_REPOS_UUID
, NULL
,
1152 _("Please upgrade the server to 0.19 or later"));
1156 return SVN_NO_ERROR
;
1160 static const svn_version_t
*
1161 ra_neon_version(void)
1166 static const svn_ra__vtable_t neon_vtable
= {
1168 ra_neon_get_description
,
1169 ra_neon_get_schemes
,
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
,
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
1204 svn_ra_neon__init(const svn_version_t
*loader_version
,
1205 const svn_ra__vtable_t
**vtable
,
1208 static const svn_version_checklist_t checklist
[] =
1210 { "svn_subr", svn_subr_version
},
1211 { "svn_delta", svn_delta_version
},
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"