[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

Re: [PATCH] Authentication for svnserve using LDAP

From: David Glasser <glasser_at_davidglasser.net>
Date: Thu, 31 Jan 2008 12:40:28 -0800

Hi Eugene. This looks pretty interesting! Do you think you could
submit it according to our patch guidelines at
http://subversion.tigris.org/hacking.html, especially writing a log
message?

--dave

2008/1/31 Ivlev, Eugene <eivlev_at_esignaldev.com>:
>
> New feature.
> Authentication for svnserve using LDAP. Information about LDAP setting
> contains in svnserve.conf.
> Example:
>
> [ldap]
> Server=ldap://dc1:389/
> Base=OU=PrivateUsers,OU=TSSupportUsers,DC=nwork,DC=local
>
> BindName=CN=LDAPSvcAccount,OU=ServiceAccount,OU=TSSupportUsers,DC=nwork,DC=local
> BindPass=supersvcpass
> UserIdAttr=sAMaccountName
>
> For compilation You are also should get the open_ldap or novell-cldap
> library.
> Compile without preprocessor directive SVN_HAVE_SASL.
>
>
> Index: include/svn_config.h
> ===================================================================
> --- include/svn_config.h (revision 28868)
> +++ include/svn_config.h (working copy)
> @@ -114,6 +114,12 @@
> #define SVN_CONFIG_OPTION_USE_SASL "use-sasl"
> #define SVN_CONFIG_OPTION_MIN_SSF "min-encryption"
> #define SVN_CONFIG_OPTION_MAX_SSF "max-encryption"
> +#define SVN_CONFIG_SECTION_LDAP
> "ldap"
> +#define SVN_CONFIG_OPTION_LDAP_SERVER "Server"
> +#define SVN_CONFIG_OPTION_LDAP_BASE
> "Base"
> +#define SVN_CONFIG_OPTION_LDAP_BIND_NAME "BindName"
> +#define SVN_CONFIG_OPTION_LDAP_BIND_PASS "BindPass"
> +#define SVN_CONFIG_OPTION_LDAP_USER_ID_ATTR "UserIdAttr"
>
> /* For repository password database */
> #define SVN_CONFIG_SECTION_USERS "users"
> Index: include/svn_ra_svn.h
> ===================================================================
> --- include/svn_ra_svn.h (revision 28868)
> +++ include/svn_ra_svn.h (working copy)
> @@ -410,6 +410,13 @@
> svn_config_t *pwdb, const char **user,
> svn_boolean_t *success);
>
> +/** This function is only intended for use by svnserve.
> + *
> + * Basic password authentication.
> + */
> +svn_error_t *svn_ra_svn_basic_server(svn_ra_svn_conn_t *conn, apr_pool_t
> *pool,
> +
> svn_config_t *cfg, const char **user,
> +
> svn_boolean_t *success);
> /**
> * Get libsvn_ra_svn version information.
> * @since New in 1.1.
> Index: libsvn_ra_svn/basic.c
> ===================================================================
> --- libsvn_ra_svn/basic.c (revision 0)
> +++ libsvn_ra_svn/basic.c (revision 0)
> @@ -0,0 +1,420 @@
> +/*
> +
> +*/
> +#define APR_WANT_STRFUNC
> +#define APR_WANT_STDIO
> +
> +#include <apr_want.h>
> +#include <apr_general.h>
> +#include <apr_strings.h>
> +#include <apr_network_io.h>
> +#include <apr_time.h>
> +#include <apr_base64.h>
> +
> +#include "svn_types.h"
> +#include "svn_string.h"
> +#include "svn_error.h"
> +#include "svn_ra_svn.h"
> +#include "svn_config.h"
> +#include "svn_private_config.h"
> +
> +#include "ra_svn.h"
> +
> +#include "lber.h"
> +#include "ldap.h"
> +
> +typedef struct {
> + char *host;
> + int port;
> +
> + char *base;
> + char *bindname;
> + int searchmode;
> + char *bindpass;
> + int ldap_auth;
> + int reset_username;
> +
> + char *userid_attr;
> + char *userpassword_attr;
> + char *groupmember_attr;
> +
> + LDAP *ld;
> +} ldap_config_struct;
> +
> +/*
> +
> +*/
> +ldap_config_struct* get_ldap_config(svn_config_t **cfg, apr_pool_t *pool)
> +{
> + ldap_config_struct* conf;
> + const char *ldap_server;
> + const char *ldap_base;
> + const char *ldap_bind_name;
> + const char *ldap_bind_pass;
> + const char *ldap_user_id_attr;
> +
> + LDAPURLDesc *ldap_url;
> +
> + int host_str_len;
> + int base_str_len;
> + int bindname_str_len;
> + int bindpass_str_len;
> + int useridattr_str_len;
> +
> + conf = (ldap_config_struct*)apr_palloc(pool,
> sizeof(ldap_config_struct));
> +
> + conf->host = "localhost";
> + conf->port = LDAP_PORT;
> +
> + conf->base = NULL;
> + conf->searchmode = LDAP_SCOPE_ONELEVEL;
> + conf->bindname = NULL;
> + conf->bindpass = NULL;
> +
> + conf->userid_attr = "userid";
> + conf->userpassword_attr = NULL;
> + conf->groupmember_attr = "uniquemember";
> +
> + conf->ldap_auth = 1;
> + conf->reset_username = 0;
> +
> + conf->ld = NULL;
> +
> + svn_config_get(*cfg, &ldap_server, SVN_CONFIG_SECTION_LDAP,
> + SVN_CONFIG_OPTION_LDAP_SERVER, NULL);
> +
> + svn_config_get(*cfg, &ldap_base, SVN_CONFIG_SECTION_LDAP,
> + SVN_CONFIG_OPTION_LDAP_BASE, NULL);
> +
> + svn_config_get(*cfg, &ldap_bind_name, SVN_CONFIG_SECTION_LDAP,
> + SVN_CONFIG_OPTION_LDAP_BIND_NAME, NULL);
> +
> + svn_config_get(*cfg, &ldap_bind_pass, SVN_CONFIG_SECTION_LDAP,
> + SVN_CONFIG_OPTION_LDAP_BIND_PASS, NULL);
> +
> + svn_config_get(*cfg, &ldap_user_id_attr, SVN_CONFIG_SECTION_LDAP,
> + SVN_CONFIG_OPTION_LDAP_USER_ID_ATTR, NULL);
> +
> + if (ldap_url_parse(ldap_server,&ldap_url)!=0)
> + {
> + return NULL; // cannot parse LDAP url
> + }
> +
> + host_str_len = strlen(ldap_url->lud_host);
> + conf->host = apr_palloc(pool, host_str_len + 1);
> + apr_cpystrn(conf->host, ldap_url->lud_host, host_str_len + 1);
> +
> + if (ldap_url->lud_port!=0)
> + conf->port=ldap_url->lud_port;
> +
> + base_str_len = strlen(ldap_base);
> + conf->base = apr_palloc(pool, base_str_len + 1);
> + apr_cpystrn(conf->base, ldap_base, base_str_len + 1);
> +
> + bindname_str_len = strlen(ldap_bind_name);
> + conf->bindname = apr_palloc(pool, bindname_str_len + 1);
> + apr_cpystrn(conf->bindname, ldap_bind_name, bindname_str_len + 1);
> +
> + bindpass_str_len = strlen(ldap_bind_pass);
> + conf->bindpass = apr_palloc(pool, bindpass_str_len + 1);
> + apr_cpystrn(conf->bindpass, ldap_bind_pass, bindpass_str_len + 1);
> +
> +
> + useridattr_str_len = strlen(ldap_user_id_attr);
> + conf->userid_attr = apr_palloc(pool, useridattr_str_len + 1);
> + apr_cpystrn(conf->userid_attr, ldap_user_id_attr, useridattr_str_len
> + 1);
> +
> + ldap_free_urldesc(ldap_url);
> +
> + return conf;
> +}
> +
> +/*
> +
> +*/
> +static LDAP *ldap_open_and_bind (char *host,int port,char *username,char
> *password) {
> + LDAP *ld;
> + int res;
> +
> + ld=ldap_open(host,port);
> + if (!ld)
> + return NULL;
> +
> + if (username == NULL) {
> + res = ldap_simple_bind_s(ld,NULL,NULL);
> + } else {
> + res = ldap_simple_bind_s(ld,username,password);
> + }
> + if (res!=LDAP_SUCCESS) {
> + ldap_unbind(ld);
> + return NULL;
> + }
> +
> + return ld;
> +}
> +
> +/*
> +
> +*/
> +svn_boolean_t *ldap_authentication(ldap_config_struct* conf, char* user,
> char* password, apr_pool_t *pool)
> +{
> + LDAPMessage *msg, *entry;
> + int res;
> + char *filter;
> + int scope;
> +
> + if (conf == NULL)
> + return FALSE;
> +
> + if (!conf->ldap_auth) return FALSE;
> +
> + /* Changed by Soren Roug */
> + scope = conf->searchmode;
> +
> + conf->ld = ldap_open_and_bind(conf->host,
> + conf->port,
> + conf->bindname,
> + conf->bindpass);
> +
> + /* we should have an open and bound connection. if not then error
> */
> + if (conf->ld==NULL)
> + return FALSE;
> +
> + filter = apr_pstrcat(pool,
> + "(",
> + conf->userid_attr,
> + "=",
> + user,
> + ")",
> + NULL);
> +
> + /* Changed this and one other occurrence from LDAP_SCOPE_whatever to
> + user configurable scope. JM 12/23/98 */
> +
> + res=ldap_search_s(conf->ld,
> + conf->base,
> + scope,
> + filter,
> + NULL,
> + 0,
> + &msg);
> + if ((res!=LDAP_SUCCESS) || !msg)
> + {
> + ldap_unbind(conf->ld);
> + return FALSE;
> + }
> +
> + entry=ldap_first_entry(conf->ld,msg);
> +
> + if (entry)
> + {
> + user=ldap_get_dn(conf->ld, entry);
> + /*
> + return svn_error_create(SVN_ERR_AUTHN_FAILED, NULL,
> + "LDAP authentication failed!");
> + */
> + }
> + else
> + {
> + user=NULL;
> + }
> +
> + ldap_msgfree(msg);
> +
> + if (user != NULL)
> + {
> + if (!conf->userpassword_attr)
> + {
> + ldap_unbind(conf->ld);
> + conf->ld = ldap_open_and_bind(conf->host,
> + conf->port,
> + user,
> + password);
> +
> + if (conf->ld==NULL)
> + {
> + user=NULL;
> + }
> + else
> + {
> + ldap_unbind(conf->ld);
> + }
> + }
> + else
> + {
> + if (ldap_compare_s(conf->ld,
> + user,
> + conf->userpassword_attr,
> + password)!=LDAP_COMPARE_TRUE)
> + {
> + user = NULL;
> + }
> + ldap_unbind(conf->ld);
> + }
> + }
> +
> + if (user == NULL)
> + {
> + return FALSE;
> + }
> + else
> + {
> + return TRUE;
> + }
> +}
> +
> +/* If we can, make the nonce with random bytes. If we can't... well,
> +* it just has to be different each time. The current time isn't
> +* absolutely guaranteed to be different for each connection, but it
> +* should prevent replay attacks in practice. */
> +static apr_status_t make_nonce(apr_uint64_t *nonce)
> +{
> +#if APR_HAS_RANDOM
> + return apr_generate_random_bytes((unsigned char *) nonce,
> sizeof(*nonce));
> +#else
> + *nonce = apr_time_now();
> + return APR_SUCCESS;
> +#endif
> +}
> +
> +/* Fail the authentication, from the server's perspective. */
> +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);
> +}
> +
> +svn_error_t *svn_ra_svn_basic_server(svn_ra_svn_conn_t *conn, apr_pool_t
> *pool,
> +
> svn_config_t *cfg, const char **user,
> +
> svn_boolean_t *success)
> +{
> + apr_status_t status;
> + apr_uint64_t nonce;
> + char hostbuf[APRMAXHOSTLEN + 1];
> +
> + const char *challenge;
> + svn_ra_svn_item_t *item;
> + svn_string_t *resp;
> +
> + apr_size_t resp_len;
> + apr_size_t decoded_len;
> + char* decoded_resp;
> + char* in_user;
> + char* password;
> + ldap_config_struct* conf = NULL;
> +
> + int user_str_len;
> + int password_str_len;
> + unsigned int i;
> + *success = FALSE;
> +
> + /* Send a challenge. */
> +
> + status = make_nonce(&nonce);
> + if (!status)
> + status = apr_gethostname(hostbuf, sizeof(hostbuf), pool);
> + if (status)
> + return fail(conn, pool, "Internal server error in
> authentication");
> + challenge = apr_psprintf(pool,
> + "<%" APR_UINT64_T_FMT ".%" APR_TIME_T_FMT "@%s>",
> + nonce, apr_time_now(), hostbuf);
> + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(c)", "step",
> challenge));
> +
> + /* Read the client's response and decode it into *user and password.
> */
> +
> + SVN_ERR(svn_ra_svn_read_item(conn, pool, &item));
> + if (item->kind != SVN_RA_SVN_STRING)
> + return SVN_NO_ERROR;
> +
> + resp = item->u.string;
> + resp_len = strlen(resp->data);
> + decoded_len = apr_base64_decode_len(resp->data);
> + decoded_resp = apr_palloc(pool, decoded_len + 1);
> +
> + apr_base64_decode(decoded_resp, resp->data);
> +
> + password = apr_palloc(pool, decoded_len + 1);
> + memset(password, 0, decoded_len + 1);
> +
> + in_user = apr_palloc(pool, decoded_len + 1);
> + memset(in_user, 0, decoded_len + 1);
> +
> + user_str_len = 0;
> + for (i = 0; i < decoded_len && decoded_resp[i] != ':';
> i++)user_str_len++;
> + apr_cpystrn(in_user, decoded_resp, user_str_len + 1);
> + password_str_len = decoded_len - user_str_len - 1;
> + apr_cpystrn(password, decoded_resp + user_str_len + 1,
> password_str_len);
> +
> + /* LDAP authentication */
> + conf = get_ldap_config(&cfg, pool);
> + *success = ldap_authentication(conf, in_user, password, pool);
> + if (*success)
> + {
> + *user = in_user;
> + svn_ra_svn_write_tuple(conn, pool, "w()", "success");
> + return SVN_NO_ERROR;
> + }
> + else
> + {
> + svn_ra_svn_write_tuple(conn, pool, "w()", "failure");
> + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
> + _("Authentication failed!"));
> + }
> +}
> +
> +svn_error_t *svn_ra_svn_basic_client(svn_ra_svn_conn_t *conn, apr_pool_t
> *pool,
> +
> const char *user, const char *password,
> +
> const char **message)
> +{
> + const char *status, *str;
> + char *reply;
> + char* tmp_str;
> + int encoded_len;
> +
> + /* Read the server challenge. */
> + SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "w(?c)", &status, &str));
> + if (strcmp(status, "failure") == 0 && str)
> + {
> + *message = str;
> + return SVN_NO_ERROR;
> + }
> + else if (strcmp(status, "step") != 0 || !str)
> + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
> + _("Unexpected server response to authentication"));
> +
> + /* Write our response. */
> +
> + if (user && password)
> + {
> + int user_len = strlen(user);
> + int password_len = strlen(password);
> + int all_len = user_len + password_len;
> +
> + tmp_str = apr_palloc(pool, all_len + 1);
> + memset(tmp_str, 0, all_len + 1);
> +
> + sprintf(tmp_str, "%s:%s", user, password);
> +
> + encoded_len = apr_base64_encode_len(all_len);
> + reply = apr_palloc(pool, encoded_len + 1);
> + memset(reply, 0, encoded_len + 1);
> + apr_base64_encode(reply, tmp_str, all_len + 1);
> + }
> + SVN_ERR(svn_ra_svn_write_cstring(conn, pool, reply));
> +
> + /* Read the success or failure response from the server. */
> + SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "w(?c)", &status, &str));
> + if (strcmp(status, "failure") == 0 && str)
> + {
> + *message = str;
> + return SVN_NO_ERROR;
> + }
> + else if (strcmp(status, "success") != 0 || str)
> + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
> + _("Unexpected server response to authentication"));
> +
> + *message = NULL;
> + return SVN_NO_ERROR;
> +}
> +
> \ No newline at end of file
> Index: libsvn_ra_svn/internal_auth.c
> ===================================================================
> --- libsvn_ra_svn/internal_auth.c (revision 28868)
> +++ libsvn_ra_svn/internal_auth.c (working copy)
> @@ -73,8 +73,31 @@
>
> realmstring = apr_psprintf(pool, "%s %s", sess->realm_prefix, realm);
>
> - if (sess->is_tunneled && find_mech(mechlist, "EXTERNAL"))
> + /* new mech BASIC*/
> + if (find_mech(mechlist, "BASIC"))
> {
> + SVN_ERR(svn_auth_first_credentials(&creds, &iterstate,
> + SVN_AUTH_CRED_SIMPLE, realmstring,
> + sess->callbacks->auth_baton, pool));
> + if (!creds)
> + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
> + _("Can't get password"));
> +
> + while (creds)
> + {
> + user = ((svn_auth_cred_simple_t *) creds)->username;
> + password = ((svn_auth_cred_simple_t *) creds)->password;
> + SVN_ERR(svn_ra_svn__auth_response(conn, pool, "BASIC",
> NULL));
> + /*SVN_ERR(svn_ra_svn__cram_client(conn, pool, user,
> password, &msg));*/
> + SVN_ERR(svn_ra_svn_basic_client(conn, pool, user,
> password, &msg));
> + if (!msg)
> + break;
> + SVN_ERR(svn_auth_next_credentials(&creds, iterstate,
> pool));
> + }
> + return SVN_NO_ERROR;
> + }
> + else if (sess->is_tunneled && find_mech(mechlist, "EXTERNAL"))
> + {
> /* Ask the server to use the tunnel connection environment (on
> * Unix, that means uid) to determine the authentication name. */
> SVN_ERR(svn_ra_svn__auth_response(conn, pool, "EXTERNAL", ""));
> Index: libsvn_ra_svn/ra_svn.h
> ===================================================================
> --- libsvn_ra_svn/ra_svn.h (revision 28868)
> +++ libsvn_ra_svn/ra_svn.h (working copy)
> @@ -116,6 +116,11 @@
> const char *user, const char
> *password,
> const char **message);
>
> +/* Basic client implementation*/
> +svn_error_t *svn_ra_svn_basic_client(svn_ra_svn_conn_t *conn, apr_pool_t
> *pool,
> +
> const char *user, const char *password,
> +
> const char **message);
> +
> /* Return an error chain based on @a params (which contains a
> * command response indicating failure). The error chain will be
> * in the same order as the errors indicated in @a params. Use
> Index: svnserve/serve.c
> ===================================================================
> --- svnserve/serve.c (revision 28868)
> +++ svnserve/serve.c (working copy)
> @@ -255,6 +255,10 @@
> SVN_ERR(svn_ra_svn_write_word(conn, pool, "EXTERNAL"));
> if (b->pwdb && get_access(b, AUTHENTICATED) >= required)
> SVN_ERR(svn_ra_svn_write_word(conn, pool, "CRAM-MD5"));
> + /* new mech BASIC */
> + if (get_access(b, AUTHENTICATED) >= required)
> + SVN_ERR(svn_ra_svn_write_word(conn, pool, "BASIC"));
> +
> return SVN_NO_ERROR;
> }
>
> @@ -351,6 +355,17 @@
> return SVN_NO_ERROR;
> }
>
> + /*new mech BASIC*/
> + if (get_access(b, AUTHENTICATED) >= required
> + && strcmp(mech, "BASIC") == 0)
> + {
> + SVN_ERR(svn_ra_svn_basic_server(conn, pool, b->cfg, &user,
> success));
> + b->user = apr_pstrdup(b->pool, user);
> + return SVN_NO_ERROR;
> + }
> +
> +
> +
> return svn_ra_svn_write_tuple(conn, pool, "w(c)", "failure",
> "Must authenticate with listed mechanism");
> }
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscribe_at_subversion.tigris.org
> For additional commands, e-mail: dev-help_at_subversion.tigris.org
>

-- 
David Glasser | glasser@davidglasser.net | http://www.davidglasser.net/
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe_at_subversion.tigris.org
For additional commands, e-mail: dev-help_at_subversion.tigris.org
Received on 2008-01-31 21:40:42 CET

This is an archived mail posted to the Subversion Dev mailing list.