]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/svnserve/cyrus_auth.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / svnserve / cyrus_auth.c
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 */
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)
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
102 static sasl_callback_t callbacks[] =
103 {
104   { SASL_CB_CANON_USER, (int (*)(void))canonicalize_username, NULL },
105   { SASL_CB_LIST_END, NULL, NULL }
106 };
107
108 static 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 = sasl_server_init(callbacks, SVN_RA_SVN_SASL_NAME);
116   if (result != SASL_OK)
117     {
118       svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
119                                           sasl_errstring(result, NULL, NULL));
120       return svn_error_quick_wrap(err,
121                                   _("Could not initialize the SASL library"));
122     }
123   return SVN_NO_ERROR;
124 }
125
126 svn_error_t *cyrus_init(apr_pool_t *pool)
127 {
128   SVN_ERR(svn_atomic__init_once(&svn_ra_svn__sasl_status,
129                                 initialize, NULL, pool));
130   return SVN_NO_ERROR;
131 }
132
133 /* Tell the client the authentication failed. This is only used during
134    the authentication exchange (i.e. inside try_auth()). */
135 static svn_error_t *
136 fail_auth(svn_ra_svn_conn_t *conn, apr_pool_t *pool, sasl_conn_t *sasl_ctx)
137 {
138   const char *msg = sasl_errdetail(sasl_ctx);
139   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c)", "failure", msg));
140   return svn_ra_svn__flush(conn, pool);
141 }
142
143 /* Like svn_ra_svn_write_cmd_failure, but also clears the given error
144    and sets it to SVN_NO_ERROR. */
145 static svn_error_t *
146 write_failure(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_error_t **err_p)
147 {
148   svn_error_t *write_err = svn_ra_svn__write_cmd_failure(conn, pool, *err_p);
149   svn_error_clear(*err_p);
150   *err_p = SVN_NO_ERROR;
151   return write_err;
152 }
153
154 /* Used if we run into a SASL error outside try_auth(). */
155 static svn_error_t *
156 fail_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool, sasl_conn_t *sasl_ctx)
157 {
158   svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
159                                       sasl_errdetail(sasl_ctx));
160   SVN_ERR(write_failure(conn, pool, &err));
161   return svn_ra_svn__flush(conn, pool);
162 }
163
164 static svn_error_t *try_auth(svn_ra_svn_conn_t *conn,
165                              sasl_conn_t *sasl_ctx,
166                              apr_pool_t *pool,
167                              server_baton_t *b,
168                              svn_boolean_t *success)
169 {
170   const char *out, *mech;
171   const svn_string_t *arg = NULL, *in;
172   unsigned int outlen;
173   int result;
174   svn_boolean_t use_base64;
175
176   *success = FALSE;
177
178   /* Read the client's chosen mech and the initial token. */
179   SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "w(?s)", &mech, &in));
180
181   if (strcmp(mech, "EXTERNAL") == 0 && !in)
182     in = svn_string_create(b->client_info->tunnel_user, pool);
183   else if (in)
184     in = svn_base64_decode_string(in, pool);
185
186   /* For CRAM-MD5, we don't base64-encode stuff. */
187   use_base64 = (strcmp(mech, "CRAM-MD5") != 0);
188
189   /* sasl uses unsigned int for the length of strings, we use apr_size_t
190    * which may not be the same size.  Deal with potential integer overflow */
191   if (in && in->len > UINT_MAX)
192     return svn_error_createf(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
193                              _("Initial token is too long"));
194
195   result = sasl_server_start(sasl_ctx, mech,
196                              in ? in->data : NULL,
197                              in ? (unsigned int) in->len : 0, &out, &outlen);
198
199   if (result != SASL_OK && result != SASL_CONTINUE)
200     return fail_auth(conn, pool, sasl_ctx);
201
202   while (result == SASL_CONTINUE)
203     {
204       svn_ra_svn_item_t *item;
205
206       arg = svn_string_ncreate(out, outlen, pool);
207       /* Encode what we send to the client. */
208       if (use_base64)
209         arg = svn_base64_encode_string2(arg, TRUE, pool);
210
211       SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(s)", "step", arg));
212
213       /* Read and decode the client response. */
214       SVN_ERR(svn_ra_svn__read_item(conn, pool, &item));
215       if (item->kind != SVN_RA_SVN_STRING)
216         return SVN_NO_ERROR;
217
218       in = item->u.string;
219       if (use_base64)
220         in = svn_base64_decode_string(in, pool);
221
222       if (in->len > UINT_MAX)
223         return svn_error_createf(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
224                                  _("Step response is too long"));
225
226       result = sasl_server_step(sasl_ctx, in->data, (unsigned int) in->len,
227                                 &out, &outlen);
228     }
229
230   if (result != SASL_OK)
231     return fail_auth(conn, pool, sasl_ctx);
232
233   /* Send our last response, if necessary. */
234   if (outlen)
235     arg = svn_base64_encode_string2(svn_string_ncreate(out, outlen, pool), TRUE,
236                                     pool);
237   else
238     arg = NULL;
239
240   *success = TRUE;
241   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(?s)", "success", arg));
242
243   return SVN_NO_ERROR;
244 }
245
246 static apr_status_t sasl_dispose_cb(void *data)
247 {
248   sasl_conn_t *sasl_ctx = (sasl_conn_t*) data;
249   sasl_dispose(&sasl_ctx);
250   return APR_SUCCESS;
251 }
252
253 svn_error_t *cyrus_auth_request(svn_ra_svn_conn_t *conn,
254                                 apr_pool_t *pool,
255                                 server_baton_t *b,
256                                 enum access_type required,
257                                 svn_boolean_t needs_username)
258 {
259   sasl_conn_t *sasl_ctx;
260   apr_pool_t *subpool;
261   apr_status_t apr_err;
262   const char *localaddrport = NULL, *remoteaddrport = NULL;
263   const char *mechlist;
264   char hostname[APRMAXHOSTLEN + 1];
265   sasl_security_properties_t secprops;
266   svn_boolean_t success, no_anonymous;
267   int mech_count, result = SASL_OK;
268
269   SVN_ERR(svn_ra_svn__get_addresses(&localaddrport, &remoteaddrport,
270                                         conn, pool));
271   apr_err = apr_gethostname(hostname, sizeof(hostname), pool);
272   if (apr_err)
273     {
274       svn_error_t *err = svn_error_wrap_apr(apr_err, _("Can't get hostname"));
275       SVN_ERR(write_failure(conn, pool, &err));
276       return svn_ra_svn__flush(conn, pool);
277     }
278
279   /* Create a SASL context. SASL_SUCCESS_DATA tells SASL that the protocol
280      supports sending data along with the final "success" message. */
281   result = sasl_server_new(SVN_RA_SVN_SASL_NAME,
282                            hostname, b->repository->realm,
283                            localaddrport, remoteaddrport,
284                            NULL, SASL_SUCCESS_DATA,
285                            &sasl_ctx);
286   if (result != SASL_OK)
287     {
288       svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
289                                           sasl_errstring(result, NULL, NULL));
290       SVN_ERR(write_failure(conn, pool, &err));
291       return svn_ra_svn__flush(conn, pool);
292     }
293
294   /* Make sure the context is always destroyed. */
295   apr_pool_cleanup_register(b->pool, sasl_ctx, sasl_dispose_cb,
296                             apr_pool_cleanup_null);
297
298   /* Initialize security properties. */
299   svn_ra_svn__default_secprops(&secprops);
300
301   /* Don't allow ANONYMOUS if a username is required. */
302   no_anonymous = needs_username || b->repository->anon_access < required;
303   if (no_anonymous)
304     secprops.security_flags |= SASL_SEC_NOANONYMOUS;
305
306   secprops.min_ssf = b->repository->min_ssf;
307   secprops.max_ssf = b->repository->max_ssf;
308
309   /* Set security properties. */
310   result = sasl_setprop(sasl_ctx, SASL_SEC_PROPS, &secprops);
311   if (result != SASL_OK)
312     return fail_cmd(conn, pool, sasl_ctx);
313
314   /* SASL needs to know if we are externally authenticated. */
315   if (b->client_info->tunnel_user)
316     result = sasl_setprop(sasl_ctx, SASL_AUTH_EXTERNAL,
317                           b->client_info->tunnel_user);
318   if (result != SASL_OK)
319     return fail_cmd(conn, pool, sasl_ctx);
320
321   /* Get the list of mechanisms. */
322   result = sasl_listmech(sasl_ctx, NULL, NULL, " ", NULL,
323                          &mechlist, NULL, &mech_count);
324
325   if (result != SASL_OK)
326     return fail_cmd(conn, pool, sasl_ctx);
327
328   if (mech_count == 0)
329     {
330       svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
331                                           _("Could not obtain the list"
332                                           " of SASL mechanisms"));
333       SVN_ERR(write_failure(conn, pool, &err));
334       return svn_ra_svn__flush(conn, pool);
335     }
336
337   /* Send the list of mechanisms and the realm to the client. */
338   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "(w)c",
339                                          mechlist, b->repository->realm));
340
341   /* The main authentication loop. */
342   subpool = svn_pool_create(pool);
343   do
344     {
345       svn_pool_clear(subpool);
346       SVN_ERR(try_auth(conn, sasl_ctx, subpool, b, &success));
347     }
348   while (!success);
349   svn_pool_destroy(subpool);
350
351   SVN_ERR(svn_ra_svn__enable_sasl_encryption(conn, sasl_ctx, pool));
352
353   if (no_anonymous)
354     {
355       char *p;
356       const void *user;
357
358       /* Get the authenticated username. */
359       result = sasl_getprop(sasl_ctx, SASL_USERNAME, &user);
360
361       if (result != SASL_OK)
362         return fail_cmd(conn, pool, sasl_ctx);
363
364       if ((p = strchr(user, '@')) != NULL)
365         {
366           /* Drop the realm part. */
367           b->client_info->user = apr_pstrndup(b->pool, user,
368                                               p - (const char *)user);
369         }
370       else
371         {
372           svn_error_t *err;
373           err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
374                                  _("Couldn't obtain the authenticated"
375                                  " username"));
376           SVN_ERR(write_failure(conn, pool, &err));
377           return svn_ra_svn__flush(conn, pool);
378         }
379     }
380
381   return SVN_NO_ERROR;
382 }
383
384 #endif /* SVN_HAVE_SASL */