1/* 2 * sasl_auth.c : Functions for SASL-based authentication 3 * 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 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 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 20 * under the License. 21 * ==================================================================== 22 */ 23 24#include "svn_private_config.h" 25#ifdef SVN_HAVE_SASL 26 27#define APR_WANT_STRFUNC 28#include <apr_want.h> 29#include <apr_general.h> 30#include <apr_strings.h> 31 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" 38 39#include "private/svn_atomic.h" 40#include "private/ra_svn_sasl.h" 41#include "private/svn_ra_svn_private.h" 42 43#include "server.h" 44 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. 52 53 Note that the value returned in *OUT does not need to be 54 '\0'-terminated; we just need to set *OUT_LEN correctly. 55*/ 56static 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) 64{ 65 size_t realm_len = strlen(user_realm); 66 char *pos; 67 68 *out_len = inlen; 69 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); 73 if (pos) 74 { 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 (realm_len != inlen-(pos-in+1)) 78 return SASL_BADPROT; 79 if (strncmp(pos+1, user_realm, inlen-(pos-in+1)) != 0) 80 return SASL_BADPROT; 81 } 82 else 83 *out_len += realm_len + 1; 84 85 /* First, check that the output buffer is large enough. */ 86 if (*out_len > out_max) 87 return SASL_BADPROT; 88 89 /* Copy the username part. */ 90 strncpy(out, in, inlen); 91 92 /* If necessary, copy the realm part. */ 93 if (!pos) 94 { 95 out[inlen] = '@'; 96 strncpy(&out[inlen+1], user_realm, realm_len); 97 } 98 99 return SASL_OK; 100} 101 102static sasl_callback_t callbacks[] = 103{ 104 { SASL_CB_CANON_USER, (int (*)(void))canonicalize_username, NULL }, 105 { SASL_CB_LIST_END, NULL, NULL } 106}; 107 108static svn_error_t *initialize(void *baton, apr_pool_t *pool) 109{ 110 int result; 111 SVN_ERR(svn_ra_svn__sasl_common_init(pool)); 112 113 /* The second parameter tells SASL to look for a configuration file 114 named subversion.conf. */ 115 result = svn_sasl__server_init(callbacks, SVN_RA_SVN_SASL_NAME); 116 if (result != SASL_OK) 117 { 118 svn_error_t *err = svn_error_create( 119 SVN_ERR_RA_NOT_AUTHORIZED, NULL, 120 svn_sasl__errstring(result, NULL, NULL)); 121 return svn_error_quick_wrap(err, 122 _("Could not initialize the SASL library")); 123 } 124 return SVN_NO_ERROR; 125} 126 127svn_error_t *cyrus_init(apr_pool_t *pool) 128{ 129 SVN_ERR(svn_atomic__init_once(&svn_ra_svn__sasl_status, 130 initialize, NULL, pool)); 131 return SVN_NO_ERROR; 132} 133 134/* Tell the client the authentication failed. This is only used during 135 the authentication exchange (i.e. inside try_auth()). */ 136static svn_error_t * 137fail_auth(svn_ra_svn_conn_t *conn, apr_pool_t *pool, sasl_conn_t *sasl_ctx) 138{ 139 const char *msg = svn_sasl__errdetail(sasl_ctx); 140 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c)", "failure", msg)); 141 return svn_ra_svn__flush(conn, pool); 142} 143 144/* Like svn_ra_svn_write_cmd_failure, but also clears the given error 145 and sets it to SVN_NO_ERROR. */ 146static svn_error_t * 147write_failure(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_error_t **err_p) 148{ 149 svn_error_t *write_err = svn_ra_svn__write_cmd_failure(conn, pool, *err_p); 150 svn_error_clear(*err_p); 151 *err_p = SVN_NO_ERROR; 152 return write_err; 153} 154 155/* Used if we run into a SASL error outside try_auth(). */ 156static svn_error_t * 157fail_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool, sasl_conn_t *sasl_ctx) 158{ 159 svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 160 svn_sasl__errdetail(sasl_ctx)); 161 SVN_ERR(write_failure(conn, pool, &err)); 162 return svn_ra_svn__flush(conn, pool); 163} 164 165static svn_error_t *try_auth(svn_ra_svn_conn_t *conn, 166 sasl_conn_t *sasl_ctx, 167 apr_pool_t *pool, 168 server_baton_t *b, 169 svn_boolean_t *success) 170{ 171 const char *out, *mech; 172 const svn_string_t *arg = NULL, *in; 173 unsigned int outlen; 174 int result; 175 svn_boolean_t use_base64; 176 177 *success = FALSE; 178 179 /* Read the client's chosen mech and the initial token. */ 180 SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "w(?s)", &mech, &in)); 181 182 if (strcmp(mech, "EXTERNAL") == 0 && !in) 183 in = svn_string_create(b->client_info->tunnel_user, pool); 184 else if (in) 185 in = svn_base64_decode_string(in, pool); 186 187 /* For CRAM-MD5, we don't base64-encode stuff. */ 188 use_base64 = (strcmp(mech, "CRAM-MD5") != 0); 189 190 /* sasl uses unsigned int for the length of strings, we use apr_size_t 191 * which may not be the same size. Deal with potential integer overflow */ 192 if (in && in->len > UINT_MAX) 193 return svn_error_createf(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 194 _("Initial token is too long")); 195 196 result = svn_sasl__server_start(sasl_ctx, mech, 197 in ? in->data : NULL, 198 in ? (unsigned int) in->len : 0, 199 &out, &outlen); 200 201 if (result != SASL_OK && result != SASL_CONTINUE) 202 return fail_auth(conn, pool, sasl_ctx); 203 204 while (result == SASL_CONTINUE) 205 { 206 svn_ra_svn__item_t *item; 207 208 arg = svn_string_ncreate(out, outlen, pool); 209 /* Encode what we send to the client. */ 210 if (use_base64) 211 arg = svn_base64_encode_string2(arg, TRUE, pool); 212 213 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(s)", "step", arg)); 214 215 /* Read and decode the client response. */ 216 SVN_ERR(svn_ra_svn__read_item(conn, pool, &item)); 217 if (item->kind != SVN_RA_SVN_STRING) 218 return SVN_NO_ERROR; 219 220 in = &item->u.string; 221 if (use_base64) 222 in = svn_base64_decode_string(in, pool); 223 224 if (in->len > UINT_MAX) 225 return svn_error_createf(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 226 _("Step response is too long")); 227 228 result = svn_sasl__server_step(sasl_ctx, in->data, 229 (unsigned int) in->len, 230 &out, &outlen); 231 } 232 233 if (result != SASL_OK) 234 return fail_auth(conn, pool, sasl_ctx); 235 236 /* Send our last response, if necessary. */ 237 if (outlen) 238 arg = svn_base64_encode_string2(svn_string_ncreate(out, outlen, pool), TRUE, 239 pool); 240 else 241 arg = NULL; 242 243 *success = TRUE; 244 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(?s)", "success", arg)); 245 246 return SVN_NO_ERROR; 247} 248 249static apr_status_t sasl_dispose_cb(void *data) 250{ 251 sasl_conn_t *sasl_ctx = (sasl_conn_t*) data; 252 svn_sasl__dispose(&sasl_ctx); 253 return APR_SUCCESS; 254} 255 256svn_error_t *cyrus_auth_request(svn_ra_svn_conn_t *conn, 257 apr_pool_t *pool, 258 server_baton_t *b, 259 enum access_type required, 260 svn_boolean_t needs_username) 261{ 262 sasl_conn_t *sasl_ctx; 263 apr_pool_t *subpool; 264 apr_status_t apr_err; 265 const char *localaddrport = NULL, *remoteaddrport = NULL; 266 const char *mechlist; 267 char hostname[APRMAXHOSTLEN + 1]; 268 sasl_security_properties_t secprops; 269 svn_boolean_t success, no_anonymous; 270 int mech_count, result = SASL_OK; 271 272 SVN_ERR(svn_ra_svn__get_addresses(&localaddrport, &remoteaddrport, 273 conn, pool)); 274 apr_err = apr_gethostname(hostname, sizeof(hostname), pool); 275 if (apr_err) 276 { 277 svn_error_t *err = svn_error_wrap_apr(apr_err, _("Can't get hostname")); 278 SVN_ERR(write_failure(conn, pool, &err)); 279 return svn_ra_svn__flush(conn, pool); 280 } 281 282 /* Create a SASL context. SASL_SUCCESS_DATA tells SASL that the protocol 283 supports sending data along with the final "success" message. */ 284 result = svn_sasl__server_new(SVN_RA_SVN_SASL_NAME, 285 hostname, b->repository->realm, 286 localaddrport, remoteaddrport, 287 NULL, SASL_SUCCESS_DATA, 288 &sasl_ctx); 289 if (result != SASL_OK) 290 { 291 svn_error_t *err = svn_error_create( 292 SVN_ERR_RA_NOT_AUTHORIZED, NULL, 293 svn_sasl__errstring(result, NULL, NULL)); 294 SVN_ERR(write_failure(conn, pool, &err)); 295 return svn_ra_svn__flush(conn, pool); 296 } 297 298 /* Make sure the context is always destroyed. */ 299 apr_pool_cleanup_register(b->pool, sasl_ctx, sasl_dispose_cb, 300 apr_pool_cleanup_null); 301 302 /* Initialize security properties. */ 303 svn_ra_svn__default_secprops(&secprops); 304 305 /* Don't allow ANONYMOUS if a username is required. */ 306 no_anonymous = needs_username || b->repository->anon_access < required; 307 if (no_anonymous) 308 secprops.security_flags |= SASL_SEC_NOANONYMOUS; 309 310 secprops.min_ssf = b->repository->min_ssf; 311 secprops.max_ssf = b->repository->max_ssf; 312 313 /* Set security properties. */ 314 result = svn_sasl__setprop(sasl_ctx, SASL_SEC_PROPS, &secprops); 315 if (result != SASL_OK) 316 return fail_cmd(conn, pool, sasl_ctx); 317 318 /* SASL needs to know if we are externally authenticated. */ 319 if (b->client_info->tunnel_user) 320 result = svn_sasl__setprop(sasl_ctx, SASL_AUTH_EXTERNAL, 321 b->client_info->tunnel_user); 322 if (result != SASL_OK) 323 return fail_cmd(conn, pool, sasl_ctx); 324 325 /* Get the list of mechanisms. */ 326 result = svn_sasl__listmech(sasl_ctx, NULL, NULL, " ", NULL, 327 &mechlist, NULL, &mech_count); 328 329 if (result != SASL_OK) 330 return fail_cmd(conn, pool, sasl_ctx); 331 332 if (mech_count == 0) 333 { 334 svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 335 _("Could not obtain the list" 336 " of SASL mechanisms")); 337 SVN_ERR(write_failure(conn, pool, &err)); 338 return svn_ra_svn__flush(conn, pool); 339 } 340 341 /* Send the list of mechanisms and the realm to the client. */ 342 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "(w)c", 343 mechlist, b->repository->realm)); 344 345 /* The main authentication loop. */ 346 subpool = svn_pool_create(pool); 347 do 348 { 349 svn_pool_clear(subpool); 350 SVN_ERR(try_auth(conn, sasl_ctx, subpool, b, &success)); 351 } 352 while (!success); 353 svn_pool_destroy(subpool); 354 355 SVN_ERR(svn_ra_svn__enable_sasl_encryption(conn, sasl_ctx, pool)); 356 357 if (no_anonymous) 358 { 359 char *p; 360 const void *user; 361 362 /* Get the authenticated username. */ 363 result = svn_sasl__getprop(sasl_ctx, SASL_USERNAME, &user); 364 365 if (result != SASL_OK) 366 return fail_cmd(conn, pool, sasl_ctx); 367 368 if ((p = strchr(user, '@')) != NULL) 369 { 370 /* Drop the realm part. */ 371 b->client_info->user = apr_pstrndup(b->pool, user, 372 p - (const char *)user); 373 } 374 else 375 { 376 svn_error_t *err; 377 err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 378 _("Couldn't obtain the authenticated" 379 " username")); 380 SVN_ERR(write_failure(conn, pool, &err)); 381 return svn_ra_svn__flush(conn, pool); 382 } 383 } 384 385 return SVN_NO_ERROR; 386} 387 388#endif /* SVN_HAVE_SASL */ 389