xref: /freebsd/crypto/krb5/src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap_conn.c (revision f1c4c3daccbaf3820f0e2224de53df12fc952fcc)
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         ldap_unbind_ext_s(server->ldap_handle, NULL, NULL);
193         free(server);
194         return ret;
195     }
196 
197     server->next = info->ldap_server_handles;
198     info->ldap_server_handles = server;
199     info->num_conns++;
200     info->server_status = ON;
201     return 0;
202 }
203 
204 /*
205  * initialization for data base routines.
206  */
207 
208 krb5_error_code
krb5_ldap_db_init(krb5_context context,krb5_ldap_context * ctx)209 krb5_ldap_db_init(krb5_context context, krb5_ldap_context *ctx)
210 {
211     krb5_error_code ret;
212     int i, version = LDAP_VERSION3;
213     unsigned int conns;
214     krb5_ldap_server_info *info;
215     struct timeval local_timelimit = { 10, 0 };
216 
217     ret = validate_context(context, ctx);
218     if (ret)
219         return ret;
220 
221 #ifdef LDAP_OPT_DEBUG_LEVEL
222     ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &ctx->ldap_debug);
223 #endif
224     ldap_set_option(NULL, LDAP_OPT_PROTOCOL_VERSION, &version);
225 #ifdef LDAP_OPT_NETWORK_TIMEOUT
226     ldap_set_option(NULL, LDAP_OPT_NETWORK_TIMEOUT, &local_timelimit);
227 #elif defined LDAP_X_OPT_CONNECT_TIMEOUT
228     ldap_set_option(NULL, LDAP_X_OPT_CONNECT_TIMEOUT, &local_timelimit);
229 #endif
230 
231     HNDL_LOCK(ctx);
232     for (i = 0; ctx->server_info_list[i] != NULL; i++) {
233         info = ctx->server_info_list[i];
234         if (info->server_status == NOTSET) {
235             krb5_clear_error_message(context);
236 
237 #ifdef LDAP_MOD_INCREMENT
238             info->modify_increment = has_modify_increment(context,
239                                                           info->server_name);
240 #else
241             info->modify_increment = 0;
242 #endif
243 
244             for (conns = 0; conns < ctx->max_server_conns; conns++) {
245                 ret = initialize_server(ctx, info);
246                 if (ret)
247                     break;
248             }
249 
250             /* If we opened a connection, don't try any more servers. */
251             if (info->server_status == ON)
252                 break;
253         }
254     }
255     HNDL_UNLOCK(ctx);
256 
257     return ret;
258 }
259 
260 
261 /*
262  * get a single handle. Do not lock the mutex
263  */
264 
265 krb5_error_code
krb5_ldap_db_single_init(krb5_ldap_context * ldap_context)266 krb5_ldap_db_single_init(krb5_ldap_context *ldap_context)
267 {
268     krb5_error_code             st=0;
269     int                         cnt=0;
270     krb5_ldap_server_info       *server_info=NULL;
271 
272     while (ldap_context->server_info_list[cnt] != NULL) {
273         server_info = ldap_context->server_info_list[cnt];
274         if ((server_info->server_status == NOTSET || server_info->server_status == ON)) {
275             if (server_info->num_conns < ldap_context->max_server_conns-1) {
276                 st = initialize_server(ldap_context, server_info);
277                 if (st == LDAP_SUCCESS)
278                     goto cleanup;
279             }
280         }
281         ++cnt;
282     }
283 
284     /* If we are here, try to connect to all the servers */
285 
286     cnt = 0;
287     while (ldap_context->server_info_list[cnt] != NULL) {
288         server_info = ldap_context->server_info_list[cnt];
289         st = initialize_server(ldap_context, server_info);
290         if (st == LDAP_SUCCESS)
291             goto cleanup;
292         ++cnt;
293     }
294 cleanup:
295     return (st);
296 }
297 
298 krb5_error_code
krb5_ldap_rebind(krb5_ldap_context * ldap_context,krb5_ldap_server_handle ** ldap_server_handle)299 krb5_ldap_rebind(krb5_ldap_context *ldap_context,
300                  krb5_ldap_server_handle **ldap_server_handle)
301 {
302     krb5_ldap_server_handle *handle = *ldap_server_handle;
303 
304     ldap_unbind_ext_s(handle->ldap_handle, NULL, NULL);
305     if (ldap_initialize(&handle->ldap_handle,
306                         handle->server_info->server_name) != LDAP_SUCCESS ||
307         authenticate(ldap_context, handle) != 0) {
308         return krb5_ldap_request_next_handle_from_pool(ldap_context,
309                                                        ldap_server_handle);
310     }
311     return LDAP_SUCCESS;
312 }
313 
314 /*
315  *     DAL API functions
316  */
317 krb5_error_code
krb5_ldap_lib_init(void)318 krb5_ldap_lib_init(void)
319 {
320     return 0;
321 }
322 
323 krb5_error_code
krb5_ldap_lib_cleanup(void)324 krb5_ldap_lib_cleanup(void)
325 {
326     /* right now, no cleanup required */
327     return 0;
328 }
329 
330 krb5_error_code
krb5_ldap_free_ldap_context(krb5_ldap_context * ldap_context)331 krb5_ldap_free_ldap_context(krb5_ldap_context *ldap_context)
332 {
333     if (ldap_context == NULL)
334         return 0;
335 
336     free(ldap_context->container_dn);
337     ldap_context->container_dn = NULL;
338 
339     krb5_ldap_free_realm_params(ldap_context->lrparams);
340     ldap_context->lrparams = NULL;
341 
342     krb5_ldap_free_server_params(ldap_context);
343 
344     return 0;
345 }
346 
347 krb5_error_code
krb5_ldap_close(krb5_context context)348 krb5_ldap_close(krb5_context context)
349 {
350     kdb5_dal_handle  *dal_handle=NULL;
351     krb5_ldap_context *ldap_context=NULL;
352 
353     if (context == NULL ||
354         context->dal_handle == NULL ||
355         context->dal_handle->db_context == NULL)
356         return 0;
357 
358     dal_handle = context->dal_handle;
359     ldap_context = (krb5_ldap_context *) dal_handle->db_context;
360     dal_handle->db_context = NULL;
361 
362     krb5_ldap_free_ldap_context(ldap_context);
363 
364     return 0;
365 }
366