xref: /freebsd/crypto/krb5/src/plugins/kdb/ldap/libkdb_ldap/ldap_pwd_policy.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* plugins/kdb/ldap/libkdb_ldap/ldap_pwd_policy.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  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
32  * Use is subject to license terms.
33  */
34 
35 #include "ldap_main.h"
36 #include "kdb_ldap.h"
37 #include "ldap_pwd_policy.h"
38 #include "ldap_err.h"
39 
40 static char *password_policy_attributes[] = { "cn", "krbmaxpwdlife", "krbminpwdlife",
41                                               "krbpwdmindiffchars", "krbpwdminlength",
42                                               "krbpwdhistorylength", "krbpwdmaxfailure",
43                                               "krbpwdfailurecountinterval",
44                                               "krbpwdlockoutduration",
45                                               "krbpwdattributes",
46                                               "krbpwdmaxlife",
47                                               "krbpwdmaxrenewablelife",
48                                               "krbpwdallowedkeysalts", NULL };
49 
50 /* Fill in mods with LDAP operations for the fields of policy, using the
51  * modification type op.  mods must be freed by the caller on error. */
52 static krb5_error_code
add_policy_mods(krb5_context context,LDAPMod *** mods,osa_policy_ent_t policy,int op)53 add_policy_mods(krb5_context context, LDAPMod ***mods, osa_policy_ent_t policy,
54                 int op)
55 {
56     krb5_error_code st;
57     char *strval[2] = { NULL };
58 
59     st = krb5_add_int_mem_ldap_mod(mods, "krbmaxpwdlife", op,
60                                    (int)policy->pw_max_life);
61     if (st)
62         return st;
63 
64     st = krb5_add_int_mem_ldap_mod(mods, "krbminpwdlife", op,
65                                    (int)policy->pw_min_life);
66     if (st)
67         return st;
68 
69     st = krb5_add_int_mem_ldap_mod(mods, "krbpwdmindiffchars", op,
70                                    (int)policy->pw_min_classes);
71     if (st)
72         return st;
73 
74     st = krb5_add_int_mem_ldap_mod(mods, "krbpwdminlength", op,
75                                    (int)policy->pw_min_length);
76     if (st)
77         return st;
78 
79     st = krb5_add_int_mem_ldap_mod(mods, "krbpwdhistorylength", op,
80                                    (int)policy->pw_history_num);
81     if (st)
82         return st;
83 
84     st = krb5_add_int_mem_ldap_mod(mods, "krbpwdmaxfailure", op,
85                                    (int)policy->pw_max_fail);
86     if (st)
87         return st;
88 
89     st = krb5_add_int_mem_ldap_mod(mods, "krbpwdfailurecountinterval", op,
90                                    (int)policy->pw_failcnt_interval);
91     if (st)
92         return st;
93 
94     st = krb5_add_int_mem_ldap_mod(mods, "krbpwdlockoutduration", op,
95                                    (int)policy->pw_lockout_duration);
96     if (st)
97         return st;
98 
99     st = krb5_add_int_mem_ldap_mod(mods, "krbpwdattributes", op,
100                                    (int)policy->attributes);
101     if (st)
102         return st;
103 
104     st = krb5_add_int_mem_ldap_mod(mods, "krbpwdmaxlife", op,
105                                    (int)policy->max_life);
106     if (st)
107         return st;
108 
109     st = krb5_add_int_mem_ldap_mod(mods, "krbpwdmaxrenewablelife", op,
110                                    (int)policy->max_renewable_life);
111     if (st)
112         return st;
113 
114     if (policy->allowed_keysalts != NULL) {
115         strval[0] = policy->allowed_keysalts;
116         st = krb5_add_str_mem_ldap_mod(mods, "krbpwdallowedkeysalts",
117                                        op, strval);
118         if (st)
119             return st;
120     }
121 
122     /*
123      * Each policy tl-data type we add should be explicitly marshalled here.
124      * Unlike principals, we do not marshal unrecognized policy tl-data.
125      */
126 
127     return 0;
128 }
129 
130 /*
131  * Function to create password policy object.
132  */
133 
134 krb5_error_code
krb5_ldap_create_password_policy(krb5_context context,osa_policy_ent_t policy)135 krb5_ldap_create_password_policy(krb5_context context, osa_policy_ent_t policy)
136 {
137     krb5_error_code             st=0;
138     LDAP                        *ld=NULL;
139     LDAPMod                     **mods={NULL};
140     kdb5_dal_handle             *dal_handle=NULL;
141     krb5_ldap_context           *ldap_context=NULL;
142     krb5_ldap_server_handle     *ldap_server_handle=NULL;
143     char                        *strval[2]={NULL}, *policy_dn=NULL;
144 
145     /* Clear the global error string */
146     krb5_clear_error_message(context);
147 
148     /* validate the input parameters */
149     if (policy == NULL || policy->name == NULL)
150         return EINVAL;
151 
152     SETUP_CONTEXT();
153     GET_HANDLE();
154 
155     st = krb5_ldap_name_to_policydn (context, policy->name, &policy_dn);
156     if (st != 0)
157         goto cleanup;
158 
159     strval[0] = policy->name;
160     if ((st=krb5_add_str_mem_ldap_mod(&mods, "cn", LDAP_MOD_ADD, strval)) != 0)
161         goto cleanup;
162 
163     strval[0] = "krbPwdPolicy";
164     if ((st=krb5_add_str_mem_ldap_mod(&mods, "objectclass", LDAP_MOD_ADD, strval)) != 0)
165         goto cleanup;
166 
167     st = add_policy_mods(context, &mods, policy, LDAP_MOD_ADD);
168     if (st)
169         goto cleanup;
170 
171     /* password policy object creation */
172     if ((st=ldap_add_ext_s(ld, policy_dn, mods, NULL, NULL)) != LDAP_SUCCESS) {
173         st = set_ldap_error (context, st, OP_ADD);
174         goto cleanup;
175     }
176 
177 cleanup:
178     free(policy_dn);
179     ldap_mods_free(mods, 1);
180     krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle);
181     return(st);
182 }
183 
184 /*
185  * Function to modify password policy object.
186  */
187 
188 krb5_error_code
krb5_ldap_put_password_policy(krb5_context context,osa_policy_ent_t policy)189 krb5_ldap_put_password_policy(krb5_context context, osa_policy_ent_t policy)
190 {
191     char                        *policy_dn=NULL;
192     krb5_error_code             st=0;
193     LDAP                        *ld=NULL;
194     LDAPMod                     **mods=NULL;
195     kdb5_dal_handle             *dal_handle=NULL;
196     krb5_ldap_context           *ldap_context=NULL;
197     krb5_ldap_server_handle     *ldap_server_handle=NULL;
198 
199     /* Clear the global error string */
200     krb5_clear_error_message(context);
201 
202     /* validate the input parameters */
203     if (policy == NULL || policy->name == NULL)
204         return EINVAL;
205 
206     SETUP_CONTEXT();
207     GET_HANDLE();
208 
209     st = krb5_ldap_name_to_policydn (context, policy->name, &policy_dn);
210     if (st != 0)
211         goto cleanup;
212 
213     st = add_policy_mods(context, &mods, policy, LDAP_MOD_REPLACE);
214     if (st)
215         goto cleanup;
216 
217     /* modify the password policy object. */
218     /*
219      * This will fail if the 'policy_dn' is anywhere other than under the realm
220      * container. This is correct behaviour. 'kdb5_ldap_util' will support
221      * management of only such policy objects.
222      */
223     if ((st=ldap_modify_ext_s(ld, policy_dn, mods, NULL, NULL)) != LDAP_SUCCESS) {
224         st = set_ldap_error (context, st, OP_MOD);
225         goto cleanup;
226     }
227 
228 cleanup:
229     free(policy_dn);
230     ldap_mods_free(mods, 1);
231     krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle);
232     return(st);
233 }
234 
235 static void
get_ui4(LDAP * ld,LDAPMessage * ent,char * name,krb5_ui_4 * out)236 get_ui4(LDAP *ld, LDAPMessage *ent, char *name, krb5_ui_4 *out)
237 {
238     int val;
239 
240     krb5_ldap_get_value(ld, ent, name, &val);
241     *out = val;
242 }
243 
244 static krb5_error_code
populate_policy(krb5_context context,LDAP * ld,LDAPMessage * ent,char * pol_name,osa_policy_ent_t pol_entry)245 populate_policy(krb5_context context,
246                 LDAP *ld,
247                 LDAPMessage *ent,
248                 char *pol_name,
249                 osa_policy_ent_t pol_entry)
250 {
251     int st = 0;
252 
253     pol_entry->name = strdup(pol_name);
254     CHECK_NULL(pol_entry->name);
255     pol_entry->version = 1;
256 
257     get_ui4(ld, ent, "krbmaxpwdlife", &pol_entry->pw_max_life);
258     get_ui4(ld, ent, "krbminpwdlife", &pol_entry->pw_min_life);
259     get_ui4(ld, ent, "krbpwdmindiffchars", &pol_entry->pw_min_classes);
260     get_ui4(ld, ent, "krbpwdminlength", &pol_entry->pw_min_length);
261     get_ui4(ld, ent, "krbpwdhistorylength", &pol_entry->pw_history_num);
262     get_ui4(ld, ent, "krbpwdmaxfailure", &pol_entry->pw_max_fail);
263     get_ui4(ld, ent, "krbpwdfailurecountinterval",
264             &pol_entry->pw_failcnt_interval);
265     get_ui4(ld, ent, "krbpwdlockoutduration", &pol_entry->pw_lockout_duration);
266     get_ui4(ld, ent, "krbpwdattributes", &pol_entry->attributes);
267     get_ui4(ld, ent, "krbpwdmaxlife", &pol_entry->max_life);
268     get_ui4(ld, ent, "krbpwdmaxrenewablelife", &pol_entry->max_renewable_life);
269 
270     st = krb5_ldap_get_string(ld, ent, "krbpwdallowedkeysalts",
271                               &(pol_entry->allowed_keysalts), NULL);
272     if (st)
273         goto cleanup;
274     /*
275      * We don't store the policy refcnt, because principals might be maintained
276      * outside of kadmin.  Instead, we will check for principal references when
277      * policies are deleted.
278      */
279     pol_entry->policy_refcnt = 0;
280 
281 cleanup:
282     return st;
283 }
284 
285 static krb5_error_code
krb5_ldap_get_password_policy_from_dn(krb5_context context,char * pol_name,char * pol_dn,osa_policy_ent_t * policy)286 krb5_ldap_get_password_policy_from_dn(krb5_context context, char *pol_name,
287                                       char *pol_dn, osa_policy_ent_t *policy)
288 {
289     krb5_error_code             st=0, tempst=0;
290     LDAP                        *ld=NULL;
291     LDAPMessage                 *result=NULL,*ent=NULL;
292     kdb5_dal_handle             *dal_handle=NULL;
293     krb5_ldap_context           *ldap_context=NULL;
294     krb5_ldap_server_handle     *ldap_server_handle=NULL;
295 
296     /* Clear the global error string */
297     krb5_clear_error_message(context);
298 
299     /* validate the input parameters */
300     if (pol_dn == NULL)
301         return EINVAL;
302 
303     *policy = NULL;
304     SETUP_CONTEXT();
305     GET_HANDLE();
306 
307     *(policy) = (osa_policy_ent_t) malloc(sizeof(osa_policy_ent_rec));
308     if (*policy == NULL) {
309         st = ENOMEM;
310         goto cleanup;
311     }
312     memset(*policy, 0, sizeof(osa_policy_ent_rec));
313 
314     LDAP_SEARCH(pol_dn, LDAP_SCOPE_BASE, "(objectclass=krbPwdPolicy)", password_policy_attributes);
315 
316     ent=ldap_first_entry(ld, result);
317     if (ent == NULL) {
318         st = KRB5_KDB_NOENTRY;
319         goto cleanup;
320     }
321     st = populate_policy(context, ld, ent, pol_name, *policy);
322 
323 cleanup:
324     ldap_msgfree(result);
325     if (st != 0) {
326         if (*policy != NULL) {
327             krb5_db_free_policy(context, *policy);
328             *policy = NULL;
329         }
330     }
331 
332     krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle);
333     return st;
334 }
335 
336 /*
337  * Convert 'name' into a directory DN and call
338  * 'krb5_ldap_get_password_policy_from_dn'
339  */
340 krb5_error_code
krb5_ldap_get_password_policy(krb5_context context,char * name,osa_policy_ent_t * policy)341 krb5_ldap_get_password_policy(krb5_context context, char *name,
342                               osa_policy_ent_t *policy)
343 {
344     krb5_error_code             st = 0;
345     char                        *policy_dn = NULL;
346 
347     /* Clear the global error string */
348     krb5_clear_error_message(context);
349 
350     /* validate the input parameters */
351     if (name == NULL) {
352         st = EINVAL;
353         goto cleanup;
354     }
355 
356     st = krb5_ldap_name_to_policydn(context, name, &policy_dn);
357     if (st != 0)
358         goto cleanup;
359 
360     st = krb5_ldap_get_password_policy_from_dn(context, name, policy_dn,
361                                                policy);
362 
363 cleanup:
364     free(policy_dn);
365     return st;
366 }
367 
368 krb5_error_code
krb5_ldap_delete_password_policy(krb5_context context,char * policy)369 krb5_ldap_delete_password_policy(krb5_context context, char *policy)
370 {
371     int                         mask = 0;
372     char                        *policy_dn = NULL, *class[] = {"krbpwdpolicy", NULL};
373     krb5_error_code             st=0;
374     LDAP                        *ld=NULL;
375     kdb5_dal_handle             *dal_handle=NULL;
376     krb5_ldap_context           *ldap_context=NULL;
377     krb5_ldap_server_handle     *ldap_server_handle=NULL;
378 
379     /* Clear the global error string */
380     krb5_clear_error_message(context);
381 
382     /* validate the input parameters */
383     if (policy == NULL)
384         return EINVAL;
385 
386     SETUP_CONTEXT();
387     GET_HANDLE();
388 
389     st = krb5_ldap_name_to_policydn (context, policy, &policy_dn);
390     if (st != 0)
391         goto cleanup;
392 
393     /* Ensure that the object is a password policy */
394     if ((st=checkattributevalue(ld, policy_dn, "objectclass", class, &mask)) != 0)
395         goto cleanup;
396 
397     if (mask == 0) {
398         st = KRB5_KDB_NOENTRY;
399         goto cleanup;
400     }
401 
402     if ((st=ldap_delete_ext_s(ld, policy_dn, NULL, NULL)) != LDAP_SUCCESS) {
403         st = set_ldap_error (context, st, OP_DEL);
404         goto cleanup;
405     }
406 
407 cleanup:
408     krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle);
409     free(policy_dn);
410 
411     return st;
412 }
413 
414 krb5_error_code
krb5_ldap_iterate_password_policy(krb5_context context,char * match_expr,void (* func)(krb5_pointer,osa_policy_ent_t),krb5_pointer func_arg)415 krb5_ldap_iterate_password_policy(krb5_context context, char *match_expr,
416                                   void (*func)(krb5_pointer, osa_policy_ent_t),
417                                   krb5_pointer func_arg)
418 {
419     osa_policy_ent_rec          *entry=NULL;
420     char                        *policy=NULL;
421     krb5_error_code             st=0, tempst=0;
422     LDAP                        *ld=NULL;
423     LDAPMessage                 *result=NULL, *ent=NULL;
424     kdb5_dal_handle             *dal_handle=NULL;
425     krb5_ldap_context           *ldap_context=NULL;
426     krb5_ldap_server_handle     *ldap_server_handle=NULL;
427 
428     /* Clear the global error string */
429     krb5_clear_error_message(context);
430 
431     SETUP_CONTEXT();
432     GET_HANDLE();
433 
434     if (ldap_context->lrparams->realmdn == NULL) {
435         st = EINVAL;
436         goto cleanup;
437     }
438 
439     LDAP_SEARCH(ldap_context->lrparams->realmdn, LDAP_SCOPE_ONELEVEL, "(objectclass=krbpwdpolicy)", password_policy_attributes);
440     for (ent=ldap_first_entry(ld, result); ent != NULL; ent=ldap_next_entry(ld, ent)) {
441         krb5_boolean attr_present;
442 
443         st = krb5_ldap_get_string(ld, ent, "cn", &policy, &attr_present);
444         if (st != 0)
445             goto cleanup;
446         if (attr_present == FALSE)
447             continue;
448 
449         entry = (osa_policy_ent_t) malloc(sizeof(osa_policy_ent_rec));
450         CHECK_NULL(entry);
451         memset(entry, 0, sizeof(osa_policy_ent_rec));
452         if ((st = populate_policy(context, ld, ent, policy, entry)) != 0)
453             goto cleanup;
454 
455         (*func)(func_arg, entry);
456         krb5_db_free_policy(context, entry);
457         entry = NULL;
458 
459         free(policy);
460         policy = NULL;
461     }
462 
463 cleanup:
464     free(entry);
465     free(policy);
466     ldap_msgfree(result);
467     krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle);
468     return st;
469 }
470