Index: subversion/include/svn_config.h =================================================================== --- subversion/include/svn_config.h (revision 21115) +++ subversion/include/svn_config.h (working copy) @@ -104,6 +104,8 @@ #define SVN_CONFIG_OPTION_PASSWORD_DB "password-db" #define SVN_CONFIG_OPTION_REALM "realm" #define SVN_CONFIG_OPTION_AUTHZ_DB "authz-db" +#define SVN_CONFIG_SECTION_SASL "sasl" +#define SVN_CONFIG_OPTION_USE_SASL "use-sasl" /* For repository password database */ #define SVN_CONFIG_SECTION_USERS "users" Index: subversion/libsvn_repos/repos.c =================================================================== --- subversion/libsvn_repos/repos.c (revision 21115) +++ subversion/libsvn_repos/repos.c (working copy) @@ -1419,6 +1419,10 @@ APR_EOL_STR "### the file's location is relative to the conf directory." APR_EOL_STR +#ifdef SVN_HAVE_SASL + "### If use-sasl is set to \"true\" below, this file will NOT be used." + APR_EOL_STR +#endif "### Uncomment the line below to use the default password file." APR_EOL_STR "# password-db = passwd" @@ -1446,6 +1450,17 @@ "### is repository's uuid." APR_EOL_STR "# realm = My First Repository" +#ifdef SVN_HAVE_SASL + APR_EOL_STR + APR_EOL_STR + "[sasl]" + APR_EOL_STR + "### This option specifies whether you want to use the Cyrus SASL" + APR_EOL_STR + "### library for authentication. Default is false." + APR_EOL_STR + "# use-sasl = true" +#endif APR_EOL_STR; SVN_ERR_W(svn_io_file_create(svn_repos_svnserve_conf(repos, pool), Index: subversion/svnserve/sasl_auth.c =================================================================== --- subversion/svnserve/sasl_auth.c (revision 0) +++ subversion/svnserve/sasl_auth.c (revision 0) @@ -0,0 +1,320 @@ +/* + * sasl_auth.c : Functions for SASL-based authentication + * + * ==================================================================== + * Copyright (c) 2000-2006 CollabNet. All rights reserved. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://subversion.tigris.org/license-1.html. + * If newer versions of this license are posted there, you may use a + * newer version instead, at your option. + * + * This software consists of voluntary contributions made by many + * individuals. For exact contribution history, see the revision + * history and logs, available at http://subversion.tigris.org/. + * ==================================================================== + */ + +#include "svn_private_config.h" +#ifdef SVN_HAVE_SASL + +#define APR_WANT_STRFUNC +#include +#include +#include + +#include "svn_types.h" +#include "svn_string.h" +#include "svn_pools.h" +#include "svn_error.h" +#include "svn_ra_svn.h" +#include "svn_base64.h" + +#include "server.h" + +#include "../libsvn_ra_svn/ra_svn_sasl.h" + +/* SASL calls this function before doing anything with a username, which gives + us an opportunity to do some sanity-checking. If the username contains + an '@', SASL interprets the part following the '@' as the name of the + authentication realm, and worst of all, this realm overrides the one that + we pass to sasl_server_new(). If we didn't check this, a user that could + successfully authenticate in one realm would be able to authenticate + in any other realm, simply by appending '@realm' to his username. */ +static int canonicalize_username(sasl_conn_t *conn, + void *context, /* not used */ + const char *in, /* the username */ + unsigned inlen, /* its length */ + unsigned flags, /* not used */ + const char *user_realm, + char *out, /* the output buffer */ + unsigned out_max, unsigned *out_len) +{ + int realm_len = strlen(user_realm); + char *pos; + + *out_len = inlen; + + /* If the username contains an '@', the part after the '@' is the realm + that the user wants to authenticate in. */ + pos = memchr(in, '@', inlen); + if (pos) + { + /* The only valid realm is user_realm (i.e. the repository's realm). + If the user gave us another realm, complain. */ + if (strncmp(pos+1, user_realm, inlen-(pos-in+1)) != 0) + return SASL_BADPROT; + } + else + *out_len += realm_len + 1; + + /* First, check that the output buffer is large enough. */ + if (*out_len > out_max) + return SASL_BADPROT; + + /* Copy the username part. */ + strncpy(out, in, inlen); + + /* If necessary, copy the realm part. */ + if (!pos) + { + out[inlen] = '@'; + strncpy(out+inlen+1, user_realm, realm_len); + } + + return SASL_OK; +} + +static sasl_callback_t callbacks[] = +{ + { SASL_CB_CANON_USER, canonicalize_username, NULL }, + { SASL_CB_LIST_END, NULL, NULL } +}; + +svn_error_t *sasl_init() +{ + int result; + apr_status_t err = svn_ra_svn__sasl_common_init(); + + if (err) + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + _("Could not initialize the SASL library")); + + /* The second parameter tells SASL to look for a configuration file + named Subversion.conf. */ + result = sasl_server_init(callbacks, "Subversion"); + if (result != SASL_OK) + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + sasl_errstring(result, NULL, NULL)); + return SVN_NO_ERROR; +} + +/* Tell the client the authentication failed. */ +static svn_error_t *fail(svn_ra_svn_conn_t *conn, apr_pool_t *pool, + const char *msg) +{ + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(c)", "failure", msg)); + return svn_ra_svn_flush(conn, pool); +} + +static svn_error_t *try_auth(svn_ra_svn_conn_t *conn, + sasl_conn_t *sasl_ctx, + apr_pool_t *pool, + server_baton_t *b, + svn_boolean_t *success) +{ + const char *out, *mech; + const svn_string_t *arg = NULL, *in; + unsigned int outlen; + int result; + svn_boolean_t use_base64; + + *success = FALSE; + + /* Read the client's chosen mech and the initial response. */ + SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "w(?s)", &mech, &in)); + + if (strcmp(mech, "EXTERNAL") == 0 && !in) + in = svn_string_create(b->tunnel_user, pool); + else if (in) + in = svn_base64_decode_string(in, pool); + + /* For CRAM-MD5, we don't base64-encode stuff. */ + use_base64 = (strcmp(mech, "CRAM-MD5") != 0); + + result = sasl_server_start(sasl_ctx, mech, + in ? in->data : NULL, + in ? in->len : 0, &out, &outlen); + + if (result != SASL_OK && result != SASL_CONTINUE) + return fail(conn, pool, sasl_errdetail(sasl_ctx)); + + while (result == SASL_CONTINUE) + { + svn_ra_svn_item_t *item; + + arg = svn_string_ncreate(out, outlen, pool); + /* Encode what we send to the client. */ + if (use_base64) + arg = svn_base64_encode_string(arg, pool); + + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(s)", "step", arg)); + + /* Read and decode the client response. */ + SVN_ERR(svn_ra_svn_read_item(conn, pool, &item)); + if (item->kind != SVN_RA_SVN_STRING) + return SVN_NO_ERROR; + + in = item->u.string; + if (use_base64) + in = svn_base64_decode_string(in, pool); + result = sasl_server_step(sasl_ctx, in->data, in->len, &out, &outlen); + } + + if (result != SASL_OK) + return fail(conn, pool, sasl_errdetail(sasl_ctx)); + + /* Send our last response, if necessary. */ + if (outlen) + arg = svn_base64_encode_string(svn_string_ncreate(out, outlen, pool), + pool); + else + arg = NULL; + + *success = TRUE; + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(?s)", "success", arg)); + + return SVN_NO_ERROR; +} + +static svn_error_t *get_local_hostname(char **hostname, + apr_socket_t *sock) +{ + apr_status_t apr_err; + apr_sockaddr_t *sa; + + apr_err = apr_socket_addr_get(&sa, APR_LOCAL, sock); + if (apr_err) + return svn_error_wrap_apr(apr_err, NULL); + + apr_err = apr_getnameinfo(hostname, sa, 0); + if (apr_err) + return svn_error_wrap_apr(apr_err, NULL); + + return SVN_NO_ERROR; +} + +static apr_status_t sasl_dispose_cb(void *data) +{ + sasl_conn_t *sasl_ctx = (sasl_conn_t*) data; + sasl_dispose(&sasl_ctx); + return APR_SUCCESS; +} + +svn_error_t *sasl_auth_request(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + server_baton_t *b, + enum access_type required, + svn_boolean_t needs_username) +{ + sasl_conn_t *sasl_ctx; + const char *localaddrport = NULL, *remoteaddrport = NULL; + const char *mechlist; + char *hostname = NULL; + sasl_security_properties_t secprops = SVN_RA_SVN__DEFAULT_SECPROPS; + svn_boolean_t success, no_anonymous; + int mech_count, result = SASL_OK; + + if (!b->tunnel_user) + { + SVN_ERR(svn_ra_svn__get_addresses(&localaddrport, &remoteaddrport, + conn->sock, pool)); + SVN_ERR(get_local_hostname(&hostname, conn->sock)); + } + + /* Create a SASL context. SASL_SUCCESS_DATA tells SASL that the protocol + supports sending data along with the final "success" message. */ + result = sasl_server_new("svn", + hostname, b->realm, + localaddrport, remoteaddrport, + NULL, SASL_SUCCESS_DATA, + &sasl_ctx); + if (result != SASL_OK) + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + sasl_errdetail(sasl_ctx)); + + /* Make sure the context is always destroyed. */ + apr_pool_cleanup_register(pool, sasl_ctx, sasl_dispose_cb, + apr_pool_cleanup_null); + + /* Don't allow PLAIN or LOGIN, since we don't support TLS yet. */ + secprops.security_flags = SASL_SEC_NOPLAINTEXT; + + /* Don't allow ANONYMOUS if a username is required. */ + no_anonymous = needs_username || get_access(b, UNAUTHENTICATED) < required; + if (no_anonymous) + secprops.security_flags |= SASL_SEC_NOANONYMOUS; + + /* Set security properties. */ + result = sasl_setprop(sasl_ctx, SASL_SEC_PROPS, &secprops); + if (result != SASL_OK) + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + sasl_errdetail(sasl_ctx)); + + /* SASL needs to know if we are externally authenticated. */ + if (b->tunnel_user) + result = sasl_setprop(sasl_ctx, SASL_AUTH_EXTERNAL, b->tunnel_user); + if (result != SASL_OK) + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + sasl_errdetail(sasl_ctx)); + + /* Get the list of mechanisms. */ + result = sasl_listmech(sasl_ctx, NULL, NULL, " ", NULL, + &mechlist, NULL, &mech_count); + + if (result != SASL_OK || mech_count == 0) + { + svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + _("Could not obtain the list" + " of SASL mechanisms")); + SVN_ERR(svn_ra_svn_write_cmd_failure(conn, pool, err)); + SVN_ERR(svn_ra_svn_flush(conn, pool)); + return err; + } + + /* Send the list of mechanisms and the realm to the client. */ + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((w)c)", "success", + mechlist, b->realm)); + + /* The main authentication loop. */ + apr_pool_t *subpool = svn_pool_create(pool); + do + { + svn_pool_clear(subpool); + SVN_ERR(try_auth(conn, sasl_ctx, subpool, b, &success)); + } + while (!success); + svn_pool_destroy(subpool); + + if (no_anonymous) + { + char *p; + const char *user; + + /* Get the authenticated username. */ + result = sasl_getprop(sasl_ctx, SASL_USERNAME, (const void **)&user); + + /* Drop the realm part. */ + if ((p = strchr(user, '@')) != NULL) + b->user = apr_pstrndup(pool, user, p - user); + else + /* This shouldn't happen. */ + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + _("SASL gave us a malformed username")); + } + + return SVN_NO_ERROR; +} + +#endif /* SVN_HAVE_SASL */ Index: subversion/svnserve/main.c =================================================================== --- subversion/svnserve/main.c (revision 21115) +++ subversion/svnserve/main.c (working copy) @@ -329,6 +329,10 @@ if (svn_cmdline_init("svnserve", stderr) != EXIT_SUCCESS) return EXIT_FAILURE; +#ifdef SVN_HAVE_SASL + sasl_init(); +#endif + /* Create our top-level pool. */ pool = svn_pool_create(NULL); Index: subversion/svnserve/serve.c =================================================================== --- subversion/svnserve/serve.c (revision 21115) +++ subversion/svnserve/serve.c (working copy) @@ -43,25 +43,7 @@ #include "server.h" typedef struct { - svn_repos_t *repos; - svn_fs_t *fs; /* For convenience; same as svn_repos_fs(repos) */ - svn_config_t *cfg; /* Parsed repository svnserve.conf */ - svn_config_t *pwdb; /* Parsed password database */ - svn_authz_t *authzdb; /* Parsed authz rules */ - const char *authz_repos_name; /* The name of the repository */ - const char *realm; /* Authentication realm */ - const char *repos_url; /* URL to base of repository */ - svn_stringbuf_t *fs_path;/* Decoded base path inside repository */ - const char *user; - svn_boolean_t tunnel; /* Tunneled through login agent */ - const char *tunnel_user; /* Allow EXTERNAL to authenticate as this */ - svn_boolean_t read_only; /* Disallow write access (global flag) */ - int protocol_version; apr_pool_t *pool; -} server_baton_t; - -typedef struct { - apr_pool_t *pool; svn_revnum_t *new_rev; const char **date; const char **author; @@ -85,9 +67,6 @@ apr_pool_t *pool; /* Pool provided in the handler call. */ } file_revs_baton_t; -enum authn_type { UNAUTHENTICATED, AUTHENTICATED }; -enum access_type { NO_ACCESS, READ_ACCESS, WRITE_ACCESS }; - /* Verify that URL is inside REPOS_URL and get its fs path. Assume that REPOS_URL and URL are already URI-decoded. */ static svn_error_t *get_fs_path(const char *repos_url, const char *url, @@ -182,7 +161,7 @@ } -static enum access_type get_access(server_baton_t *b, enum authn_type auth) +enum access_type get_access(server_baton_t *b, enum authn_type auth) { const char *var = (auth == AUTHENTICATED) ? SVN_CONFIG_OPTION_AUTH_ACCESS : SVN_CONFIG_OPTION_ANON_ACCESS; @@ -313,13 +292,11 @@ "Must authenticate with listed mechanism"); } -/* Perform an authentication request in order to get an access level of - * REQUIRED or higher. Since the client may escape the authentication - * exchange, the caller should check current_access(b) to see if - * authentication succeeded. */ -static svn_error_t *auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool, - server_baton_t *b, enum access_type required, - svn_boolean_t needs_username) +/* Perform an authentication request using the built-in SASL implementation. */ +static svn_error_t * +simple_auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool, + server_baton_t *b, enum access_type required, + svn_boolean_t needs_username) { svn_boolean_t success; const char *mech, *mecharg; @@ -339,6 +316,23 @@ return SVN_NO_ERROR; } +/* Perform an authentication request in order to get an access level of + * REQUIRED or higher. Since the client may escape the authentication + * exchange, the caller should check current_access(b) to see if + * authentication succeeded. */ +static svn_error_t *auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool, + server_baton_t *b, enum access_type required, + svn_boolean_t needs_username) +{ +#ifdef SVN_HAVE_SASL + if (b->use_sasl) + SVN_ERR(sasl_auth_request(conn, pool, b, required, needs_username)); + else +#endif + SVN_ERR(simple_auth_request(conn, pool, b, required, needs_username)); + return SVN_NO_ERROR; +} + /* Send a trivial auth notification on CONN which lists no mechanisms, * indicating that authentication is unnecessary. Usually called in * response to invocation of a svnserve command. @@ -437,7 +431,11 @@ the first time round. */ if (b->user == NULL && get_access(b, AUTHENTICATED) >= req - && (b->tunnel_user || b->pwdb) && b->protocol_version >= 2) + && (b->tunnel_user || b->pwdb +#ifdef SVN_HAVE_SASL + || b->use_sasl +#endif + ) && b->protocol_version >= 2) SVN_ERR(auth_request(conn, pool, b, req, TRUE)); /* Now that an authentication has been done get the new take of @@ -2150,15 +2148,19 @@ svn_error_clear(err); else if (err) return err; - else - { - /* Use the repository UUID as the default realm. */ - SVN_ERR(svn_fs_get_uuid(b->fs, &b->realm, pool)); - svn_config_get(b->cfg, &b->realm, SVN_CONFIG_SECTION_GENERAL, - SVN_CONFIG_OPTION_REALM, b->realm); - } } +#ifdef SVN_HAVE_SASL + /* Should we use Cyrus SASL? */ + svn_config_get_bool(b->cfg, &b->use_sasl, SVN_CONFIG_SECTION_SASL, + SVN_CONFIG_OPTION_USE_SASL, FALSE); +#endif + + /* Use the repository UUID as the default realm. */ + SVN_ERR(svn_fs_get_uuid(b->fs, &b->realm, pool)); + svn_config_get(b->cfg, &b->realm, SVN_CONFIG_SECTION_GENERAL, + SVN_CONFIG_OPTION_REALM, b->realm); + /* Read authz configuration. */ svn_config_get(b->cfg, &authz_path, SVN_CONFIG_SECTION_GENERAL, SVN_CONFIG_OPTION_AUTHZ_DB, NULL); @@ -2180,7 +2182,11 @@ are given by the client. */ if (get_access(b, UNAUTHENTICATED) == NO_ACCESS && (get_access(b, AUTHENTICATED) == NO_ACCESS - || (!b->tunnel_user && !b->pwdb))) + || (!b->tunnel_user && !b->pwdb +#ifdef SVN_HAVE_SASL + && !b->use_sasl +#endif + ))) return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, "No access allowed to this repository"); return SVN_NO_ERROR; Index: subversion/svnserve/server.h =================================================================== --- subversion/svnserve/server.h (revision 21115) +++ subversion/svnserve/server.h (working copy) @@ -27,6 +27,34 @@ extern "C" { #endif /* __cplusplus */ +#include "svn_repos.h" + +typedef struct server_baton_t { + svn_repos_t *repos; + svn_fs_t *fs; /* For convenience; same as svn_repos_fs(repos) */ + svn_config_t *cfg; /* Parsed repository svnserve.conf */ + svn_config_t *pwdb; /* Parsed password database */ + svn_authz_t *authzdb; /* Parsed authz rules */ + const char *authz_repos_name; /* The name of the repository */ + const char *realm; /* Authentication realm */ + const char *repos_url; /* URL to base of repository */ + svn_stringbuf_t *fs_path;/* Decoded base path inside repository */ + const char *user; + svn_boolean_t tunnel; /* Tunneled through login agent */ + const char *tunnel_user; /* Allow EXTERNAL to authenticate as this */ + svn_boolean_t read_only; /* Disallow write access (global flag) */ +#ifdef SVN_HAVE_SASL + svn_boolean_t use_sasl; /* Use Cyrus SASL for authentication */ +#endif + int protocol_version; + apr_pool_t *pool; +} server_baton_t; + +enum authn_type { UNAUTHENTICATED, AUTHENTICATED }; +enum access_type { NO_ACCESS, READ_ACCESS, WRITE_ACCESS }; + +enum access_type get_access(server_baton_t *b, enum authn_type auth); + typedef struct serve_params_t { /* The virtual root of the repositories to serve. The client URL path is interpreted relative to this root and is not allowed to @@ -51,6 +79,16 @@ svn_error_t *serve(svn_ra_svn_conn_t *conn, serve_params_t *params, apr_pool_t *pool); +/* Initialize the Cyrus SASL library. */ +svn_error_t *sasl_init(); + +/* Authenticate using Cyrus SASL. */ +svn_error_t *sasl_auth_request(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + server_baton_t *b, + enum access_type required, + svn_boolean_t needs_username); + #ifdef __cplusplus } #endif /* __cplusplus */