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"
22 #define APR_WANT_STRFUNC
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"
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
);
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
);
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)
75 *out_len
+= realm_len
+ 1;
77 /* First, check that the output buffer is large enough. */
78 if (*out_len
> out_max
)
81 /* Copy the username part. */
82 strncpy(out
, in
, inlen
);
84 /* If necessary, copy the realm part. */
88 strncpy(&out
[inlen
+1], user_realm
, realm_len
);
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
)
105 status
= svn_ra_svn__sasl_common_init(pool
);
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"));
123 svn_error_t
*cyrus_init(apr_pool_t
*pool
)
125 SVN_ERR(svn_atomic__init_once(&svn_ra_svn__sasl_status
, initialize
, pool
));
129 /* Tell the client the authentication failed. This is only used during
130 the authentication exchange (i.e. inside try_auth()). */
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. */
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
;
150 /* Used if we run into a SASL error outside try_auth(). */
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
,
164 svn_boolean_t
*success
)
166 const char *out
, *mech
;
167 const svn_string_t
*arg
= NULL
, *in
;
170 svn_boolean_t use_base64
;
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
);
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. */
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
)
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. */
219 arg
= svn_base64_encode_string(svn_string_ncreate(out
, outlen
, pool
),
225 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w(?s)", "success", arg
));
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
);
237 svn_error_t
*cyrus_auth_request(svn_ra_svn_conn_t
*conn
,
240 enum access_type required
,
241 svn_boolean_t needs_username
)
243 sasl_conn_t
*sasl_ctx
;
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
,
255 apr_err
= apr_gethostname(hostname
, sizeof(hostname
), pool
);
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
,
267 localaddrport
, remoteaddrport
,
268 NULL
, SASL_SUCCESS_DATA
,
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
;
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
,
297 secprops
.min_ssf
= atoi(val
);
299 svn_config_get(b
->cfg
, &val
,
300 SVN_CONFIG_SECTION_SASL
,
301 SVN_CONFIG_OPTION_MAX_SSF
,
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. */
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
);
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
));
344 svn_pool_destroy(subpool
);
346 SVN_ERR(svn_ra_svn__enable_sasl_encryption(conn
, sasl_ctx
, pool
));
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
);
365 err
= svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED
, NULL
,
366 _("Couldn't obtain the authenticated"
368 SVN_ERR(write_failure(conn
, pool
, &err
));
369 return svn_ra_svn_flush(conn
, pool
);
376 #endif /* SVN_HAVE_SASL */