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