]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/svnserve/cyrus_auth.c
Update svn-1.9.7 to 1.10.0.
[FreeBSD/FreeBSD.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 = 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
127 svn_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()). */
136 static svn_error_t *
137 fail_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. */
146 static svn_error_t *
147 write_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(). */
156 static svn_error_t *
157 fail_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
165 static 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
249 static 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
256 svn_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 */