2 * sasl_auth.c : Functions for SASL-based authentication
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
24 #include "svn_private_config.h"
27 #define APR_WANT_STRFUNC
29 #include <apr_general.h>
30 #include <apr_strings.h>
32 #include "svn_types.h"
33 #include "svn_string.h"
34 #include "svn_pools.h"
35 #include "svn_error.h"
36 #include "svn_ra_svn.h"
37 #include "svn_base64.h"
39 #include "private/svn_atomic.h"
40 #include "private/ra_svn_sasl.h"
41 #include "private/svn_ra_svn_private.h"
45 /* SASL calls this function before doing anything with a username, which gives
46 us an opportunity to do some sanity-checking. If the username contains
47 an '@', SASL interprets the part following the '@' as the name of the
48 authentication realm, and worst of all, this realm overrides the one that
49 we pass to sasl_server_new(). If we didn't check this, a user that could
50 successfully authenticate in one realm would be able to authenticate
51 in any other realm, simply by appending '@realm' to his username.
53 Note that the value returned in *OUT does not need to be
54 '\0'-terminated; we just need to set *OUT_LEN correctly.
56 static int canonicalize_username(sasl_conn_t *conn,
57 void *context, /* not used */
58 const char *in, /* the username */
59 unsigned inlen, /* its length */
60 unsigned flags, /* not used */
61 const char *user_realm,
62 char *out, /* the output buffer */
63 unsigned out_max, unsigned *out_len)
65 size_t realm_len = strlen(user_realm);
70 /* If the username contains an '@', the part after the '@' is the realm
71 that the user wants to authenticate in. */
72 pos = memchr(in, '@', inlen);
75 /* The only valid realm is user_realm (i.e. the repository's realm).
76 If the user gave us another realm, complain. */
77 if (strncmp(pos+1, user_realm, inlen-(pos-in+1)) != 0)
81 *out_len += realm_len + 1;
83 /* First, check that the output buffer is large enough. */
84 if (*out_len > out_max)
87 /* Copy the username part. */
88 strncpy(out, in, inlen);
90 /* If necessary, copy the realm part. */
94 strncpy(&out[inlen+1], user_realm, realm_len);
100 static sasl_callback_t callbacks[] =
102 { SASL_CB_CANON_USER, (int (*)(void))canonicalize_username, NULL },
103 { SASL_CB_LIST_END, NULL, NULL }
106 static svn_error_t *initialize(void *baton, apr_pool_t *pool)
109 SVN_ERR(svn_ra_svn__sasl_common_init(pool));
111 /* The second parameter tells SASL to look for a configuration file
112 named subversion.conf. */
113 result = sasl_server_init(callbacks, SVN_RA_SVN_SASL_NAME);
114 if (result != SASL_OK)
116 svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
117 sasl_errstring(result, NULL, NULL));
118 return svn_error_quick_wrap(err,
119 _("Could not initialize the SASL library"));
124 svn_error_t *cyrus_init(apr_pool_t *pool)
126 SVN_ERR(svn_atomic__init_once(&svn_ra_svn__sasl_status,
127 initialize, NULL, pool));
131 /* Tell the client the authentication failed. This is only used during
132 the authentication exchange (i.e. inside try_auth()). */
134 fail_auth(svn_ra_svn_conn_t *conn, apr_pool_t *pool, sasl_conn_t *sasl_ctx)
136 const char *msg = sasl_errdetail(sasl_ctx);
137 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c)", "failure", msg));
138 return svn_ra_svn__flush(conn, pool);
141 /* Like svn_ra_svn_write_cmd_failure, but also clears the given error
142 and sets it to SVN_NO_ERROR. */
144 write_failure(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_error_t **err_p)
146 svn_error_t *write_err = svn_ra_svn__write_cmd_failure(conn, pool, *err_p);
147 svn_error_clear(*err_p);
148 *err_p = SVN_NO_ERROR;
152 /* Used if we run into a SASL error outside try_auth(). */
154 fail_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool, sasl_conn_t *sasl_ctx)
156 svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
157 sasl_errdetail(sasl_ctx));
158 SVN_ERR(write_failure(conn, pool, &err));
159 return svn_ra_svn__flush(conn, pool);
162 static svn_error_t *try_auth(svn_ra_svn_conn_t *conn,
163 sasl_conn_t *sasl_ctx,
166 svn_boolean_t *success)
168 const char *out, *mech;
169 const svn_string_t *arg = NULL, *in;
172 svn_boolean_t use_base64;
176 /* Read the client's chosen mech and the initial token. */
177 SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "w(?s)", &mech, &in));
179 if (strcmp(mech, "EXTERNAL") == 0 && !in)
180 in = svn_string_create(b->tunnel_user, pool);
182 in = svn_base64_decode_string(in, pool);
184 /* For CRAM-MD5, we don't base64-encode stuff. */
185 use_base64 = (strcmp(mech, "CRAM-MD5") != 0);
187 result = sasl_server_start(sasl_ctx, mech,
188 in ? in->data : NULL,
189 in ? in->len : 0, &out, &outlen);
191 if (result != SASL_OK && result != SASL_CONTINUE)
192 return fail_auth(conn, pool, sasl_ctx);
194 while (result == SASL_CONTINUE)
196 svn_ra_svn_item_t *item;
198 arg = svn_string_ncreate(out, outlen, pool);
199 /* Encode what we send to the client. */
201 arg = svn_base64_encode_string2(arg, TRUE, pool);
203 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(s)", "step", arg));
205 /* Read and decode the client response. */
206 SVN_ERR(svn_ra_svn__read_item(conn, pool, &item));
207 if (item->kind != SVN_RA_SVN_STRING)
212 in = svn_base64_decode_string(in, pool);
213 result = sasl_server_step(sasl_ctx, in->data, in->len, &out, &outlen);
216 if (result != SASL_OK)
217 return fail_auth(conn, pool, sasl_ctx);
219 /* Send our last response, if necessary. */
221 arg = svn_base64_encode_string2(svn_string_ncreate(out, outlen, pool), TRUE,
227 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(?s)", "success", arg));
232 static apr_status_t sasl_dispose_cb(void *data)
234 sasl_conn_t *sasl_ctx = (sasl_conn_t*) data;
235 sasl_dispose(&sasl_ctx);
239 svn_error_t *cyrus_auth_request(svn_ra_svn_conn_t *conn,
242 enum access_type required,
243 svn_boolean_t needs_username)
245 sasl_conn_t *sasl_ctx;
247 apr_status_t apr_err;
248 const char *localaddrport = NULL, *remoteaddrport = NULL;
249 const char *mechlist, *val;
250 char hostname[APRMAXHOSTLEN + 1];
251 sasl_security_properties_t secprops;
252 svn_boolean_t success, no_anonymous;
253 int mech_count, result = SASL_OK;
255 SVN_ERR(svn_ra_svn__get_addresses(&localaddrport, &remoteaddrport,
257 apr_err = apr_gethostname(hostname, sizeof(hostname), pool);
260 svn_error_t *err = svn_error_wrap_apr(apr_err, _("Can't get hostname"));
261 SVN_ERR(write_failure(conn, pool, &err));
262 return svn_ra_svn__flush(conn, pool);
265 /* Create a SASL context. SASL_SUCCESS_DATA tells SASL that the protocol
266 supports sending data along with the final "success" message. */
267 result = sasl_server_new(SVN_RA_SVN_SASL_NAME,
269 localaddrport, remoteaddrport,
270 NULL, SASL_SUCCESS_DATA,
272 if (result != SASL_OK)
274 svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
275 sasl_errstring(result, NULL, NULL));
276 SVN_ERR(write_failure(conn, pool, &err));
277 return svn_ra_svn__flush(conn, pool);
280 /* Make sure the context is always destroyed. */
281 apr_pool_cleanup_register(b->pool, sasl_ctx, sasl_dispose_cb,
282 apr_pool_cleanup_null);
284 /* Initialize security properties. */
285 svn_ra_svn__default_secprops(&secprops);
287 /* Don't allow ANONYMOUS if a username is required. */
288 no_anonymous = needs_username || get_access(b, UNAUTHENTICATED) < required;
290 secprops.security_flags |= SASL_SEC_NOANONYMOUS;
292 svn_config_get(b->cfg, &val,
293 SVN_CONFIG_SECTION_SASL,
294 SVN_CONFIG_OPTION_MIN_SSF,
296 SVN_ERR(svn_cstring_atoui(&secprops.min_ssf, val));
298 svn_config_get(b->cfg, &val,
299 SVN_CONFIG_SECTION_SASL,
300 SVN_CONFIG_OPTION_MAX_SSF,
302 SVN_ERR(svn_cstring_atoui(&secprops.max_ssf, val));
304 /* Set security properties. */
305 result = sasl_setprop(sasl_ctx, SASL_SEC_PROPS, &secprops);
306 if (result != SASL_OK)
307 return fail_cmd(conn, pool, sasl_ctx);
309 /* SASL needs to know if we are externally authenticated. */
311 result = sasl_setprop(sasl_ctx, SASL_AUTH_EXTERNAL, b->tunnel_user);
312 if (result != SASL_OK)
313 return fail_cmd(conn, pool, sasl_ctx);
315 /* Get the list of mechanisms. */
316 result = sasl_listmech(sasl_ctx, NULL, NULL, " ", NULL,
317 &mechlist, NULL, &mech_count);
319 if (result != SASL_OK)
320 return fail_cmd(conn, pool, sasl_ctx);
324 svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
325 _("Could not obtain the list"
326 " of SASL mechanisms"));
327 SVN_ERR(write_failure(conn, pool, &err));
328 return svn_ra_svn__flush(conn, pool);
331 /* Send the list of mechanisms and the realm to the client. */
332 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "(w)c",
333 mechlist, b->realm));
335 /* The main authentication loop. */
336 subpool = svn_pool_create(pool);
339 svn_pool_clear(subpool);
340 SVN_ERR(try_auth(conn, sasl_ctx, subpool, b, &success));
343 svn_pool_destroy(subpool);
345 SVN_ERR(svn_ra_svn__enable_sasl_encryption(conn, sasl_ctx, pool));
352 /* Get the authenticated username. */
353 result = sasl_getprop(sasl_ctx, SASL_USERNAME, &user);
355 if (result != SASL_OK)
356 return fail_cmd(conn, pool, sasl_ctx);
358 if ((p = strchr(user, '@')) != NULL)
360 /* Drop the realm part. */
361 b->user = apr_pstrndup(b->pool, user, p - (const char *)user);
366 err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
367 _("Couldn't obtain the authenticated"
369 SVN_ERR(write_failure(conn, pool, &err));
370 return svn_ra_svn__flush(conn, pool);
377 #endif /* SVN_HAVE_SASL */