Followup to r29625: fix getopt tests.
[svn.git] / subversion / svnserve / cyrus_auth.c
blobb71d2a81f698b6670ac62c47683ff6650d38d150
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. */
46 static int canonicalize_username(sasl_conn_t *conn,
47 void *context, /* not used */
48 const char *in, /* the username */
49 unsigned inlen, /* its length */
50 unsigned flags, /* not used */
51 const char *user_realm,
52 char *out, /* the output buffer */
53 unsigned out_max, unsigned *out_len)
55 int realm_len = strlen(user_realm);
56 char *pos;
58 *out_len = inlen;
60 /* If the username contains an '@', the part after the '@' is the realm
61 that the user wants to authenticate in. */
62 pos = memchr(in, '@', inlen);
63 if (pos)
65 /* The only valid realm is user_realm (i.e. the repository's realm).
66 If the user gave us another realm, complain. */
67 if (strncmp(pos+1, user_realm, inlen-(pos-in+1)) != 0)
68 return SASL_BADPROT;
70 else
71 *out_len += realm_len + 1;
73 /* First, check that the output buffer is large enough. */
74 if (*out_len > out_max)
75 return SASL_BADPROT;
77 /* Copy the username part. */
78 strncpy(out, in, inlen);
80 /* If necessary, copy the realm part. */
81 if (!pos)
83 out[inlen] = '@';
84 strncpy(&out[inlen+1], user_realm, realm_len);
87 return SASL_OK;
90 static sasl_callback_t callbacks[] =
92 { SASL_CB_CANON_USER, canonicalize_username, NULL },
93 { SASL_CB_LIST_END, NULL, NULL }
96 static svn_error_t *initialize(apr_pool_t *pool)
98 int result;
99 apr_status_t status;
101 status = svn_ra_svn__sasl_common_init(pool);
102 if (status)
103 return svn_error_wrap_apr(status,
104 _("Could not initialize the SASL library"));
106 /* The second parameter tells SASL to look for a configuration file
107 named subversion.conf. */
108 result = sasl_server_init(callbacks, SVN_RA_SVN_SASL_NAME);
109 if (result != SASL_OK)
111 svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
112 sasl_errstring(result, NULL, NULL));
113 return svn_error_quick_wrap(err,
114 _("Could not initialize the SASL library"));
116 return SVN_NO_ERROR;
119 svn_error_t *cyrus_init(apr_pool_t *pool)
121 SVN_ERR(svn_atomic__init_once(&svn_ra_svn__sasl_status, initialize, pool));
122 return SVN_NO_ERROR;
125 /* Tell the client the authentication failed. This is only used during
126 the authentication exchange (i.e. inside try_auth()). */
127 static svn_error_t *
128 fail_auth(svn_ra_svn_conn_t *conn, apr_pool_t *pool, sasl_conn_t *sasl_ctx)
130 const char *msg = sasl_errdetail(sasl_ctx);
131 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(c)", "failure", msg));
132 return svn_ra_svn_flush(conn, pool);
135 /* Like svn_ra_svn_write_cmd_failure, but also clears the given error
136 and sets it to SVN_NO_ERROR. */
137 static svn_error_t *
138 write_failure(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_error_t **err_p)
140 svn_error_t *write_err = svn_ra_svn_write_cmd_failure(conn, pool, *err_p);
141 svn_error_clear(*err_p);
142 *err_p = SVN_NO_ERROR;
143 return write_err;
146 /* Used if we run into a SASL error outside try_auth(). */
147 static svn_error_t *
148 fail_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool, sasl_conn_t *sasl_ctx)
150 svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
151 sasl_errdetail(sasl_ctx));
152 SVN_ERR(write_failure(conn, pool, &err));
153 return svn_ra_svn_flush(conn, pool);
156 static svn_error_t *try_auth(svn_ra_svn_conn_t *conn,
157 sasl_conn_t *sasl_ctx,
158 apr_pool_t *pool,
159 server_baton_t *b,
160 svn_boolean_t *success)
162 const char *out, *mech;
163 const svn_string_t *arg = NULL, *in;
164 unsigned int outlen;
165 int result;
166 svn_boolean_t use_base64;
168 *success = FALSE;
170 /* Read the client's chosen mech and the initial token. */
171 SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "w(?s)", &mech, &in));
173 if (strcmp(mech, "EXTERNAL") == 0 && !in)
174 in = svn_string_create(b->tunnel_user, pool);
175 else if (in)
176 in = svn_base64_decode_string(in, pool);
178 /* For CRAM-MD5, we don't base64-encode stuff. */
179 use_base64 = (strcmp(mech, "CRAM-MD5") != 0);
181 result = sasl_server_start(sasl_ctx, mech,
182 in ? in->data : NULL,
183 in ? in->len : 0, &out, &outlen);
185 if (result != SASL_OK && result != SASL_CONTINUE)
186 return fail_auth(conn, pool, sasl_ctx);
188 while (result == SASL_CONTINUE)
190 svn_ra_svn_item_t *item;
192 arg = svn_string_ncreate(out, outlen, pool);
193 /* Encode what we send to the client. */
194 if (use_base64)
195 arg = svn_base64_encode_string(arg, pool);
197 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(s)", "step", arg));
199 /* Read and decode the client response. */
200 SVN_ERR(svn_ra_svn_read_item(conn, pool, &item));
201 if (item->kind != SVN_RA_SVN_STRING)
202 return SVN_NO_ERROR;
204 in = item->u.string;
205 if (use_base64)
206 in = svn_base64_decode_string(in, pool);
207 result = sasl_server_step(sasl_ctx, in->data, in->len, &out, &outlen);
210 if (result != SASL_OK)
211 return fail_auth(conn, pool, sasl_ctx);
213 /* Send our last response, if necessary. */
214 if (outlen)
215 arg = svn_base64_encode_string(svn_string_ncreate(out, outlen, pool),
216 pool);
217 else
218 arg = NULL;
220 *success = TRUE;
221 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(?s)", "success", arg));
223 return SVN_NO_ERROR;
226 static apr_status_t sasl_dispose_cb(void *data)
228 sasl_conn_t *sasl_ctx = (sasl_conn_t*) data;
229 sasl_dispose(&sasl_ctx);
230 return APR_SUCCESS;
233 svn_error_t *cyrus_auth_request(svn_ra_svn_conn_t *conn,
234 apr_pool_t *pool,
235 server_baton_t *b,
236 enum access_type required,
237 svn_boolean_t needs_username)
239 sasl_conn_t *sasl_ctx;
240 apr_pool_t *subpool;
241 apr_status_t apr_err;
242 const char *localaddrport = NULL, *remoteaddrport = NULL;
243 const char *mechlist, *val;
244 char hostname[APRMAXHOSTLEN + 1];
245 sasl_security_properties_t secprops;
246 svn_boolean_t success, no_anonymous;
247 int mech_count, result = SASL_OK;
249 SVN_ERR(svn_ra_svn__get_addresses(&localaddrport, &remoteaddrport,
250 conn, pool));
251 apr_err = apr_gethostname(hostname, sizeof(hostname), pool);
252 if (apr_err)
254 svn_error_t *err = svn_error_wrap_apr(apr_err, _("Can't get hostname"));
255 SVN_ERR(write_failure(conn, pool, &err));
256 return svn_ra_svn_flush(conn, pool);
259 /* Create a SASL context. SASL_SUCCESS_DATA tells SASL that the protocol
260 supports sending data along with the final "success" message. */
261 result = sasl_server_new(SVN_RA_SVN_SASL_NAME,
262 hostname, b->realm,
263 localaddrport, remoteaddrport,
264 NULL, SASL_SUCCESS_DATA,
265 &sasl_ctx);
266 if (result != SASL_OK)
268 svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
269 sasl_errstring(result, NULL, NULL));
270 SVN_ERR(write_failure(conn, pool, &err));
271 return svn_ra_svn_flush(conn, pool);
274 /* Make sure the context is always destroyed. */
275 apr_pool_cleanup_register(b->pool, sasl_ctx, sasl_dispose_cb,
276 apr_pool_cleanup_null);
278 /* Initialize security properties. */
279 svn_ra_svn__default_secprops(&secprops);
281 /* Don't allow PLAIN or LOGIN, since we don't support TLS yet. */
282 secprops.security_flags = SASL_SEC_NOPLAINTEXT;
284 /* Don't allow ANONYMOUS if a username is required. */
285 no_anonymous = needs_username || get_access(b, UNAUTHENTICATED) < required;
286 if (no_anonymous)
287 secprops.security_flags |= SASL_SEC_NOANONYMOUS;
289 svn_config_get(b->cfg, &val,
290 SVN_CONFIG_SECTION_SASL,
291 SVN_CONFIG_OPTION_MIN_SSF,
292 "0");
293 secprops.min_ssf = atoi(val);
295 svn_config_get(b->cfg, &val,
296 SVN_CONFIG_SECTION_SASL,
297 SVN_CONFIG_OPTION_MAX_SSF,
298 "256");
299 secprops.max_ssf = atoi(val);
301 /* Set security properties. */
302 result = sasl_setprop(sasl_ctx, SASL_SEC_PROPS, &secprops);
303 if (result != SASL_OK)
304 return fail_cmd(conn, pool, sasl_ctx);
306 /* SASL needs to know if we are externally authenticated. */
307 if (b->tunnel_user)
308 result = sasl_setprop(sasl_ctx, SASL_AUTH_EXTERNAL, b->tunnel_user);
309 if (result != SASL_OK)
310 return fail_cmd(conn, pool, sasl_ctx);
312 /* Get the list of mechanisms. */
313 result = sasl_listmech(sasl_ctx, NULL, NULL, " ", NULL,
314 &mechlist, NULL, &mech_count);
316 if (result != SASL_OK)
317 return fail_cmd(conn, pool, sasl_ctx);
319 if (mech_count == 0)
321 svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
322 _("Could not obtain the list"
323 " of SASL mechanisms"));
324 SVN_ERR(write_failure(conn, pool, &err));
325 return svn_ra_svn_flush(conn, pool);
328 /* Send the list of mechanisms and the realm to the client. */
329 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "(w)c",
330 mechlist, b->realm));
332 /* The main authentication loop. */
333 subpool = svn_pool_create(pool);
336 svn_pool_clear(subpool);
337 SVN_ERR(try_auth(conn, sasl_ctx, subpool, b, &success));
339 while (!success);
340 svn_pool_destroy(subpool);
342 SVN_ERR(svn_ra_svn__enable_sasl_encryption(conn, sasl_ctx, pool));
344 if (no_anonymous)
346 char *p;
347 const void *user;
349 /* Get the authenticated username. */
350 result = sasl_getprop(sasl_ctx, SASL_USERNAME, &user);
352 if (result != SASL_OK)
353 return fail_cmd(conn, pool, sasl_ctx);
355 if ((p = strchr(user, '@')) != NULL)
356 /* Drop the realm part. */
357 b->user = apr_pstrndup(b->pool, user, p - (char *)user);
358 else
360 svn_error_t *err;
361 err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
362 _("Couldn't obtain the authenticated"
363 " username"));
364 SVN_ERR(write_failure(conn, pool, &err));
365 return svn_ra_svn_flush(conn, pool);
369 return SVN_NO_ERROR;
372 #endif /* SVN_HAVE_SASL */