xref: /freebsd/crypto/krb5/src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap_conn.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* plugins/kdb/ldap/libkdb_ldap/kdb_ldap_conn.c */
3 /*
4  * Copyright (c) 2004-2005, Novell, Inc.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions are met:
9  *
10  *   * Redistributions of source code must retain the above copyright notice,
11  *       this list of conditions and the following disclaimer.
12  *   * Redistributions in binary form must reproduce the above copyright
13  *       notice, this list of conditions and the following disclaimer in the
14  *       documentation and/or other materials provided with the distribution.
15  *   * The copyright holder's name is not used to endorse or promote products
16  *       derived from this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  * POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 #include "autoconf.h"
32 #if HAVE_UNISTD_H
33 #include <unistd.h>
34 #endif
35 
36 #include "ldap_main.h"
37 #include "ldap_service_stash.h"
38 #include <kdb5.h>
39 #ifdef HAVE_SASL_SASL_H
40 #include <sasl/sasl.h>
41 #endif
42 
43 /* Ensure that we have the parameters we need to authenticate to the LDAP
44  * server.  Read the password if necessary. */
45 static krb5_error_code
validate_context(krb5_context context,krb5_ldap_context * ctx)46 validate_context(krb5_context context, krb5_ldap_context *ctx)
47 {
48     krb5_error_code ret;
49 
50     if (ctx->sasl_mech != NULL) {
51         /* Read the password for use as the SASL secret if we can, but do not
52          * require one as not all mechanisms need it. */
53         if (ctx->bind_pwd == NULL && ctx->sasl_authcid != NULL &&
54             ctx->service_password_file != NULL) {
55             (void)krb5_ldap_readpassword(context, ctx->service_password_file,
56                                          ctx->sasl_authcid, &ctx->bind_pwd);
57         }
58         return 0;
59     }
60 
61     /* For a simple bind, a DN and password are required. */
62 
63     if (ctx->bind_dn == NULL) {
64         k5_setmsg(context, EINVAL, _("LDAP bind dn value missing"));
65         return EINVAL;
66     }
67 
68     if (ctx->bind_pwd == NULL && ctx->service_password_file == NULL) {
69         k5_setmsg(context, EINVAL, _("LDAP bind password value missing"));
70         return EINVAL;
71     }
72 
73     if (ctx->bind_pwd == NULL && ctx->service_password_file != NULL) {
74         ret = krb5_ldap_readpassword(context, ctx->service_password_file,
75                                      ctx->bind_dn, &ctx->bind_pwd);
76         if (ret) {
77             k5_prependmsg(context, ret,
78                           _("Error reading password from stash"));
79             return ret;
80         }
81     }
82 
83     /* An empty password is not allowed. */
84     if (*ctx->bind_pwd == '\0') {
85         k5_setmsg(context, EINVAL, _("Service password length is zero"));
86         return EINVAL;
87     }
88 
89     return 0;
90 }
91 
92 /*
93  * Internal Functions called by init functions.
94  */
95 
96 #ifdef HAVE_SASL_SASL_H
97 
98 static int
interact(LDAP * ld,unsigned flags,void * defaults,void * sin)99 interact(LDAP *ld, unsigned flags, void *defaults, void *sin)
100 {
101     sasl_interact_t *in = NULL;
102     krb5_ldap_context *ctx = defaults;
103 
104     for (in = sin; in != NULL && in->id != SASL_CB_LIST_END; in++) {
105         if (in->id == SASL_CB_AUTHNAME)
106             in->result = ctx->sasl_authcid;
107         else if (in->id == SASL_CB_USER)
108             in->result = ctx->sasl_authzid;
109         else if (in->id == SASL_CB_GETREALM)
110             in->result = ctx->sasl_realm;
111         else if (in->id == SASL_CB_PASS)
112             in->result = ctx->bind_pwd;
113         else
114             return LDAP_OTHER;
115         in->len = (in->result != NULL) ? strlen(in->result) : 0;
116     }
117 
118     return LDAP_SUCCESS;
119 }
120 
121 #else /* HAVE_SASL_SASL_H */
122 
123 /* We can't define an interaction function, so only non-interactive mechs like
124  * EXTERNAL can work. */
125 static int
interact(LDAP * ld,unsigned flags,void * defaults,void * sin)126 interact(LDAP *ld, unsigned flags, void *defaults, void *sin)
127 {
128     return LDAP_OTHER;
129 }
130 
131 #endif
132 
133 static krb5_error_code
authenticate(krb5_ldap_context * ctx,krb5_ldap_server_handle * server)134 authenticate(krb5_ldap_context *ctx, krb5_ldap_server_handle *server)
135 {
136     int st;
137     struct berval bv;
138 
139     if (ctx->sasl_mech != NULL) {
140         st = ldap_sasl_interactive_bind_s(server->ldap_handle, NULL,
141                                           ctx->sasl_mech, NULL, NULL,
142                                           LDAP_SASL_QUIET, interact, ctx);
143         if (st != LDAP_SUCCESS) {
144             k5_setmsg(ctx->kcontext, KRB5_KDB_ACCESS_ERROR,
145                       _("Cannot bind to LDAP server '%s' with SASL mechanism "
146                         "'%s': %s"), server->server_info->server_name,
147                       ctx->sasl_mech, ldap_err2string(st));
148             return KRB5_KDB_ACCESS_ERROR;
149         }
150     } else {
151         /* Do a simple bind with DN and password. */
152         bv.bv_val = ctx->bind_pwd;
153         bv.bv_len = strlen(ctx->bind_pwd);
154         st = ldap_sasl_bind_s(server->ldap_handle, ctx->bind_dn, NULL, &bv,
155                               NULL, NULL, NULL);
156         if (st != LDAP_SUCCESS) {
157             k5_setmsg(ctx->kcontext, KRB5_KDB_ACCESS_ERROR,
158                       _("Cannot bind to LDAP server '%s' as '%s': %s"),
159                       server->server_info->server_name, ctx->bind_dn,
160                       ldap_err2string(st));
161             return KRB5_KDB_ACCESS_ERROR;
162         }
163     }
164     return 0;
165 }
166 
167 static krb5_error_code
initialize_server(krb5_ldap_context * ldap_context,krb5_ldap_server_info * info)168 initialize_server(krb5_ldap_context *ldap_context, krb5_ldap_server_info *info)
169 {
170     krb5_ldap_server_handle *server;
171     krb5_error_code ret;
172     int st;
173 
174     server = calloc(1, sizeof(krb5_ldap_server_handle));
175     if (server == NULL)
176         return ENOMEM;
177     server->server_info = info;
178 
179     st = ldap_initialize(&server->ldap_handle, info->server_name);
180     if (st) {
181         free(server);
182         k5_setmsg(ldap_context->kcontext, KRB5_KDB_ACCESS_ERROR,
183                   _("Cannot create LDAP handle for '%s': %s"),
184                   info->server_name, ldap_err2string(st));
185         return KRB5_KDB_ACCESS_ERROR;
186     }
187 
188     ret = authenticate(ldap_context, server);
189     if (ret) {
190         info->server_status = OFF;
191         time(&info->downtime);
192         free(server);
193         return ret;
194     }
195 
196     server->next = info->ldap_server_handles;
197     info->ldap_server_handles = server;
198     info->num_conns++;
199     info->server_status = ON;
200     return 0;
201 }
202 
203 /*
204  * initialization for data base routines.
205  */
206 
207 krb5_error_code
krb5_ldap_db_init(krb5_context context,krb5_ldap_context * ctx)208 krb5_ldap_db_init(krb5_context context, krb5_ldap_context *ctx)
209 {
210     krb5_error_code ret;
211     int i, version = LDAP_VERSION3;
212     unsigned int conns;
213     krb5_ldap_server_info *info;
214     struct timeval local_timelimit = { 10, 0 };
215 
216     ret = validate_context(context, ctx);
217     if (ret)
218         return ret;
219 
220 #ifdef LDAP_OPT_DEBUG_LEVEL
221     ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &ctx->ldap_debug);
222 #endif
223     ldap_set_option(NULL, LDAP_OPT_PROTOCOL_VERSION, &version);
224 #ifdef LDAP_OPT_NETWORK_TIMEOUT
225     ldap_set_option(NULL, LDAP_OPT_NETWORK_TIMEOUT, &local_timelimit);
226 #elif defined LDAP_X_OPT_CONNECT_TIMEOUT
227     ldap_set_option(NULL, LDAP_X_OPT_CONNECT_TIMEOUT, &local_timelimit);
228 #endif
229 
230     HNDL_LOCK(ctx);
231     for (i = 0; ctx->server_info_list[i] != NULL; i++) {
232         info = ctx->server_info_list[i];
233         if (info->server_status == NOTSET) {
234             krb5_clear_error_message(context);
235 
236 #ifdef LDAP_MOD_INCREMENT
237             info->modify_increment = has_modify_increment(context,
238                                                           info->server_name);
239 #else
240             info->modify_increment = 0;
241 #endif
242 
243             for (conns = 0; conns < ctx->max_server_conns; conns++) {
244                 ret = initialize_server(ctx, info);
245                 if (ret)
246                     break;
247             }
248 
249             /* If we opened a connection, don't try any more servers. */
250             if (info->server_status == ON)
251                 break;
252         }
253     }
254     HNDL_UNLOCK(ctx);
255 
256     return ret;
257 }
258 
259 
260 /*
261  * get a single handle. Do not lock the mutex
262  */
263 
264 krb5_error_code
krb5_ldap_db_single_init(krb5_ldap_context * ldap_context)265 krb5_ldap_db_single_init(krb5_ldap_context *ldap_context)
266 {
267     krb5_error_code             st=0;
268     int                         cnt=0;
269     krb5_ldap_server_info       *server_info=NULL;
270 
271     while (ldap_context->server_info_list[cnt] != NULL) {
272         server_info = ldap_context->server_info_list[cnt];
273         if ((server_info->server_status == NOTSET || server_info->server_status == ON)) {
274             if (server_info->num_conns < ldap_context->max_server_conns-1) {
275                 st = initialize_server(ldap_context, server_info);
276                 if (st == LDAP_SUCCESS)
277                     goto cleanup;
278             }
279         }
280         ++cnt;
281     }
282 
283     /* If we are here, try to connect to all the servers */
284 
285     cnt = 0;
286     while (ldap_context->server_info_list[cnt] != NULL) {
287         server_info = ldap_context->server_info_list[cnt];
288         st = initialize_server(ldap_context, server_info);
289         if (st == LDAP_SUCCESS)
290             goto cleanup;
291         ++cnt;
292     }
293 cleanup:
294     return (st);
295 }
296 
297 krb5_error_code
krb5_ldap_rebind(krb5_ldap_context * ldap_context,krb5_ldap_server_handle ** ldap_server_handle)298 krb5_ldap_rebind(krb5_ldap_context *ldap_context,
299                  krb5_ldap_server_handle **ldap_server_handle)
300 {
301     krb5_ldap_server_handle *handle = *ldap_server_handle;
302 
303     ldap_unbind_ext_s(handle->ldap_handle, NULL, NULL);
304     if (ldap_initialize(&handle->ldap_handle,
305                         handle->server_info->server_name) != LDAP_SUCCESS ||
306         authenticate(ldap_context, handle) != 0) {
307         return krb5_ldap_request_next_handle_from_pool(ldap_context,
308                                                        ldap_server_handle);
309     }
310     return LDAP_SUCCESS;
311 }
312 
313 /*
314  *     DAL API functions
315  */
316 krb5_error_code
krb5_ldap_lib_init()317 krb5_ldap_lib_init()
318 {
319     return 0;
320 }
321 
322 krb5_error_code
krb5_ldap_lib_cleanup()323 krb5_ldap_lib_cleanup()
324 {
325     /* right now, no cleanup required */
326     return 0;
327 }
328 
329 krb5_error_code
krb5_ldap_free_ldap_context(krb5_ldap_context * ldap_context)330 krb5_ldap_free_ldap_context(krb5_ldap_context *ldap_context)
331 {
332     if (ldap_context == NULL)
333         return 0;
334 
335     free(ldap_context->container_dn);
336     ldap_context->container_dn = NULL;
337 
338     krb5_ldap_free_realm_params(ldap_context->lrparams);
339     ldap_context->lrparams = NULL;
340 
341     krb5_ldap_free_server_params(ldap_context);
342 
343     return 0;
344 }
345 
346 krb5_error_code
krb5_ldap_close(krb5_context context)347 krb5_ldap_close(krb5_context context)
348 {
349     kdb5_dal_handle  *dal_handle=NULL;
350     krb5_ldap_context *ldap_context=NULL;
351 
352     if (context == NULL ||
353         context->dal_handle == NULL ||
354         context->dal_handle->db_context == NULL)
355         return 0;
356 
357     dal_handle = context->dal_handle;
358     ldap_context = (krb5_ldap_context *) dal_handle->db_context;
359     dal_handle->db_context = NULL;
360 
361     krb5_ldap_free_ldap_context(ldap_context);
362 
363     return 0;
364 }
365