Mark many merge tests as skip-against-old-server.
[svn.git] / subversion / svnserve / cyrus_auth.c
blobb1d75b9e0a8e6df5c6d2e15937187564bb8948f8
1 /*
2 * sasl_auth.c : Functions for SASL-based authentication
4 * ====================================================================
5 * Copyright (c) 2006-2007 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 * ====================================================================
19 #include "svn_private_config.h"
20 #ifdef SVN_HAVE_SASL
22 #define APR_WANT_STRFUNC
23 #include <apr_want.h>
24 #include <apr_general.h>
25 #include <apr_strings.h>
27 #include "svn_types.h"
28 #include "svn_string.h"
29 #include "svn_pools.h"
30 #include "svn_error.h"
31 #include "svn_ra_svn.h"
32 #include "svn_base64.h"
34 #include "private/svn_atomic.h"
35 #include "private/ra_svn_sasl.h"
37 #include "server.h"
39 /* SASL calls this function before doing anything with a username, which gives
40 us an opportunity to do some sanity-checking. If the username contains
41 an '@', SASL interprets the part following the '@' as the name of the
42 authentication realm, and worst of all, this realm overrides the one that
43 we pass to sasl_server_new(). If we didn't check this, a user that could
44 successfully authenticate in one realm would be able to authenticate
45 in any other realm, simply by appending '@realm' to his username.
47 Note that the value returned in *OUT does not need to be
48 '\0'-terminated; we just need to set *OUT_LEN correctly.
50 static int canonicalize_username(sasl_conn_t *conn,
51 void *context, /* not used */
52 const char *in, /* the username */
53 unsigned inlen, /* its length */
54 unsigned flags, /* not used */
55 const char *user_realm,
56 char *out, /* the output buffer */
57 unsigned out_max, unsigned *out_len)
59 int realm_len = strlen(user_realm);
60 char *pos;
62 *out_len = inlen;
64 /* If the username contains an '@', the part after the '@' is the realm
65 that the user wants to authenticate in. */
66 pos = memchr(in, '@', inlen);
67 if (pos)
69 /* The only valid realm is user_realm (i.e. the repository's realm).
70 If the user gave us another realm, complain. */
71 if (strncmp(pos+1, user_realm, inlen-(pos-in+1)) != 0)
72 return SASL_BADPROT;
74 else
75 *out_len += realm_len + 1;
77 /* First, check that the output buffer is large enough. */
78 if (*out_len > out_max)
79 return SASL_BADPROT;
81 /* Copy the username part. */
82 strncpy(out, in, inlen);
84 /* If necessary, copy the realm part. */
85 if (!pos)
87 out[inlen] = '@';
88 strncpy(&out[inlen+1], user_realm, realm_len);
91 return SASL_OK;
94 static sasl_callback_t callbacks[] =
96 { SASL_CB_CANON_USER, canonicalize_username, NULL },
97 { SASL_CB_LIST_END, NULL, NULL }
100 static svn_error_t *initialize(apr_pool_t *pool)
102 int result;
103 apr_status_t status;
105 status = svn_ra_svn__sasl_common_init(pool);
106 if (status)
107 return svn_error_wrap_apr(status,
108 _("Could not initialize the SASL library"));
110 /* The second parameter tells SASL to look for a configuration file
111 named subversion.conf. */
112 result = sasl_server_init(callbacks, SVN_RA_SVN_SASL_NAME);
113 if (result != SASL_OK)
115 svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
116 sasl_errstring(result, NULL, NULL));
117 return svn_error_quick_wrap(err,
118 _("Could not initialize the SASL library"));
120 return SVN_NO_ERROR;
123 svn_error_t *cyrus_init(apr_pool_t *pool)
125 SVN_ERR(svn_atomic__init_once(&svn_ra_svn__sasl_status, initialize, pool));
126 return SVN_NO_ERROR;
129 /* Tell the client the authentication failed. This is only used during
130 the authentication exchange (i.e. inside try_auth()). */
131 static svn_error_t *
132 fail_auth(svn_ra_svn_conn_t *conn, apr_pool_t *pool, sasl_conn_t *sasl_ctx)
134 const char *msg = sasl_errdetail(sasl_ctx);
135 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(c)", "failure", msg));
136 return svn_ra_svn_flush(conn, pool);
139 /* Like svn_ra_svn_write_cmd_failure, but also clears the given error
140 and sets it to SVN_NO_ERROR. */
141 static svn_error_t *
142 write_failure(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_error_t **err_p)
144 svn_error_t *write_err = svn_ra_svn_write_cmd_failure(conn, pool, *err_p);
145 svn_error_clear(*err_p);
146 *err_p = SVN_NO_ERROR;
147 return write_err;
150 /* Used if we run into a SASL error outside try_auth(). */
151 static svn_error_t *
152 fail_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool, sasl_conn_t *sasl_ctx)
154 svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
155 sasl_errdetail(sasl_ctx));
156 SVN_ERR(write_failure(conn, pool, &err));
157 return svn_ra_svn_flush(conn, pool);
160 static svn_error_t *try_auth(svn_ra_svn_conn_t *conn,
161 sasl_conn_t *sasl_ctx,
162 apr_pool_t *pool,
163 server_baton_t *b,
164 svn_boolean_t *success)
166 const char *out, *mech;
167 const svn_string_t *arg = NULL, *in;
168 unsigned int outlen;
169 int result;
170 svn_boolean_t use_base64;
172 *success = FALSE;
174 /* Read the client's chosen mech and the initial token. */
175 SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "w(?s)", &mech, &in));
177 if (strcmp(mech, "EXTERNAL") == 0 && !in)
178 in = svn_string_create(b->tunnel_user, pool);
179 else if (in)
180 in = svn_base64_decode_string(in, pool);
182 /* For CRAM-MD5, we don't base64-encode stuff. */
183 use_base64 = (strcmp(mech, "CRAM-MD5") != 0);
185 result = sasl_server_start(sasl_ctx, mech,
186 in ? in->data : NULL,
187 in ? in->len : 0, &out, &outlen);
189 if (result != SASL_OK && result != SASL_CONTINUE)
190 return fail_auth(conn, pool, sasl_ctx);
192 while (result == SASL_CONTINUE)
194 svn_ra_svn_item_t *item;
196 arg = svn_string_ncreate(out, outlen, pool);
197 /* Encode what we send to the client. */
198 if (use_base64)
199 arg = svn_base64_encode_string(arg, pool);
201 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(s)", "step", arg));
203 /* Read and decode the client response. */
204 SVN_ERR(svn_ra_svn_read_item(conn, pool, &item));
205 if (item->kind != SVN_RA_SVN_STRING)
206 return SVN_NO_ERROR;
208 in = item->u.string;
209 if (use_base64)
210 in = svn_base64_decode_string(in, pool);
211 result = sasl_server_step(sasl_ctx, in->data, in->len, &out, &outlen);
214 if (result != SASL_OK)
215 return fail_auth(conn, pool, sasl_ctx);
217 /* Send our last response, if necessary. */
218 if (outlen)
219 arg = svn_base64_encode_string(svn_string_ncreate(out, outlen, pool),
220 pool);
221 else
222 arg = NULL;
224 *success = TRUE;
225 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(?s)", "success", arg));
227 return SVN_NO_ERROR;
230 static apr_status_t sasl_dispose_cb(void *data)
232 sasl_conn_t *sasl_ctx = (sasl_conn_t*) data;
233 sasl_dispose(&sasl_ctx);
234 return APR_SUCCESS;
237 svn_error_t *cyrus_auth_request(svn_ra_svn_conn_t *conn,
238 apr_pool_t *pool,
239 server_baton_t *b,
240 enum access_type required,
241 svn_boolean_t needs_username)
243 sasl_conn_t *sasl_ctx;
244 apr_pool_t *subpool;
245 apr_status_t apr_err;
246 const char *localaddrport = NULL, *remoteaddrport = NULL;
247 const char *mechlist, *val;
248 char hostname[APRMAXHOSTLEN + 1];
249 sasl_security_properties_t secprops;
250 svn_boolean_t success, no_anonymous;
251 int mech_count, result = SASL_OK;
253 SVN_ERR(svn_ra_svn__get_addresses(&localaddrport, &remoteaddrport,
254 conn, pool));
255 apr_err = apr_gethostname(hostname, sizeof(hostname), pool);
256 if (apr_err)
258 svn_error_t *err = svn_error_wrap_apr(apr_err, _("Can't get hostname"));
259 SVN_ERR(write_failure(conn, pool, &err));
260 return svn_ra_svn_flush(conn, pool);
263 /* Create a SASL context. SASL_SUCCESS_DATA tells SASL that the protocol
264 supports sending data along with the final "success" message. */
265 result = sasl_server_new(SVN_RA_SVN_SASL_NAME,
266 hostname, b->realm,
267 localaddrport, remoteaddrport,
268 NULL, SASL_SUCCESS_DATA,
269 &sasl_ctx);
270 if (result != SASL_OK)
272 svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
273 sasl_errstring(result, NULL, NULL));
274 SVN_ERR(write_failure(conn, pool, &err));
275 return svn_ra_svn_flush(conn, pool);
278 /* Make sure the context is always destroyed. */
279 apr_pool_cleanup_register(b->pool, sasl_ctx, sasl_dispose_cb,
280 apr_pool_cleanup_null);
282 /* Initialize security properties. */
283 svn_ra_svn__default_secprops(&secprops);
285 /* Don't allow PLAIN or LOGIN, since we don't support TLS yet. */
286 secprops.security_flags = SASL_SEC_NOPLAINTEXT;
288 /* Don't allow ANONYMOUS if a username is required. */
289 no_anonymous = needs_username || get_access(b, UNAUTHENTICATED) < required;
290 if (no_anonymous)
291 secprops.security_flags |= SASL_SEC_NOANONYMOUS;
293 svn_config_get(b->cfg, &val,
294 SVN_CONFIG_SECTION_SASL,
295 SVN_CONFIG_OPTION_MIN_SSF,
296 "0");
297 secprops.min_ssf = atoi(val);
299 svn_config_get(b->cfg, &val,
300 SVN_CONFIG_SECTION_SASL,
301 SVN_CONFIG_OPTION_MAX_SSF,
302 "256");
303 secprops.max_ssf = atoi(val);
305 /* Set security properties. */
306 result = sasl_setprop(sasl_ctx, SASL_SEC_PROPS, &secprops);
307 if (result != SASL_OK)
308 return fail_cmd(conn, pool, sasl_ctx);
310 /* SASL needs to know if we are externally authenticated. */
311 if (b->tunnel_user)
312 result = sasl_setprop(sasl_ctx, SASL_AUTH_EXTERNAL, b->tunnel_user);
313 if (result != SASL_OK)
314 return fail_cmd(conn, pool, sasl_ctx);
316 /* Get the list of mechanisms. */
317 result = sasl_listmech(sasl_ctx, NULL, NULL, " ", NULL,
318 &mechlist, NULL, &mech_count);
320 if (result != SASL_OK)
321 return fail_cmd(conn, pool, sasl_ctx);
323 if (mech_count == 0)
325 svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
326 _("Could not obtain the list"
327 " of SASL mechanisms"));
328 SVN_ERR(write_failure(conn, pool, &err));
329 return svn_ra_svn_flush(conn, pool);
332 /* Send the list of mechanisms and the realm to the client. */
333 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "(w)c",
334 mechlist, b->realm));
336 /* The main authentication loop. */
337 subpool = svn_pool_create(pool);
340 svn_pool_clear(subpool);
341 SVN_ERR(try_auth(conn, sasl_ctx, subpool, b, &success));
343 while (!success);
344 svn_pool_destroy(subpool);
346 SVN_ERR(svn_ra_svn__enable_sasl_encryption(conn, sasl_ctx, pool));
348 if (no_anonymous)
350 char *p;
351 const void *user;
353 /* Get the authenticated username. */
354 result = sasl_getprop(sasl_ctx, SASL_USERNAME, &user);
356 if (result != SASL_OK)
357 return fail_cmd(conn, pool, sasl_ctx);
359 if ((p = strchr(user, '@')) != NULL)
360 /* Drop the realm part. */
361 b->user = apr_pstrndup(b->pool, user, p - (char *)user);
362 else
364 svn_error_t *err;
365 err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
366 _("Couldn't obtain the authenticated"
367 " username"));
368 SVN_ERR(write_failure(conn, pool, &err));
369 return svn_ra_svn_flush(conn, pool);
373 return SVN_NO_ERROR;
376 #endif /* SVN_HAVE_SASL */