13207ed1908d7c3843c494c463da9a185db9062b
[freeradius.git] / src / modules / rlm_ldap / rlm_ldap.c
1 /*
2  *   This program is is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU General Public License as published by
4  *   the Free Software Foundation; either version 2 of the License, or (at
5  *   your option) any later version.
6  *
7  *   This program is distributed in the hope that it will be useful,
8  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  *   GNU General Public License for more details.
11  *
12  *   You should have received a copy of the GNU General Public License
13  *   along with this program; if not, write to the Free Software
14  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16
17 /**
18  * $Id$
19  * @file rlm_ldap.c
20  * @brief LDAP authorization and authentication module.
21  *
22  * @author Arran Cudbard-Bell <a.cudbardb@freeradius.org>
23  * @author Alan DeKok <aland@freeradius.org>
24  *
25  * @copyright 2012,2015 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
26  * @copyright 2013,2015 Network RADIUS SARL <info@networkradius.com>
27  * @copyright 2012 Alan DeKok <aland@freeradius.org>
28  * @copyright 1999-2013 The FreeRADIUS Server Project.
29  */
30 RCSID("$Id$")
31
32 #include        <freeradius-devel/rad_assert.h>
33
34 #include        <stdarg.h>
35 #include        <ctype.h>
36
37 #include        "ldap.h"
38
39 /*
40  *      Scopes
41  */
42 FR_NAME_NUMBER const ldap_scope[] = {
43         { "sub",        LDAP_SCOPE_SUB  },
44         { "one",        LDAP_SCOPE_ONE  },
45         { "base",       LDAP_SCOPE_BASE },
46 #ifdef LDAP_SCOPE_CHILDREN
47         { "children",   LDAP_SCOPE_CHILDREN },
48 #endif
49         {  NULL , -1 }
50 };
51
52 #ifdef LDAP_OPT_X_TLS_NEVER
53 FR_NAME_NUMBER const ldap_tls_require_cert[] = {
54         { "never",      LDAP_OPT_X_TLS_NEVER    },
55         { "demand",     LDAP_OPT_X_TLS_DEMAND   },
56         { "allow",      LDAP_OPT_X_TLS_ALLOW    },
57         { "try",        LDAP_OPT_X_TLS_TRY      },
58         { "hard",       LDAP_OPT_X_TLS_HARD     },      /* oh yes, just like that */
59
60         {  NULL , -1 }
61 };
62 #endif
63
64 static FR_NAME_NUMBER const ldap_dereference[] = {
65         { "never",      LDAP_DEREF_NEVER        },
66         { "searching",  LDAP_DEREF_SEARCHING    },
67         { "finding",    LDAP_DEREF_FINDING      },
68         { "always",     LDAP_DEREF_ALWAYS       },
69
70         {  NULL , -1 }
71 };
72
73 static CONF_PARSER sasl_mech_dynamic[] = {
74         { "mech", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_TMPL | PW_TYPE_NOT_EMPTY, ldap_sasl_dynamic, mech), NULL },
75         { "proxy", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_TMPL, ldap_sasl_dynamic, proxy), NULL },
76         { "realm", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_TMPL, ldap_sasl_dynamic, realm), NULL },
77         CONF_PARSER_TERMINATOR
78 };
79
80 static CONF_PARSER sasl_mech_static[] = {
81         { "mech", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_NOT_EMPTY, ldap_sasl, mech), NULL },
82         { "proxy", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_sasl, proxy), NULL },
83         { "realm", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_sasl, realm), NULL },
84         CONF_PARSER_TERMINATOR
85 };
86
87 /*
88  *      TLS Configuration
89  */
90 static CONF_PARSER tls_config[] = {
91         /*
92          *      Deprecated attributes
93          */
94         { "cacertfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT | PW_TYPE_DEPRECATED, rlm_ldap_t, tls_ca_file), NULL },
95         { "ca_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_ldap_t, tls_ca_file), NULL },
96
97         { "cacertdir", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT | PW_TYPE_DEPRECATED, rlm_ldap_t, tls_ca_path), NULL },
98         { "ca_path", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_ldap_t, tls_ca_path), NULL },
99
100         { "certfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT | PW_TYPE_DEPRECATED, rlm_ldap_t, tls_certificate_file), NULL },
101         { "certificate_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_ldap_t, tls_certificate_file), NULL },
102
103         { "keyfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT | PW_TYPE_DEPRECATED, rlm_ldap_t, tls_private_key_file), NULL }, // OK if it changes on HUP
104         { "private_key_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_ldap_t, tls_private_key_file), NULL }, // OK if it changes on HUP
105
106         { "randfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT | PW_TYPE_DEPRECATED, rlm_ldap_t, tls_random_file), NULL },
107         { "random_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_ldap_t, tls_random_file), NULL },
108
109         /*
110          *      LDAP Specific TLS attributes
111          */
112         { "start_tls", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_ldap_t, start_tls), "no" },
113         { "require_cert", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_ldap_t, tls_require_cert_str), NULL },
114         CONF_PARSER_TERMINATOR
115 };
116
117
118 static CONF_PARSER profile_config[] = {
119         { "filter", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_TMPL, rlm_ldap_t, profile_filter), "(&)" }, //!< Correct filter for when the DN is known.
120         { "attribute", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_ldap_t, profile_attr), NULL },
121         { "default", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_TMPL, rlm_ldap_t, default_profile), NULL },
122         CONF_PARSER_TERMINATOR
123 };
124
125 /*
126  *      User configuration
127  */
128 static CONF_PARSER user_config[] = {
129         { "filter", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_TMPL, rlm_ldap_t, userobj_filter), NULL },
130         { "scope", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_ldap_t, userobj_scope_str), "sub" },
131         { "base_dn", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_TMPL, rlm_ldap_t, userobj_base_dn), "" },
132         { "sort_by", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_ldap_t, userobj_sort_by), NULL },
133
134         { "access_attribute", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_ldap_t, userobj_access_attr), NULL },
135         { "access_positive", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_ldap_t, access_positive), "yes" },
136
137         /* Should be deprecated */
138         { "sasl", FR_CONF_OFFSET(PW_TYPE_SUBSECTION, rlm_ldap_t, user_sasl), (void const *) sasl_mech_dynamic },
139         CONF_PARSER_TERMINATOR
140 };
141
142 /*
143  *      Group configuration
144  */
145 static CONF_PARSER group_config[] = {
146         { "filter", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_ldap_t, groupobj_filter), NULL },
147         { "scope", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_ldap_t, groupobj_scope_str), "sub" },
148         { "base_dn", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_TMPL, rlm_ldap_t, groupobj_base_dn), "" },
149
150         { "name_attribute", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_ldap_t, groupobj_name_attr), "cn" },
151         { "membership_attribute", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_ldap_t, userobj_membership_attr), NULL },
152         { "membership_filter", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_ldap_t, groupobj_membership_filter), NULL },
153         { "cacheable_name", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_ldap_t, cacheable_group_name), "no" },
154         { "cacheable_dn", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_ldap_t, cacheable_group_dn), "no" },
155         { "cache_attribute", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_ldap_t, cache_attribute), NULL },
156         CONF_PARSER_TERMINATOR
157 };
158
159 static CONF_PARSER client_config[] = {
160         { "filter", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_ldap_t, clientobj_filter), NULL },
161         { "scope", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_ldap_t, clientobj_scope_str), "sub" },
162         { "base_dn", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_ldap_t, clientobj_base_dn), "" },
163         CONF_PARSER_TERMINATOR
164 };
165
166 /*
167  *      Reference for accounting updates
168  */
169 static const CONF_PARSER acct_section_config[] = {
170         { "reference", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, ldap_acct_section_t, reference), "." },
171         CONF_PARSER_TERMINATOR
172 };
173
174 /*
175  *      Various options that don't belong in the main configuration.
176  *
177  *      Note that these overlap a bit with the connection pool code!
178  */
179 static CONF_PARSER option_config[] = {
180         /*
181          *      Debugging flags to the server
182          */
183         { "ldap_debug", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_ldap_t, ldap_debug), "0x0000" },
184
185         { "dereference", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_ldap_t, dereference_str), NULL },
186
187         { "chase_referrals", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_ldap_t, chase_referrals), NULL },
188
189         { "rebind", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_ldap_t, rebind), NULL },
190
191 #ifdef LDAP_OPT_NETWORK_TIMEOUT
192         /* timeout on network activity */
193         { "net_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_ldap_t, net_timeout), "10" },
194 #endif
195
196         /* timeout for search results */
197         { "res_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_ldap_t, res_timeout), "20" },
198
199         /* allow server unlimited time for search (server-side limit) */
200         { "srv_timelimit", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_ldap_t, srv_timelimit), "20" },
201
202 #ifdef LDAP_OPT_X_KEEPALIVE_IDLE
203         { "idle", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_ldap_t, keepalive_idle), "60" },
204 #endif
205 #ifdef LDAP_OPT_X_KEEPALIVE_PROBES
206         { "probes", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_ldap_t, keepalive_probes), "3" },
207 #endif
208 #ifdef LDAP_OPT_X_KEEPALIVE_INTERVAL
209         { "interval", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_ldap_t, keepalive_interval), "30" },
210 #endif
211         CONF_PARSER_TERMINATOR
212 };
213
214
215 static const CONF_PARSER module_config[] = {
216         { "server", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_ldap_t, config_server), NULL },  /* Do not set to required */
217         { "port", FR_CONF_OFFSET(PW_TYPE_SHORT, rlm_ldap_t, port), NULL },
218
219         { "identity", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_ldap_t, admin_identity), NULL },
220         { "password", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_SECRET, rlm_ldap_t, admin_password), NULL },
221
222         { "sasl", FR_CONF_OFFSET(PW_TYPE_SUBSECTION, rlm_ldap_t, admin_sasl), (void const *) sasl_mech_static },
223
224         { "valuepair_attribute", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_ldap_t, valuepair_attr), NULL },
225
226 #ifdef WITH_EDIR
227         /* support for eDirectory Universal Password */
228         { "edir", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_ldap_t, edir), NULL }, /* NULL defaults to "no" */
229
230         /*
231          *      Attempt to bind with the cleartext password we got from eDirectory
232          *      Universal password for additional authorization checks.
233          */
234         { "edir_autz", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_ldap_t, edir_autz), NULL }, /* NULL defaults to "no" */
235 #endif
236
237         { "read_clients", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_ldap_t, do_clients), NULL }, /* NULL defaults to "no" */
238
239         { "user", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) user_config },
240
241         { "group", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) group_config },
242
243         { "client", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) client_config },
244
245         { "profile", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) profile_config },
246
247         { "options", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) option_config },
248
249         { "tls", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) tls_config },
250         CONF_PARSER_TERMINATOR
251 };
252
253 static ssize_t ldapquote_xlat(UNUSED void *instance, REQUEST *request, char const *fmt, char *out, size_t freespace)
254 {
255         return rlm_ldap_escape_func(request, out, freespace, fmt, NULL);
256 }
257
258 /** Expand an LDAP URL into a query, and return a string result from that query.
259  *
260  */
261 static ssize_t ldap_xlat(void *instance, REQUEST *request, char const *fmt, char *out, size_t freespace)
262 {
263         ldap_rcode_t            status;
264         size_t                  len = 0;
265         rlm_ldap_t              *inst = instance;
266
267         LDAPURLDesc             *ldap_url;
268         LDAPMessage             *result = NULL;
269         LDAPMessage             *entry = NULL;
270
271         struct berval           **values;
272
273         ldap_handle_t           *conn;
274         int                     ldap_errno;
275
276         char const              *url;
277         char const              **attrs;
278
279         url = fmt;
280
281         if (!ldap_is_ldap_url(url)) {
282                 REDEBUG("String passed does not look like an LDAP URL");
283                 return -1;
284         }
285
286         if (ldap_url_parse(url, &ldap_url)){
287                 REDEBUG("Parsing LDAP URL failed");
288                 return -1;
289         }
290
291         /*
292          *      Nothing, empty string, "*" string, or got 2 things, die.
293          */
294         if (!ldap_url->lud_attrs || !ldap_url->lud_attrs[0] ||
295             !*ldap_url->lud_attrs[0] ||
296             (strcmp(ldap_url->lud_attrs[0], "*") == 0) ||
297             ldap_url->lud_attrs[1]) {
298                 REDEBUG("Bad attributes list in LDAP URL. URL must specify exactly one attribute to retrieve");
299
300                 goto free_urldesc;
301         }
302
303         conn = mod_conn_get(inst, request);
304         if (!conn) goto free_urldesc;
305
306         memcpy(&attrs, &ldap_url->lud_attrs, sizeof(attrs));
307
308         status = rlm_ldap_search(&result, inst, request, &conn, ldap_url->lud_dn, ldap_url->lud_scope,
309                                  ldap_url->lud_filter, attrs, NULL, NULL);
310         switch (status) {
311         case LDAP_PROC_SUCCESS:
312                 break;
313
314         default:
315                 goto free_socket;
316         }
317
318         rad_assert(conn);
319         rad_assert(result);
320
321         entry = ldap_first_entry(conn->handle, result);
322         if (!entry) {
323                 ldap_get_option(conn->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
324                 REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno));
325                 len = -1;
326                 goto free_result;
327         }
328
329         values = ldap_get_values_len(conn->handle, entry, ldap_url->lud_attrs[0]);
330         if (!values) {
331                 RDEBUG("No \"%s\" attributes found in specified object", ldap_url->lud_attrs[0]);
332                 goto free_result;
333         }
334
335         if (values[0]->bv_len >= freespace) goto free_values;
336
337         memcpy(out, values[0]->bv_val, values[0]->bv_len + 1);  /* +1 as strlcpy expects buffer size */
338         len = values[0]->bv_len;
339
340 free_values:
341         ldap_value_free_len(values);
342 free_result:
343         ldap_msgfree(result);
344 free_socket:
345         mod_conn_release(inst, conn);
346 free_urldesc:
347         ldap_free_urldesc(ldap_url);
348
349         return len;
350 }
351
352 /** Perform LDAP-Group comparison checking
353  *
354  * Attempts to match users to groups using a variety of methods.
355  *
356  * @param instance of the rlm_ldap module.
357  * @param request Current request.
358  * @param thing Unknown.
359  * @param check Which group to check for user membership.
360  * @param check_pairs Unknown.
361  * @param reply_pairs Unknown.
362  * @return
363  *      - 1 on failure (or if the user is not a member).
364  *      - 0 on success.
365  */
366 static int rlm_ldap_groupcmp(void *instance, REQUEST *request, UNUSED VALUE_PAIR *thing, VALUE_PAIR *check,
367                              UNUSED VALUE_PAIR *check_pairs, UNUSED VALUE_PAIR **reply_pairs)
368 {
369         rlm_ldap_t      *inst = instance;
370         rlm_rcode_t     rcode;
371
372         bool            found = false;
373         bool            check_is_dn;
374
375         ldap_handle_t   *conn = NULL;
376         char const      *user_dn;
377
378         rad_assert(inst->groupobj_base_dn);
379
380         RDEBUG("Searching for user in group \"%s\"", check->vp_strvalue);
381
382         if (check->vp_length == 0) {
383                 REDEBUG("Cannot do comparison (group name is empty)");
384                 return 1;
385         }
386
387         /*
388          *      Check if we can do cached membership verification
389          */
390         check_is_dn = rlm_ldap_is_dn(check->vp_strvalue, check->vp_length);
391         if (check_is_dn) {
392                 char *norm;
393
394                 MEM(norm = talloc_memdup(check, check->vp_strvalue, talloc_array_length(check->vp_strvalue)));
395                 rlm_ldap_normalise_dn(norm, check->vp_strvalue);
396                 fr_pair_value_strsteal(check, norm);
397         }
398         if ((check_is_dn && inst->cacheable_group_dn) || (!check_is_dn && inst->cacheable_group_name)) {
399                 switch (rlm_ldap_check_cached(inst, request, check)) {
400                 case RLM_MODULE_NOTFOUND:
401                         found = false;
402                         goto finish;
403
404                 case RLM_MODULE_OK:
405                         found = true;
406                         goto finish;
407                 /*
408                  *      Fallback to dynamic search on failure
409                  */
410                 case RLM_MODULE_FAIL:
411                 case RLM_MODULE_INVALID:
412                 default:
413                         break;
414                 }
415         }
416
417         conn = mod_conn_get(inst, request);
418         if (!conn) return 1;
419
420         /*
421          *      This is used in the default membership filter.
422          */
423         user_dn = rlm_ldap_find_user(inst, request, &conn, NULL, false, NULL, &rcode);
424         if (!user_dn) {
425                 mod_conn_release(inst, conn);
426                 return 1;
427         }
428
429         rad_assert(conn);
430
431         /*
432          *      Check groupobj user membership
433          */
434         if (inst->groupobj_membership_filter) {
435                 switch (rlm_ldap_check_groupobj_dynamic(inst, request, &conn, check)) {
436                 case RLM_MODULE_NOTFOUND:
437                         break;
438
439                 case RLM_MODULE_OK:
440                         found = true;
441
442                 default:
443                         goto finish;
444                 }
445         }
446
447         rad_assert(conn);
448
449         /*
450          *      Check userobj group membership
451          */
452         if (inst->userobj_membership_attr) {
453                 switch (rlm_ldap_check_userobj_dynamic(inst, request, &conn, user_dn, check)) {
454                 case RLM_MODULE_NOTFOUND:
455                         break;
456
457                 case RLM_MODULE_OK:
458                         found = true;
459
460                 default:
461                         goto finish;
462                 }
463         }
464
465         rad_assert(conn);
466
467 finish:
468         if (conn) mod_conn_release(inst, conn);
469
470         if (!found) {
471                 RDEBUG("User is not a member of \"%s\"", check->vp_strvalue);
472
473                 return 1;
474         }
475
476         return 0;
477 }
478
479 /** Detach from the LDAP server and cleanup internal state.
480  *
481  */
482 static int mod_detach(void *instance)
483 {
484         rlm_ldap_t *inst = instance;
485
486         fr_connection_pool_free(inst->pool);
487
488         if (inst->user_map) {
489                 talloc_free(inst->user_map);
490         }
491
492         /*
493          *      Keeping the dummy ld around for the lifetime
494          *      of the module should always work,
495          *      irrespective of what changes happen in libldap.
496          */
497         if (inst->handle) {
498 #ifdef HAVE_LDAP_UNBIND_EXT_S
499                 ldap_unbind_ext_s(inst->handle, NULL, NULL);
500 #else
501                 ldap_unbind_s(inst->handle);
502 #endif
503         }
504
505 #ifdef HAVE_LDAP_CREATE_SORT_CONTROL
506         if (inst->userobj_sort_ctrl) ldap_control_free(inst->userobj_sort_ctrl);
507 #endif
508
509         return 0;
510 }
511
512 /** Parse an accounting sub section.
513  *
514  * Allocate a new ldap_acct_section_t and write the config data into it.
515  *
516  * @param[in] inst rlm_ldap configuration.
517  * @param[in] parent of the config section.
518  * @param[out] config to write the sub section parameters to.
519  * @param[in] comp The section name were parsing the config for.
520  * @return
521  *      - 0 on success.
522  *      - < 0 on failure.
523  */
524 static int parse_sub_section(rlm_ldap_t *inst, CONF_SECTION *parent, ldap_acct_section_t **config,
525                              rlm_components_t comp)
526 {
527         CONF_SECTION *cs;
528
529         char const *name = section_type_value[comp].section;
530
531         cs = cf_section_sub_find(parent, name);
532         if (!cs) {
533                 DEBUG2("rlm_ldap (%s): Couldn't find configuration for %s, will return NOOP for calls "
534                        "from this section", inst->name, name);
535
536                 return 0;
537         }
538
539         *config = talloc_zero(inst, ldap_acct_section_t);
540         if (cf_section_parse(cs, *config, acct_section_config) < 0) {
541                 LDAP_ERR("Failed parsing configuration for section %s", name);
542
543                 return -1;
544         }
545
546         (*config)->cs = cs;
547
548         return 0;
549 }
550
551 /** Bootstrap the module
552  *
553  * Define attributes.
554  *
555  * @param conf to parse.
556  * @param instance configuration data.
557  * @return
558  *      - 0 on success.
559  *      - < 0 on failure.
560  */
561 static int mod_bootstrap(CONF_SECTION *conf, void *instance)
562 {
563         rlm_ldap_t *inst = instance;
564
565         inst->name = cf_section_name2(conf);
566         if (!inst->name) {
567                 inst->name = cf_section_name1(conf);
568         }
569
570         /*
571          *      Group comparison checks.
572          */
573         if (cf_section_name2(conf)) {
574                 char buffer[256];
575
576                 snprintf(buffer, sizeof(buffer), "%s-LDAP-Group", inst->name);
577
578                 if (paircompare_register_byname(buffer, dict_attrbyvalue(PW_USER_NAME, 0), false, rlm_ldap_groupcmp, inst) < 0) {
579                         LDAP_ERR("Error registering group comparison: %s", fr_strerror());
580                         goto error;
581                 }
582
583                 inst->group_da = dict_attrbyname(buffer);
584
585                 /*
586                  *      We're the default instance
587                  */
588         } else {
589                 if (paircompare_register_byname("LDAP-Group", dict_attrbyvalue(PW_USER_NAME, 0),
590                                                 false, rlm_ldap_groupcmp, inst) < 0) {
591                         LDAP_ERR("Error registering group comparison: %s", fr_strerror());
592                         goto error;
593                 }
594
595                 inst->group_da = dict_attrbyname("LDAP-Group");
596         }
597
598         /*
599          *      Setup the cache attribute
600          */
601         if (inst->cache_attribute) {
602                 ATTR_FLAGS flags;
603
604                 memset(&flags, 0, sizeof(flags));
605                 if (dict_addattr(inst->cache_attribute, -1, 0, PW_TYPE_STRING, flags) < 0) {
606                         LDAP_ERR("Error creating cache attribute: %s", fr_strerror());
607                 error:
608                         return -1;
609
610                 }
611                 inst->cache_da = dict_attrbyname(inst->cache_attribute);
612         } else {
613                 inst->cache_da = inst->group_da;        /* Default to the group_da */
614         }
615
616         xlat_register(inst->name, ldap_xlat, rlm_ldap_escape_func, inst);
617         xlat_register("ldapquote", ldapquote_xlat, NULL, inst);
618
619         return 0;
620 }
621
622
623 /** Instantiate the module
624  *
625  * Creates a new instance of the module reading parameters from a configuration section.
626  *
627  * @param conf to parse.
628  * @param instance configuration data.
629  * @return
630  *      - 0 on success.
631  *      - < 0 on failure.
632  */
633 static int mod_instantiate(CONF_SECTION *conf, void *instance)
634 {
635         static bool     version_done;
636
637         CONF_PAIR       *cp;
638         CONF_ITEM       *ci;
639
640         CONF_SECTION *options, *update;
641         rlm_ldap_t *inst = instance;
642
643         inst->cs = conf;
644
645         options = cf_section_sub_find(conf, "options");
646         if (!options || !cf_pair_find(options, "chase_referrals")) {
647                 inst->chase_referrals_unset = true;      /* use OpenLDAP defaults */
648         }
649
650         /*
651          *      Only needs to be done once, prevents races in environment
652          *      initialisation within libldap.
653          *
654          *      See: https://github.com/arr2036/ldapperf/issues/2
655          */
656 #ifdef HAVE_LDAP_INITIALIZE
657         ldap_initialize(&inst->handle, "");
658 #else
659         inst->handle = ldap_init("", 0);
660 #endif
661
662         /*
663          *      Get version info from the LDAP API.
664          */
665         if (!version_done) {
666                 static LDAPAPIInfo info = { .ldapai_info_version = LDAP_API_INFO_VERSION };     /* static to quiet valgrind about this being uninitialised */
667                 int ldap_errno;
668
669                 version_done = true;
670
671                 ldap_errno = ldap_get_option(NULL, LDAP_OPT_API_INFO, &info);
672                 if (ldap_errno == LDAP_OPT_SUCCESS) {
673                         /*
674                          *      Don't generate warnings if the compile type vendor name
675                          *      is found within the link time vendor name.
676                          *
677                          *      This allows the server to be built against OpenLDAP but
678                          *      run with Symas OpenLDAP.
679                          */
680                         if (strcasestr(info.ldapai_vendor_name, LDAP_VENDOR_NAME) == NULL) {
681                                 WARN("rlm_ldap: libldap vendor changed since the server was built");
682                                 WARN("rlm_ldap: linked: %s, built: %s", info.ldapai_vendor_name, LDAP_VENDOR_NAME);
683                         }
684
685                         if (info.ldapai_vendor_version < LDAP_VENDOR_VERSION) {
686                                 WARN("rlm_ldap: libldap older than the version the server was built against");
687                                 WARN("rlm_ldap: linked: %i, built: %i",
688                                      info.ldapai_vendor_version, LDAP_VENDOR_VERSION);
689                         }
690
691                         INFO("rlm_ldap: libldap vendor: %s, version: %i", info.ldapai_vendor_name,
692                              info.ldapai_vendor_version);
693
694                         ldap_memfree(info.ldapai_vendor_name);
695                         ldap_memfree(info.ldapai_extensions);
696                 } else {
697                         DEBUG("rlm_ldap: Falling back to build time libldap version info.  Query for LDAP_OPT_API_INFO "
698                               "returned: %i", ldap_errno);
699                         INFO("rlm_ldap: libldap vendor: %s, version: %i.%i.%i", LDAP_VENDOR_NAME,
700                              LDAP_VENDOR_VERSION_MAJOR, LDAP_VENDOR_VERSION_MINOR, LDAP_VENDOR_VERSION_PATCH);
701                 }
702         }
703
704         /*
705          *      If the configuration parameters can't be parsed, then fail.
706          */
707         if ((parse_sub_section(inst, conf, &inst->accounting, MOD_ACCOUNTING) < 0) ||
708             (parse_sub_section(inst, conf, &inst->postauth, MOD_POST_AUTH) < 0)) {
709                 cf_log_err_cs(conf, "Failed parsing configuration");
710
711                 goto error;
712         }
713
714         /*
715          *      Sanity checks for cacheable groups code.
716          */
717         if (inst->cacheable_group_name && inst->groupobj_membership_filter) {
718                 if (!inst->groupobj_name_attr) {
719                         cf_log_err_cs(conf, "Configuration item 'group.name_attribute' must be set if cacheable "
720                                       "group names are enabled");
721
722                         goto error;
723                 }
724         }
725
726         /*
727          *      If we have a *pair* as opposed to a *section*
728          *      then the module is referencing another ldap module's
729          *      connection pool.
730          */
731         if (!cf_pair_find(conf, "pool")) {
732                 if (!inst->config_server) {
733                         cf_log_err_cs(conf, "Configuration item 'server' must have a value");
734                         goto error;
735                 }
736         }
737
738 #ifndef WITH_SASL
739         if (inst->user_sasl.mech) {
740                 cf_log_err_cs(conf, "Configuration item 'user.sasl.mech' not supported.  "
741                               "Linked libldap does not provide ldap_sasl_bind function");
742                 goto error;
743         }
744
745         if (inst->admin_sasl.mech) {
746                 cf_log_err_cs(conf, "Configuration item 'sasl.mech' not supported.  "
747                               "Linked libldap does not provide ldap_sasl_interactive_bind function");
748                 goto error;
749         }
750 #endif
751
752 #ifndef HAVE_LDAP_CREATE_SORT_CONTROL
753         if (inst->userobj_sort_by) {
754                 cf_log_err_cs(conf, "Configuration item 'sort_by' not supported.  "
755                               "Linked libldap does not provide ldap_create_sort_control function");
756                 goto error;
757         }
758 #endif
759
760         /*
761          *      For backwards compatibility hack up the first 'server'
762          *      CONF_ITEM into chunks, and add them back into the config.
763          *
764          *      @fixme this should be removed at some point.
765          */
766         if (inst->config_server) {
767                 char const      *value;
768                 char const      *p;
769                 char const      *q;
770                 char            *buff;
771
772                 bool            done = false;
773                 bool            first = true;
774
775                 cp = cf_pair_find(conf, "server");
776                 if (!cp) {
777                         cf_log_err_cs(conf, "Configuration item 'server' must have a value");
778                         return -1;
779                 }
780
781                 value = cf_pair_value(cp);
782
783                 p = value;
784                 q = p;
785                 while (!done) {
786                         switch (*q) {
787                         case '\0':
788                                 done = true;
789                                 if (p == value) break;  /* string contained no separators */
790
791                                 /* FALL-THROUGH */
792
793                         case ',':
794                         case ';':
795                         case ' ':
796                                 while (isspace((int) *p)) p++;
797                                 if (p == q) continue;
798
799                                 buff = talloc_array(inst, char, (q - p) + 1);
800                                 strlcpy(buff, p, talloc_array_length(buff));
801                                 p = ++q;
802
803                                 if (first) {
804                                         WARN("Listing multiple LDAP servers in the 'server' configuration item "
805                                              "is deprecated and will be removed in a future release.  "
806                                              "Use multiple 'server' configuration items instead");
807                                         WARN("- server = '%s'", value);
808                                 }
809                                 WARN("+ server = '%s'", buff);
810
811                                 /*
812                                  *      For the first instance of server we find, just replace
813                                  *      the existing "server" config item.
814                                  */
815                                 if (first) {
816                                         cf_pair_replace(conf, cp, buff);
817                                         first = false;
818                                         continue;
819                                 }
820
821                                 /*
822                                  *      For subsequent instances we need to add new conf pairs.
823                                  */
824                                 cp = cf_pair_alloc(conf, "server", buff, T_OP_EQ, T_BARE_WORD, T_SINGLE_QUOTED_STRING);
825                                 if (!cp) return -1;
826
827                                 ci = cf_pair_to_item(cp);
828                                 cf_item_add(conf, ci);
829
830                                 break;
831
832                         default:
833                                 q++;
834                                 continue;
835                         }
836                 }
837         }
838
839         /*
840          *      Now iterate over all the 'server' config items
841          */
842         for (cp = cf_pair_find(conf, "server");
843              cp;
844              cp = cf_pair_find_next(conf, cp, "server")) {
845                 char const *value;
846
847                 value = cf_pair_value(cp);
848
849 #ifdef LDAP_CAN_PARSE_URLS
850                 /*
851                  *      Split original server value out into URI, server and port
852                  *      so whatever initialization function we use later will have
853                  *      the server information in the format it needs.
854                  */
855                 if (ldap_is_ldap_url(value)) {
856                         LDAPURLDesc     *ldap_url;
857                         bool            set_port_maybe = true;
858                         int             default_port = LDAP_PORT;
859                         char            *p;
860
861                         if (ldap_url_parse(value, &ldap_url)){
862                                 cf_log_err_cs(conf, "Parsing LDAP URL \"%s\" failed", value);
863                         ldap_url_error:
864                                 ldap_free_urldesc(ldap_url);
865                                 return -1;
866                         }
867
868                         if (ldap_url->lud_dn) {
869                                 cf_log_err_cs(conf, "Base DN cannot be specified via server URL");
870                                 goto ldap_url_error;
871                         }
872
873                         if (ldap_url->lud_attrs && ldap_url->lud_attrs[0]) {
874                                 cf_log_err_cs(conf, "Attribute list cannot be specified via server URL");
875                                 goto ldap_url_error;
876                         }
877
878                         /*
879                          *      ldap_url_parse sets this to base by default.
880                          */
881                         if (ldap_url->lud_scope != LDAP_SCOPE_BASE) {
882                                 cf_log_err_cs(conf, "Scope cannot be specified via server URL");
883                                 goto ldap_url_error;
884                         }
885                         ldap_url->lud_scope = -1;       /* Otherwise LDAP adds ?base */
886
887                         /*
888                          *      The public ldap_url_parse function sets the default
889                          *      port, so we have to discover whether a port was
890                          *      included ourselves.
891                          */
892                         if ((p = strchr(value, ']')) && (p[1] == ':')) {                        /* IPv6 */
893                                 set_port_maybe = false;
894                         } else if ((p = strchr(value, ':')) && (p = strchr(p + 1, ':'))) {      /* IPv4 */
895                                 set_port_maybe = false;
896                         }
897
898                         /* We allow extensions */
899
900 #  ifdef HAVE_LDAP_INITIALIZE
901                         {
902                                 char *url;
903
904                                 /*
905                                  *      Figure out the default port from the URL
906                                  */
907                                 if (ldap_url->lud_scheme) {
908                                         if (strcmp(ldap_url->lud_scheme, "ldaps") == 0) {
909                                                 if (inst->start_tls == true) {
910                                                         cf_log_err_cs(conf, "ldaps:// scheme is not compatible "
911                                                                       "with 'start_tls'");
912                                                         goto ldap_url_error;
913                                                 }
914                                                 default_port = LDAPS_PORT;
915
916                                         } else if (strcmp(ldap_url->lud_scheme, "ldapi") == 0) {
917                                                 set_port_maybe = false; /* Unix socket, no port */
918                                         }
919                                 }
920
921                                 if (set_port_maybe) {
922                                         /*
923                                          *      URL port overrides configured port.
924                                          */
925                                         ldap_url->lud_port = inst->port;
926
927                                         /*
928                                          *      If there's no URL port, then set it to the default
929                                          *      this is so debugging messages show explicitly
930                                          *      the port we're connecting to.
931                                          */
932                                         if (!ldap_url->lud_port) ldap_url->lud_port = default_port;
933                                 }
934
935                                 url = ldap_url_desc2str(ldap_url);
936                                 if (!url) {
937                                         cf_log_err_cs(conf, "Failed recombining URL components");
938                                         goto ldap_url_error;
939                                 }
940                                 inst->server = talloc_asprintf_append(inst->server, "%s ", url);
941                                 free(url);
942                         }
943 #  else
944                         /*
945                          *      No LDAP initialize function.  Can't specify a scheme.
946                          */
947                         if (ldap_url->lud_scheme &&
948                             ((strcmp(ldap_url->lud_scheme, "ldaps") == 0) ||
949                             (strcmp(ldap_url->lud_scheme, "ldapi") == 0) ||
950                             (strcmp(ldap_url->lud_scheme, "cldap") == 0))) {
951                                 cf_log_err_cs(conf, "%s is not supported by linked libldap",
952                                               ldap_url->lud_scheme);
953                                 return -1;
954                         }
955
956                         /*
957                          *      URL port over-rides the configured
958                          *      port.  But if there's no configured
959                          *      port, we use the hard-coded default.
960                          */
961                         if (set_port_maybe) {
962                                 ldap_url->lud_port = inst->port;
963                                 if (!ldap_url->lud_port) ldap_url->lud_port = default_port;
964                         }
965
966                         inst->server = talloc_asprintf_append(inst->server, "%s:%i ",
967                                                               ldap_url->lud_host ? ldap_url->lud_host : "localhost",
968                                                               ldap_url->lud_port);
969 #  endif
970                         /*
971                          *      @todo We could set a few other top level
972                          *      directives using the URL, like base_dn
973                          *      and scope.
974                          */
975                         ldap_free_urldesc(ldap_url);
976                 /*
977                  *      We need to construct an LDAP URI
978                  */
979                 } else
980 #endif  /* HAVE_LDAP_URL_PARSE && HAVE_LDAP_IS_LDAP_URL && LDAP_URL_DESC2STR */
981                 /*
982                  *      If it's not an URL, or we don't have the functions necessary
983                  *      to break apart the URL and recombine it, then just treat
984                  *      server as a hostname.
985                  */
986                 {
987 #ifdef HAVE_LDAP_INITIALIZE
988                         char    const *p;
989                         char    *q;
990                         int     port = 0;
991                         size_t  len;
992
993                         port = inst->port;
994
995                         /*
996                          *      We don't support URLs if the library didn't provide
997                          *      URL parsing functions.
998                          */
999                         if (strchr(value, '/')) {
1000                         bad_server_fmt:
1001 #ifdef LDAP_CAN_PARSE_URLS
1002                                 cf_log_err_cp(cp, "Invalid server value, must be in format <server>[:<port>] or "
1003                                               "an ldap URI (ldap|cldap|ldaps|ldapi)://<server>:<port>");
1004 #else
1005                                 cf_log_err_cp(cp, "Invalid server value, must be in format <server>[:<port>]");
1006 #endif
1007                                 return -1;
1008                         }
1009
1010                         p = strrchr(value, ':');
1011                         if (p) {
1012                                 port = (int)strtol((p + 1), &q, 10);
1013                                 if ((p == value) || ((p + 1) == q) || (*q != '\0')) goto bad_server_fmt;
1014                                 len = p - value;
1015                         } else {
1016                                 len = strlen(value);
1017                         }
1018                         if (port == 0) port = LDAP_PORT;
1019
1020                         inst->server = talloc_asprintf_append(inst->server, "ldap://%.*s:%i ", (int) len, value, port);
1021 #else
1022                         /*
1023                          *      ldap_init takes port, which can be overridden by :port so
1024                          *      we don't need to do any parsing here.
1025                          */
1026                         inst->server = talloc_asprintf_append(inst->server, "%s ", value);
1027 #endif
1028                 }
1029         }
1030         if (inst->server) inst->server[talloc_array_length(inst->server) - 2] = '\0';
1031
1032         DEBUG4("LDAP server string: %s", inst->server);
1033
1034 #ifdef LDAP_OPT_X_TLS_NEVER
1035         /*
1036          *      Workaround for servers which support LDAPS but not START TLS
1037          */
1038         if (inst->port == LDAPS_PORT || inst->tls_mode) {
1039                 inst->tls_mode = LDAP_OPT_X_TLS_HARD;
1040         } else {
1041                 inst->tls_mode = 0;
1042         }
1043 #endif
1044
1045         /*
1046          *      Convert dereference strings to enumerated constants
1047          */
1048         if (inst->dereference_str) {
1049                 inst->dereference = fr_str2int(ldap_dereference, inst->dereference_str, -1);
1050                 if (inst->dereference < 0) {
1051                         cf_log_err_cs(conf, "Invalid 'dereference' value \"%s\", expected 'never', 'searching', "
1052                                       "'finding' or 'always'", inst->dereference_str);
1053                         goto error;
1054                 }
1055         }
1056
1057 #if LDAP_SET_REBIND_PROC_ARGS != 3
1058         /*
1059          *      The 2-argument rebind doesn't take an instance variable.  Our rebind function needs the instance
1060          *      variable for the username, password, etc.
1061          */
1062         if (inst->rebind == true) {
1063                 cf_log_err_cs(conf, "Cannot use 'rebind' configuration item as this version of libldap "
1064                               "does not support the API that we need");
1065
1066                 goto error;
1067         }
1068 #endif
1069
1070         /*
1071          *      Convert scope strings to enumerated constants
1072          */
1073         inst->userobj_scope = fr_str2int(ldap_scope, inst->userobj_scope_str, -1);
1074         if (inst->userobj_scope < 0) {
1075                 cf_log_err_cs(conf, "Invalid 'user.scope' value \"%s\", expected 'sub', 'one'"
1076 #ifdef LDAP_SCOPE_CHILDREN
1077                               ", 'base' or 'children'"
1078 #else
1079                               " or 'base'"
1080 #endif
1081                          , inst->userobj_scope_str);
1082                 goto error;
1083         }
1084
1085         inst->groupobj_scope = fr_str2int(ldap_scope, inst->groupobj_scope_str, -1);
1086         if (inst->groupobj_scope < 0) {
1087                 cf_log_err_cs(conf, "Invalid 'group.scope' value \"%s\", expected 'sub', 'one'"
1088 #ifdef LDAP_SCOPE_CHILDREN
1089                               ", 'base' or 'children'"
1090 #else
1091                               " or 'base'"
1092 #endif
1093                          , inst->groupobj_scope_str);
1094                 goto error;
1095         }
1096
1097         inst->clientobj_scope = fr_str2int(ldap_scope, inst->clientobj_scope_str, -1);
1098         if (inst->clientobj_scope < 0) {
1099                 cf_log_err_cs(conf, "Invalid 'client.scope' value \"%s\", expected 'sub', 'one'"
1100 #ifdef LDAP_SCOPE_CHILDREN
1101                               ", 'base' or 'children'"
1102 #else
1103                               " or 'base'"
1104 #endif
1105                          , inst->clientobj_scope_str);
1106                 goto error;
1107         }
1108
1109 #ifdef HAVE_LDAP_CREATE_SORT_CONTROL
1110         /*
1111          *      Build the server side sort control for user objects
1112          */
1113         if (inst->userobj_sort_by) {
1114                 LDAPSortKey     **keys;
1115                 int             ret;
1116                 char            *p;
1117
1118                 memcpy(&p, &inst->userobj_sort_by, sizeof(p));
1119
1120                 ret = ldap_create_sort_keylist(&keys, p);
1121                 if (ret != LDAP_SUCCESS) {
1122                         cf_log_err_cs(conf, "Invalid user.sort_by value \"%s\": %s",
1123                                       inst->userobj_sort_by, ldap_err2string(ret));
1124                         goto error;
1125                 }
1126
1127                 /*
1128                  *      Always set the control as critical, if it's not needed
1129                  *      the user can comment it out...
1130                  */
1131                 ret = ldap_create_sort_control(inst->handle, keys, 1, &inst->userobj_sort_ctrl);
1132                 ldap_free_sort_keylist(keys);
1133                 if (ret != LDAP_SUCCESS) {
1134                         LDAP_ERR("Failed creating server sort control: %s", ldap_err2string(ret));
1135                         goto error;
1136                 }
1137         }
1138 #endif
1139
1140         if (inst->tls_require_cert_str) {
1141 #ifdef LDAP_OPT_X_TLS_NEVER
1142                 /*
1143                  *      Convert cert strictness to enumerated constants
1144                  */
1145                 inst->tls_require_cert = fr_str2int(ldap_tls_require_cert, inst->tls_require_cert_str, -1);
1146                 if (inst->tls_require_cert < 0) {
1147                         cf_log_err_cs(conf, "Invalid 'tls.require_cert' value \"%s\", expected 'never', "
1148                                       "'demand', 'allow', 'try' or 'hard'", inst->tls_require_cert_str);
1149                         goto error;
1150                 }
1151 #else
1152                 cf_log_err_cs(conf, "Modifying 'tls.require_cert' is not supported by current "
1153                               "version of libldap. Please upgrade or substitute current libldap and "
1154                               "rebuild this module");
1155
1156                 goto error;
1157 #endif
1158         }
1159
1160         /*
1161          *      Build the attribute map
1162          */
1163         update = cf_section_sub_find(inst->cs, "update");
1164         if (update && (map_afrom_cs(&inst->user_map, update,
1165                                     PAIR_LIST_REPLY, PAIR_LIST_REQUEST, rlm_ldap_map_verify, inst,
1166                                     LDAP_MAX_ATTRMAP) < 0)) {
1167                 return -1;
1168         }
1169
1170         /*
1171          *      Initialize the socket pool.
1172          */
1173         inst->pool = fr_connection_pool_module_init(inst->cs, inst, mod_conn_create, NULL, NULL);
1174         if (!inst->pool) goto error;
1175
1176         /*
1177          *      Bulk load dynamic clients.
1178          */
1179         if (inst->do_clients) {
1180                 CONF_SECTION *cs, *map, *tmpl;
1181
1182                 cs = cf_section_sub_find(inst->cs, "client");
1183                 if (!cs) {
1184                         cf_log_err_cs(conf, "Told to load clients but no client section found");
1185                         goto error;
1186                 }
1187
1188                 map = cf_section_sub_find(cs, "attribute");
1189                 if (!map) {
1190                         cf_log_err_cs(cs, "Told to load clients but no attribute section found");
1191                         goto error;
1192                 }
1193
1194                 tmpl = cf_section_sub_find(cs, "template");
1195
1196                 if (rlm_ldap_client_load(inst, tmpl, map) < 0) {
1197                         cf_log_err_cs(cs, "Error loading clients");
1198
1199                         return -1;
1200                 }
1201         }
1202
1203         return 0;
1204
1205 error:
1206         return -1;
1207 }
1208
1209 static rlm_rcode_t mod_authenticate(void *instance, REQUEST *request) CC_HINT(nonnull);
1210 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
1211 {
1212         rlm_rcode_t     rcode;
1213         ldap_rcode_t    status;
1214         char const      *dn;
1215         rlm_ldap_t      *inst = instance;
1216         ldap_handle_t   *conn;
1217
1218         char            sasl_mech_buff[LDAP_MAX_DN_STR_LEN];
1219         char            sasl_proxy_buff[LDAP_MAX_DN_STR_LEN];
1220         char            sasl_realm_buff[LDAP_MAX_DN_STR_LEN];
1221         ldap_sasl       sasl;
1222
1223         /*
1224          * Ensure that we're being passed a plain-text password, and not
1225          * anything else.
1226          */
1227
1228         if (!request->username) {
1229                 REDEBUG("Attribute \"User-Name\" is required for authentication");
1230
1231                 return RLM_MODULE_INVALID;
1232         }
1233
1234         if (!request->password ||
1235             (request->password->da->attr != PW_USER_PASSWORD)) {
1236                 RWDEBUG("You have set \"Auth-Type := LDAP\" somewhere");
1237                 RWDEBUG("*********************************************");
1238                 RWDEBUG("* THAT CONFIGURATION IS WRONG.  DELETE IT.   ");
1239                 RWDEBUG("* YOU ARE PREVENTING THE SERVER FROM WORKING");
1240                 RWDEBUG("*********************************************");
1241
1242                 REDEBUG("Attribute \"User-Password\" is required for authentication");
1243
1244                 return RLM_MODULE_INVALID;
1245         }
1246
1247         if (request->password->vp_length == 0) {
1248                 REDEBUG("Empty password supplied");
1249
1250                 return RLM_MODULE_INVALID;
1251         }
1252
1253         conn = mod_conn_get(inst, request);
1254         if (!conn) return RLM_MODULE_FAIL;
1255
1256         /*
1257          *      Expand dynamic SASL fields
1258          */
1259         if (conn->inst->user_sasl.mech) {
1260                 memset(&sasl, 0, sizeof(sasl));
1261
1262                 if (tmpl_expand(&sasl.mech, sasl_mech_buff, sizeof(sasl_mech_buff), request,
1263                                 conn->inst->user_sasl.mech, rlm_ldap_escape_func, inst) < 0) {
1264                         REDEBUG("Failed expanding user.sasl.mech: %s", fr_strerror());
1265                         rcode = RLM_MODULE_FAIL;
1266                         goto finish;
1267                 }
1268
1269                 if (conn->inst->user_sasl.proxy) {
1270                         if (tmpl_expand(&sasl.proxy, sasl_proxy_buff, sizeof(sasl_proxy_buff), request,
1271                                         conn->inst->user_sasl.proxy, rlm_ldap_escape_func, inst) < 0) {
1272                                 REDEBUG("Failed expanding user.sasl.proxy: %s", fr_strerror());
1273                                 rcode = RLM_MODULE_FAIL;
1274                                 goto finish;
1275                         }
1276                 }
1277
1278                 if (conn->inst->user_sasl.realm) {
1279                         if (tmpl_expand(&sasl.realm, sasl_realm_buff, sizeof(sasl_realm_buff), request,
1280                                         conn->inst->user_sasl.realm, rlm_ldap_escape_func, inst) < 0) {
1281                                 REDEBUG("Failed expanding user.sasl.realm: %s", fr_strerror());
1282                                 rcode = RLM_MODULE_FAIL;
1283                                 goto finish;
1284                         }
1285                 }
1286         }
1287
1288         RDEBUG("Login attempt by \"%s\"", request->username->vp_strvalue);
1289
1290         /*
1291          *      Get the DN by doing a search.
1292          */
1293         dn = rlm_ldap_find_user(inst, request, &conn, NULL, false, NULL, &rcode);
1294         if (!dn) {
1295                 mod_conn_release(inst, conn);
1296
1297                 return rcode;
1298         }
1299         conn->rebound = true;
1300         status = rlm_ldap_bind(inst, request, &conn, dn, request->password->vp_strvalue,
1301                                conn->inst->user_sasl.mech ? &sasl : NULL, true);
1302         switch (status) {
1303         case LDAP_PROC_SUCCESS:
1304                 rcode = RLM_MODULE_OK;
1305                 RDEBUG("Bind as user \"%s\" was successful", dn);
1306                 break;
1307
1308         case LDAP_PROC_NOT_PERMITTED:
1309                 rcode = RLM_MODULE_USERLOCK;
1310                 break;
1311
1312         case LDAP_PROC_REJECT:
1313                 rcode = RLM_MODULE_REJECT;
1314                 break;
1315
1316         case LDAP_PROC_BAD_DN:
1317                 rcode = RLM_MODULE_INVALID;
1318                 break;
1319
1320         case LDAP_PROC_NO_RESULT:
1321                 rcode = RLM_MODULE_NOTFOUND;
1322                 break;
1323
1324         default:
1325                 rcode = RLM_MODULE_FAIL;
1326                 break;
1327         };
1328
1329 finish:
1330         mod_conn_release(inst, conn);
1331
1332         return rcode;
1333 }
1334
1335 /** Search for and apply an LDAP profile
1336  *
1337  * LDAP profiles are mapped using the same attribute map as user objects, they're used to add common sets of attributes
1338  * to the request.
1339  *
1340  * @param[in] inst rlm_ldap configuration.
1341  * @param[in] request Current request.
1342  * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect.
1343  * @param[in] dn of profile object to apply.
1344  * @param[in] expanded Structure containing a list of xlat expanded attribute names and mapping information.
1345  * @return One of the RLM_MODULE_* values.
1346  */
1347 static rlm_rcode_t rlm_ldap_map_profile(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn,
1348                                         char const *dn, rlm_ldap_map_exp_t const *expanded)
1349 {
1350         rlm_rcode_t     rcode = RLM_MODULE_OK;
1351         ldap_rcode_t    status;
1352         LDAPMessage     *result = NULL, *entry = NULL;
1353         int             ldap_errno;
1354         LDAP            *handle = (*pconn)->handle;
1355         char const      *filter;
1356         char            filter_buff[LDAP_MAX_FILTER_STR_LEN];
1357
1358         rad_assert(inst->profile_filter);       /* We always have a default filter set */
1359
1360         if (!dn || !*dn) return RLM_MODULE_OK;
1361
1362         if (tmpl_expand(&filter, filter_buff, sizeof(filter_buff), request,
1363                         inst->profile_filter, rlm_ldap_escape_func, NULL) < 0) {
1364                 REDEBUG("Failed creating profile filter");
1365
1366                 return RLM_MODULE_INVALID;
1367         }
1368
1369         status = rlm_ldap_search(&result, inst, request, pconn, dn,
1370                                  LDAP_SCOPE_BASE, filter, expanded->attrs, NULL, NULL);
1371         switch (status) {
1372         case LDAP_PROC_SUCCESS:
1373                 break;
1374
1375         case LDAP_PROC_BAD_DN:
1376         case LDAP_PROC_NO_RESULT:
1377                 RDEBUG("Profile object \"%s\" not found", dn);
1378                 return RLM_MODULE_NOTFOUND;
1379
1380         default:
1381                 return RLM_MODULE_FAIL;
1382         }
1383
1384         rad_assert(*pconn);
1385         rad_assert(result);
1386
1387         entry = ldap_first_entry(handle, result);
1388         if (!entry) {
1389                 ldap_get_option(handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
1390                 REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno));
1391
1392                 rcode = RLM_MODULE_NOTFOUND;
1393
1394                 goto free_result;
1395         }
1396
1397         RDEBUG("Processing profile attributes");
1398         if (rlm_ldap_map_do(inst, request, handle, expanded, entry) > 0) rcode = RLM_MODULE_UPDATED;
1399
1400 free_result:
1401         ldap_msgfree(result);
1402
1403         return rcode;
1404 }
1405
1406 static rlm_rcode_t mod_authorize(void *instance, REQUEST *request) CC_HINT(nonnull);
1407 static rlm_rcode_t mod_authorize(void *instance, REQUEST *request)
1408 {
1409         rlm_rcode_t             rcode = RLM_MODULE_OK;
1410         ldap_rcode_t            status;
1411         int                     ldap_errno;
1412         int                     i;
1413         rlm_ldap_t              *inst = instance;
1414         struct berval           **values;
1415         VALUE_PAIR              *vp;
1416         ldap_handle_t           *conn;
1417         LDAPMessage             *result, *entry;
1418         char const              *dn = NULL;
1419         rlm_ldap_map_exp_t      expanded; /* faster than mallocing every time */
1420
1421         /*
1422          *      Don't be tempted to add a check for request->username
1423          *      or request->password here. rlm_ldap.authorize can be used for
1424          *      many things besides searching for users.
1425          */
1426
1427         if (rlm_ldap_map_expand(&expanded, request, inst->user_map) < 0) return RLM_MODULE_FAIL;
1428
1429         conn = mod_conn_get(inst, request);
1430         if (!conn) return RLM_MODULE_FAIL;
1431
1432         /*
1433          *      Add any additional attributes we need for checking access, memberships, and profiles
1434          */
1435         if (inst->userobj_access_attr) {
1436                 expanded.attrs[expanded.count++] = inst->userobj_access_attr;
1437         }
1438
1439         if (inst->userobj_membership_attr && (inst->cacheable_group_dn || inst->cacheable_group_name)) {
1440                 expanded.attrs[expanded.count++] = inst->userobj_membership_attr;
1441         }
1442
1443         if (inst->profile_attr) {
1444                 expanded.attrs[expanded.count++] = inst->profile_attr;
1445         }
1446
1447         if (inst->valuepair_attr) {
1448                 expanded.attrs[expanded.count++] = inst->valuepair_attr;
1449         }
1450
1451         expanded.attrs[expanded.count] = NULL;
1452
1453         dn = rlm_ldap_find_user(inst, request, &conn, expanded.attrs, true, &result, &rcode);
1454         if (!dn) {
1455                 goto finish;
1456         }
1457
1458         entry = ldap_first_entry(conn->handle, result);
1459         if (!entry) {
1460                 ldap_get_option(conn->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
1461                 REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno));
1462
1463                 goto finish;
1464         }
1465
1466         /*
1467          *      Check for access.
1468          */
1469         if (inst->userobj_access_attr) {
1470                 rcode = rlm_ldap_check_access(inst, request, conn, entry);
1471                 if (rcode != RLM_MODULE_OK) {
1472                         goto finish;
1473                 }
1474         }
1475
1476         /*
1477          *      Check if we need to cache group memberships
1478          */
1479         if (inst->cacheable_group_dn || inst->cacheable_group_name) {
1480                 if (inst->userobj_membership_attr) {
1481                         rcode = rlm_ldap_cacheable_userobj(inst, request, &conn, entry, inst->userobj_membership_attr);
1482                         if (rcode != RLM_MODULE_OK) {
1483                                 goto finish;
1484                         }
1485                 }
1486
1487                 rcode = rlm_ldap_cacheable_groupobj(inst, request, &conn);
1488                 if (rcode != RLM_MODULE_OK) {
1489                         goto finish;
1490                 }
1491         }
1492
1493 #ifdef WITH_EDIR
1494         /*
1495          *      We already have a Cleartext-Password.  Skip edir.
1496          */
1497         if (fr_pair_find_by_num(request->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY)) {
1498                 goto skip_edir;
1499         }
1500
1501         /*
1502          *      Retrieve Universal Password if we use eDirectory
1503          */
1504         if (inst->edir) {
1505                 int res = 0;
1506                 char password[256];
1507                 size_t pass_size = sizeof(password);
1508
1509                 /*
1510                  *      Retrive universal password
1511                  */
1512                 res = nmasldap_get_password(conn->handle, dn, password, &pass_size);
1513                 if (res != 0) {
1514                         REDEBUG("Failed to retrieve eDirectory password: (%i) %s", res, edir_errstr(res));
1515                         rcode = RLM_MODULE_FAIL;
1516
1517                         goto finish;
1518                 }
1519
1520                 /*
1521                  *      Add Cleartext-Password attribute to the request
1522                  */
1523                 vp = radius_pair_create(request, &request->config, PW_CLEARTEXT_PASSWORD, 0);
1524                 fr_pair_value_strcpy(vp, password);
1525                 vp->vp_length = pass_size;
1526
1527                 if (RDEBUG_ENABLED3) {
1528                         RDEBUG3("Added eDirectory password.  control:%s += '%s'", vp->da->name, vp->vp_strvalue);
1529                 } else {
1530                         RDEBUG2("Added eDirectory password");
1531                 }
1532
1533                 if (inst->edir_autz) {
1534                         RDEBUG2("Binding as user for eDirectory authorization checks");
1535                         /*
1536                          *      Bind as the user
1537                          */
1538                         conn->rebound = true;
1539                         status = rlm_ldap_bind(inst, request, &conn, dn, vp->vp_strvalue, NULL, true);
1540                         switch (status) {
1541                         case LDAP_PROC_SUCCESS:
1542                                 rcode = RLM_MODULE_OK;
1543                                 RDEBUG("Bind as user '%s' was successful", dn);
1544                                 break;
1545
1546                         case LDAP_PROC_NOT_PERMITTED:
1547                                 rcode = RLM_MODULE_USERLOCK;
1548                                 goto finish;
1549
1550                         case LDAP_PROC_REJECT:
1551                                 rcode = RLM_MODULE_REJECT;
1552                                 goto finish;
1553
1554                         case LDAP_PROC_BAD_DN:
1555                                 rcode = RLM_MODULE_INVALID;
1556                                 goto finish;
1557
1558                         case LDAP_PROC_NO_RESULT:
1559                                 rcode = RLM_MODULE_NOTFOUND;
1560                                 goto finish;
1561
1562                         default:
1563                                 rcode = RLM_MODULE_FAIL;
1564                                 goto finish;
1565                         };
1566                 }
1567         }
1568
1569 skip_edir:
1570 #endif
1571
1572         /*
1573          *      Apply ONE user profile, or a default user profile.
1574          */
1575         if (inst->default_profile) {
1576                 char const *profile;
1577                 char profile_buff[1024];
1578
1579                 if (tmpl_expand(&profile, profile_buff, sizeof(profile_buff),
1580                                 request, inst->default_profile, NULL, NULL) < 0) {
1581                         REDEBUG("Failed creating default profile string");
1582
1583                         rcode = RLM_MODULE_INVALID;
1584                         goto finish;
1585                 }
1586
1587                 switch (rlm_ldap_map_profile(inst, request, &conn, profile, &expanded)) {
1588                 case RLM_MODULE_INVALID:
1589                         rcode = RLM_MODULE_INVALID;
1590                         goto finish;
1591
1592                 case RLM_MODULE_FAIL:
1593                         rcode = RLM_MODULE_FAIL;
1594                         goto finish;
1595
1596                 case RLM_MODULE_UPDATED:
1597                         rcode = RLM_MODULE_UPDATED;
1598                         /* FALL-THROUGH */
1599                 default:
1600                         break;
1601                 }
1602         }
1603
1604         /*
1605          *      Apply a SET of user profiles.
1606          */
1607         if (inst->profile_attr) {
1608                 values = ldap_get_values_len(conn->handle, entry, inst->profile_attr);
1609                 if (values != NULL) {
1610                         for (i = 0; values[i] != NULL; i++) {
1611                                 char *value;
1612
1613                                 value = rlm_ldap_berval_to_string(request, values[i]);
1614                                 rlm_ldap_map_profile(inst, request, &conn, value, &expanded);
1615                                 talloc_free(value);
1616                         }
1617                         ldap_value_free_len(values);
1618                 }
1619         }
1620
1621         if (inst->user_map || inst->valuepair_attr) {
1622                 RDEBUG("Processing user attributes");
1623                 if (rlm_ldap_map_do(inst, request, conn->handle, &expanded, entry) > 0) rcode = RLM_MODULE_UPDATED;
1624                 rlm_ldap_check_reply(inst, request);
1625         }
1626
1627 finish:
1628         talloc_free(expanded.ctx);
1629         if (result) ldap_msgfree(result);
1630         mod_conn_release(inst, conn);
1631
1632         return rcode;
1633 }
1634
1635 /** Modify user's object in LDAP
1636  *
1637  * Process a modifcation map to update a user object in the LDAP directory.
1638  *
1639  * @param inst rlm_ldap instance.
1640  * @param request Current request.
1641  * @param section that holds the map to process.
1642  * @return one of the RLM_MODULE_* values.
1643  */
1644 static rlm_rcode_t user_modify(rlm_ldap_t *inst, REQUEST *request, ldap_acct_section_t *section)
1645 {
1646         rlm_rcode_t     rcode = RLM_MODULE_OK;
1647         ldap_rcode_t    status;
1648
1649         ldap_handle_t   *conn = NULL;
1650
1651         LDAPMod         *mod_p[LDAP_MAX_ATTRMAP + 1], mod_s[LDAP_MAX_ATTRMAP];
1652         LDAPMod         **modify = mod_p;
1653
1654         char            *passed[LDAP_MAX_ATTRMAP * 2];
1655         int             i, total = 0, last_pass = 0;
1656
1657         char            *expanded[LDAP_MAX_ATTRMAP];
1658         int             last_exp = 0;
1659
1660         char const      *attr;
1661         char const      *value;
1662
1663         char const      *dn;
1664         /*
1665          *      Build our set of modifications using the update sections in
1666          *      the config.
1667          */
1668         CONF_ITEM       *ci;
1669         CONF_PAIR       *cp;
1670         CONF_SECTION    *cs;
1671         FR_TOKEN        op;
1672         char            path[MAX_STRING_LEN];
1673
1674         char            *p = path;
1675
1676         rad_assert(section);
1677
1678         /*
1679          *      Locate the update section were going to be using
1680          */
1681         if (section->reference[0] != '.') {
1682                 *p++ = '.';
1683         }
1684
1685         if (radius_xlat(p, (sizeof(path) - (p - path)) - 1, request, section->reference, NULL, NULL) < 0) {
1686                 goto error;
1687         }
1688
1689         ci = cf_reference_item(NULL, section->cs, path);
1690         if (!ci) {
1691                 goto error;
1692         }
1693
1694         if (!cf_item_is_section(ci)){
1695                 REDEBUG("Reference must resolve to a section");
1696
1697                 goto error;
1698         }
1699
1700         cs = cf_section_sub_find(cf_item_to_section(ci), "update");
1701         if (!cs) {
1702                 REDEBUG("Section must contain 'update' subsection");
1703
1704                 goto error;
1705         }
1706
1707         /*
1708          *      Iterate over all the pairs, building our mods array
1709          */
1710         for (ci = cf_item_find_next(cs, NULL); ci != NULL; ci = cf_item_find_next(cs, ci)) {
1711                 bool do_xlat = false;
1712
1713                 if (total == LDAP_MAX_ATTRMAP) {
1714                         REDEBUG("Modify map size exceeded");
1715
1716                         goto error;
1717                 }
1718
1719                 if (!cf_item_is_pair(ci)) {
1720                         REDEBUG("Entry is not in \"ldap-attribute = value\" format");
1721
1722                         goto error;
1723                 }
1724
1725                 /*
1726                  *      Retrieve all the information we need about the pair
1727                  */
1728                 cp = cf_item_to_pair(ci);
1729                 value = cf_pair_value(cp);
1730                 attr = cf_pair_attr(cp);
1731                 op = cf_pair_operator(cp);
1732
1733                 if (!value || (*value == '\0')) {
1734                         RDEBUG("Empty value string, skipping attribute \"%s\"", attr);
1735
1736                         continue;
1737                 }
1738
1739                 switch (cf_pair_value_type(cp)) {
1740                 case T_BARE_WORD:
1741                 case T_SINGLE_QUOTED_STRING:
1742                         break;
1743
1744                 case T_BACK_QUOTED_STRING:
1745                 case T_DOUBLE_QUOTED_STRING:
1746                         do_xlat = true;
1747                         break;
1748
1749                 default:
1750                         rad_assert(0);
1751                         goto error;
1752                 }
1753
1754                 if (op == T_OP_CMP_FALSE) {
1755                         passed[last_pass] = NULL;
1756                 } else if (do_xlat) {
1757                         char *exp = NULL;
1758
1759                         if (radius_axlat(&exp, request, value, NULL, NULL) <= 0) {
1760                                 RDEBUG("Skipping attribute \"%s\"", attr);
1761
1762                                 talloc_free(exp);
1763
1764                                 continue;
1765                         }
1766
1767                         expanded[last_exp++] = exp;
1768                         passed[last_pass] = exp;
1769                 /*
1770                  *      Static strings
1771                  */
1772                 } else {
1773                         memcpy(&(passed[last_pass]), &value, sizeof(passed[last_pass]));
1774                 }
1775
1776                 passed[last_pass + 1] = NULL;
1777
1778                 mod_s[total].mod_values = &(passed[last_pass]);
1779
1780                 last_pass += 2;
1781
1782                 switch (op) {
1783                 /*
1784                  *  T_OP_EQ is *NOT* supported, it is impossible to
1785                  *  support because of the lack of transactions in LDAP
1786                  */
1787                 case T_OP_ADD:
1788                         mod_s[total].mod_op = LDAP_MOD_ADD;
1789                         break;
1790
1791                 case T_OP_SET:
1792                         mod_s[total].mod_op = LDAP_MOD_REPLACE;
1793                         break;
1794
1795                 case T_OP_SUB:
1796                 case T_OP_CMP_FALSE:
1797                         mod_s[total].mod_op = LDAP_MOD_DELETE;
1798                         break;
1799
1800 #ifdef LDAP_MOD_INCREMENT
1801                 case T_OP_INCRM:
1802                         mod_s[total].mod_op = LDAP_MOD_INCREMENT;
1803                         break;
1804 #endif
1805                 default:
1806                         REDEBUG("Operator '%s' is not supported for LDAP modify operations",
1807                                 fr_int2str(fr_tokens, op, "<INVALID>"));
1808
1809                         goto error;
1810                 }
1811
1812                 /*
1813                  *      Now we know the value is ok, copy the pointers into
1814                  *      the ldapmod struct.
1815                  */
1816                 memcpy(&(mod_s[total].mod_type), &attr, sizeof(mod_s[total].mod_type));
1817
1818                 mod_p[total] = &(mod_s[total]);
1819                 total++;
1820         }
1821
1822         if (total == 0) {
1823                 rcode = RLM_MODULE_NOOP;
1824                 goto release;
1825         }
1826
1827         mod_p[total] = NULL;
1828
1829         conn = mod_conn_get(inst, request);
1830         if (!conn) return RLM_MODULE_FAIL;
1831
1832
1833         dn = rlm_ldap_find_user(inst, request, &conn, NULL, false, NULL, &rcode);
1834         if (!dn || (rcode != RLM_MODULE_OK)) {
1835                 goto error;
1836         }
1837
1838         status = rlm_ldap_modify(inst, request, &conn, dn, modify);
1839         switch (status) {
1840         case LDAP_PROC_SUCCESS:
1841                 break;
1842
1843         case LDAP_PROC_REJECT:
1844         case LDAP_PROC_BAD_DN:
1845                 rcode = RLM_MODULE_INVALID;
1846                 break;
1847
1848         default:
1849                 rcode = RLM_MODULE_FAIL;
1850                 break;
1851         };
1852
1853         release:
1854         error:
1855         /*
1856          *      Free up any buffers we allocated for xlat expansion
1857          */
1858         for (i = 0; i < last_exp; i++) {
1859                 talloc_free(expanded[i]);
1860         }
1861
1862         mod_conn_release(inst, conn);
1863
1864         return rcode;
1865 }
1866
1867 static rlm_rcode_t mod_accounting(void *instance, REQUEST *request) CC_HINT(nonnull);
1868 static rlm_rcode_t mod_accounting(void *instance, REQUEST *request)
1869 {
1870         rlm_ldap_t *inst = instance;
1871
1872         if (inst->accounting) return user_modify(inst, request, inst->accounting);
1873
1874         return RLM_MODULE_NOOP;
1875 }
1876
1877 static rlm_rcode_t mod_post_auth(void *instance, REQUEST *request) CC_HINT(nonnull);
1878 static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *request)
1879 {
1880         rlm_ldap_t *inst = instance;
1881
1882         if (inst->postauth) {
1883                 return user_modify(inst, request, inst->postauth);
1884         }
1885
1886         return RLM_MODULE_NOOP;
1887 }
1888
1889
1890 /* globally exported name */
1891 extern module_t rlm_ldap;
1892 module_t rlm_ldap = {
1893         .magic          = RLM_MODULE_INIT,
1894         .name           = "ldap",
1895         .inst_size      = sizeof(rlm_ldap_t),
1896         .config         = module_config,
1897         .bootstrap      = mod_bootstrap,
1898         .instantiate    = mod_instantiate,
1899         .detach         = mod_detach,
1900         .methods = {
1901                 [MOD_AUTHENTICATE]      = mod_authenticate,
1902                 [MOD_AUTHORIZE]         = mod_authorize,
1903                 [MOD_ACCOUNTING]        = mod_accounting,
1904                 [MOD_POST_AUTH]         = mod_post_auth
1905         },
1906 };