summaryrefslogtreecommitdiffstats
path: root/subversion/svnserve/cyrus_auth.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/svnserve/cyrus_auth.c')
-rw-r--r--subversion/svnserve/cyrus_auth.c377
1 files changed, 377 insertions, 0 deletions
diff --git a/subversion/svnserve/cyrus_auth.c b/subversion/svnserve/cyrus_auth.c
new file mode 100644
index 0000000..2d75047
--- /dev/null
+++ b/subversion/svnserve/cyrus_auth.c
@@ -0,0 +1,377 @@
+/*
+ * sasl_auth.c : Functions for SASL-based authentication
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_private_config.h"
+#ifdef SVN_HAVE_SASL
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+#include <apr_general.h>
+#include <apr_strings.h>
+
+#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 "private/svn_atomic.h"
+#include "private/ra_svn_sasl.h"
+#include "private/svn_ra_svn_private.h"
+
+#include "server.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.
+
+ Note that the value returned in *OUT does not need to be
+ '\0'-terminated; we just need to set *OUT_LEN correctly.
+*/
+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)
+{
+ size_t 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, (int (*)(void))canonicalize_username, NULL },
+ { SASL_CB_LIST_END, NULL, NULL }
+};
+
+static svn_error_t *initialize(void *baton, apr_pool_t *pool)
+{
+ int result;
+ SVN_ERR(svn_ra_svn__sasl_common_init(pool));
+
+ /* The second parameter tells SASL to look for a configuration file
+ named subversion.conf. */
+ result = sasl_server_init(callbacks, SVN_RA_SVN_SASL_NAME);
+ if (result != SASL_OK)
+ {
+ svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ sasl_errstring(result, NULL, NULL));
+ return svn_error_quick_wrap(err,
+ _("Could not initialize the SASL library"));
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *cyrus_init(apr_pool_t *pool)
+{
+ SVN_ERR(svn_atomic__init_once(&svn_ra_svn__sasl_status,
+ initialize, NULL, pool));
+ return SVN_NO_ERROR;
+}
+
+/* Tell the client the authentication failed. This is only used during
+ the authentication exchange (i.e. inside try_auth()). */
+static svn_error_t *
+fail_auth(svn_ra_svn_conn_t *conn, apr_pool_t *pool, sasl_conn_t *sasl_ctx)
+{
+ const char *msg = sasl_errdetail(sasl_ctx);
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c)", "failure", msg));
+ return svn_ra_svn__flush(conn, pool);
+}
+
+/* Like svn_ra_svn_write_cmd_failure, but also clears the given error
+ and sets it to SVN_NO_ERROR. */
+static svn_error_t *
+write_failure(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_error_t **err_p)
+{
+ svn_error_t *write_err = svn_ra_svn__write_cmd_failure(conn, pool, *err_p);
+ svn_error_clear(*err_p);
+ *err_p = SVN_NO_ERROR;
+ return write_err;
+}
+
+/* Used if we run into a SASL error outside try_auth(). */
+static svn_error_t *
+fail_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool, sasl_conn_t *sasl_ctx)
+{
+ svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ sasl_errdetail(sasl_ctx));
+ SVN_ERR(write_failure(conn, pool, &err));
+ 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 token. */
+ 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_auth(conn, pool, 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_string2(arg, TRUE, 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_auth(conn, pool, sasl_ctx);
+
+ /* Send our last response, if necessary. */
+ if (outlen)
+ arg = svn_base64_encode_string2(svn_string_ncreate(out, outlen, pool), TRUE,
+ pool);
+ else
+ arg = NULL;
+
+ *success = TRUE;
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(?s)", "success", arg));
+
+ 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 *cyrus_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;
+ apr_pool_t *subpool;
+ apr_status_t apr_err;
+ const char *localaddrport = NULL, *remoteaddrport = NULL;
+ const char *mechlist, *val;
+ char hostname[APRMAXHOSTLEN + 1];
+ sasl_security_properties_t secprops;
+ svn_boolean_t success, no_anonymous;
+ int mech_count, result = SASL_OK;
+
+ SVN_ERR(svn_ra_svn__get_addresses(&localaddrport, &remoteaddrport,
+ conn, pool));
+ apr_err = apr_gethostname(hostname, sizeof(hostname), pool);
+ if (apr_err)
+ {
+ svn_error_t *err = svn_error_wrap_apr(apr_err, _("Can't get hostname"));
+ SVN_ERR(write_failure(conn, pool, &err));
+ return svn_ra_svn__flush(conn, pool);
+ }
+
+ /* 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_RA_SVN_SASL_NAME,
+ hostname, b->realm,
+ localaddrport, remoteaddrport,
+ NULL, SASL_SUCCESS_DATA,
+ &sasl_ctx);
+ if (result != SASL_OK)
+ {
+ svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ sasl_errstring(result, NULL, NULL));
+ SVN_ERR(write_failure(conn, pool, &err));
+ return svn_ra_svn__flush(conn, pool);
+ }
+
+ /* Make sure the context is always destroyed. */
+ apr_pool_cleanup_register(b->pool, sasl_ctx, sasl_dispose_cb,
+ apr_pool_cleanup_null);
+
+ /* Initialize security properties. */
+ svn_ra_svn__default_secprops(&secprops);
+
+ /* 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;
+
+ svn_config_get(b->cfg, &val,
+ SVN_CONFIG_SECTION_SASL,
+ SVN_CONFIG_OPTION_MIN_SSF,
+ "0");
+ SVN_ERR(svn_cstring_atoui(&secprops.min_ssf, val));
+
+ svn_config_get(b->cfg, &val,
+ SVN_CONFIG_SECTION_SASL,
+ SVN_CONFIG_OPTION_MAX_SSF,
+ "256");
+ SVN_ERR(svn_cstring_atoui(&secprops.max_ssf, val));
+
+ /* Set security properties. */
+ result = sasl_setprop(sasl_ctx, SASL_SEC_PROPS, &secprops);
+ if (result != SASL_OK)
+ return fail_cmd(conn, pool, 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 fail_cmd(conn, pool, sasl_ctx);
+
+ /* Get the list of mechanisms. */
+ result = sasl_listmech(sasl_ctx, NULL, NULL, " ", NULL,
+ &mechlist, NULL, &mech_count);
+
+ if (result != SASL_OK)
+ return fail_cmd(conn, pool, sasl_ctx);
+
+ if (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(write_failure(conn, pool, &err));
+ return svn_ra_svn__flush(conn, pool);
+ }
+
+ /* Send the list of mechanisms and the realm to the client. */
+ SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "(w)c",
+ mechlist, b->realm));
+
+ /* The main authentication loop. */
+ 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);
+
+ SVN_ERR(svn_ra_svn__enable_sasl_encryption(conn, sasl_ctx, pool));
+
+ if (no_anonymous)
+ {
+ char *p;
+ const void *user;
+
+ /* Get the authenticated username. */
+ result = sasl_getprop(sasl_ctx, SASL_USERNAME, &user);
+
+ if (result != SASL_OK)
+ return fail_cmd(conn, pool, sasl_ctx);
+
+ if ((p = strchr(user, '@')) != NULL)
+ {
+ /* Drop the realm part. */
+ b->user = apr_pstrndup(b->pool, user, p - (const char *)user);
+ }
+ else
+ {
+ svn_error_t *err;
+ err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ _("Couldn't obtain the authenticated"
+ " username"));
+ SVN_ERR(write_failure(conn, pool, &err));
+ return svn_ra_svn__flush(conn, pool);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+#endif /* SVN_HAVE_SASL */
OpenPOWER on IntegriCloud