1/*
2 * lib/kdb/kdb_ldap/ldap_principal.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 2008 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_principal.h"
38#include "princ_xdr.h"
39#include "ldap_err.h"
40#include <libintl.h>
41
42struct timeval timelimit = {300, 0};  /* 5 minutes */
43char     *principal_attributes[] = { "krbprincipalname",
44				     "objectclass",
45				     "krbprincipalkey",
46				     "krbmaxrenewableage",
47				     "krbmaxticketlife",
48				     "krbticketflags",
49				     "krbprincipalexpiration",
50				     "krbticketpolicyreference",
51				     "krbUpEnabled",
52				     "krbpwdpolicyreference",
53				     "krbpasswordexpiration",
54                                     "krbLastFailedAuth",
55                                     "krbLoginFailedCount",
56                                     "krbLastSuccessfulAuth",
57#ifdef HAVE_EDIRECTORY
58				     "loginexpirationtime",
59				     "logindisabled",
60#endif
61				     "loginexpirationtime",
62				     "logindisabled",
63				     "modifytimestamp",
64				     "krbLastPwdChange",
65				     "krbExtraData",
66				     "krbObjectReferences",
67				     NULL };
68
69/* Must match KDB_*_ATTR macros in ldap_principal.h.  */
70static char *attributes_set[] = { "krbmaxticketlife",
71				  "krbmaxrenewableage",
72				  "krbticketflags",
73				  "krbprincipalexpiration",
74				  "krbticketpolicyreference",
75				  "krbUpEnabled",
76				  "krbpwdpolicyreference",
77				  "krbpasswordexpiration",
78				  "krbprincipalkey",
79                                  "krblastpwdchange",
80                                  "krbextradata",
81                                  "krbLastSuccessfulAuth",
82                                  "krbLastFailedAuth",
83                                  "krbLoginFailedCount",
84				  NULL };
85
86void
87krb5_dbe_free_contents(context, entry)
88    krb5_context 	 context;
89    krb5_db_entry 	*entry;
90{
91    krb5_tl_data 	*tl_data_next=NULL;
92    krb5_tl_data 	*tl_data=NULL;
93    int i, j;
94
95    if (entry->e_data)
96	free(entry->e_data);
97    if (entry->princ)
98	krb5_free_principal(context, entry->princ);
99    for (tl_data = entry->tl_data; tl_data; tl_data = tl_data_next) {
100	tl_data_next = tl_data->tl_data_next;
101	if (tl_data->tl_data_contents)
102	    free(tl_data->tl_data_contents);
103	free(tl_data);
104    }
105    if (entry->key_data) {
106	for (i = 0; i < entry->n_key_data; i++) {
107	    for (j = 0; j < entry->key_data[i].key_data_ver; j++) {
108		if (entry->key_data[i].key_data_length[j]) {
109		    if (entry->key_data[i].key_data_contents[j]) {
110			memset(entry->key_data[i].key_data_contents[j],
111			       0,
112			       (unsigned) entry->key_data[i].key_data_length[j]);
113			free (entry->key_data[i].key_data_contents[j]);
114		    }
115		}
116		entry->key_data[i].key_data_contents[j] = NULL;
117		entry->key_data[i].key_data_length[j] = 0;
118		entry->key_data[i].key_data_type[j] = 0;
119	    }
120	}
121	free(entry->key_data);
122    }
123    memset(entry, 0, sizeof(*entry));
124    return;
125}
126
127
128krb5_error_code
129krb5_ldap_free_principal(kcontext , entries, nentries)
130    krb5_context  kcontext;
131    krb5_db_entry *entries;
132    int           nentries;
133{
134    register int i;
135    for (i = 0; i < nentries; i++)
136	krb5_dbe_free_contents(kcontext, &entries[i]);
137    return 0;
138}
139
140krb5_error_code
141krb5_ldap_iterate(context, match_expr, func, func_arg, db_args)
142    krb5_context           context;
143    char                   *match_expr;
144    krb5_error_code        (*func) (krb5_pointer, krb5_db_entry *);
145    krb5_pointer           func_arg;
146    /* Solaris Kerberos: adding support for -rev/recurse flags */
147    char                   **db_args;
148{
149    krb5_db_entry            entry;
150    krb5_principal           principal;
151    char                     **subtree=NULL, *princ_name=NULL, *realm=NULL, **values=NULL, *filter=NULL;
152    unsigned int             filterlen=0, tree=0, ntree=1, i=0;
153    krb5_error_code          st=0, tempst=0;
154    LDAP                     *ld=NULL;
155    LDAPMessage              *result=NULL, *ent=NULL;
156    kdb5_dal_handle          *dal_handle=NULL;
157    krb5_ldap_context        *ldap_context=NULL;
158    krb5_ldap_server_handle  *ldap_server_handle=NULL;
159    char                     *default_match_expr = "*";
160
161    /* Clear the global error string */
162    krb5_clear_error_message(context);
163
164    /* Solaris Kerberos: adding support for -rev/recurse flags */
165    if (db_args) {
166	/* LDAP does not support db_args DB arguments for krb5_ldap_iterate */
167	krb5_set_error_message(context, EINVAL,
168			       gettext("Unsupported argument \"%s\" for ldap"),
169			       db_args[0]);
170	return EINVAL;
171    }
172
173    memset(&entry, 0, sizeof(krb5_db_entry));
174    SETUP_CONTEXT();
175
176    realm = ldap_context->lrparams->realm_name;
177    if (realm == NULL) {
178	realm = context->default_realm;
179	if (realm == NULL) {
180	    st = EINVAL;
181	    krb5_set_error_message(context, st, gettext("Default realm not set"));
182	    goto cleanup;
183	}
184    }
185
186    /*
187     * If no match_expr then iterate through all krb princs like the db2 plugin
188     */
189    if (match_expr == NULL)
190	match_expr = default_match_expr;
191
192    filterlen = strlen(FILTER) + strlen(match_expr) + 2 + 1;  /* 2 for closing brackets */
193    filter = malloc (filterlen);
194    CHECK_NULL(filter);
195    memset(filter, 0, filterlen);
196    /*LINTED*/
197    sprintf(filter, FILTER"%s))", match_expr);
198
199    if ((st = krb5_get_subtree_info(ldap_context, &subtree, &ntree)) != 0)
200	goto cleanup;
201
202    GET_HANDLE();
203
204    for (tree=0; tree < ntree; ++tree) {
205
206	LDAP_SEARCH(subtree[tree], ldap_context->lrparams->search_scope, filter, principal_attributes);
207	for (ent=ldap_first_entry(ld, result); ent != NULL; ent=ldap_next_entry(ld, ent)) {
208	    if ((values=ldap_get_values(ld, ent, "krbprincipalname")) != NULL) {
209		for (i=0; values[i] != NULL; ++i) {
210		    if (values[i])
211		    if (krb5_ldap_parse_principal_name(values[i], &princ_name) != 0)
212			continue;
213		    if (krb5_parse_name(context, princ_name, &principal) != 0)
214			continue;
215		    if (is_principal_in_realm(ldap_context, principal) == 0) {
216			if ((st = populate_krb5_db_entry(context, ldap_context, ld, ent, principal,
217				    &entry)) != 0)
218			    goto cleanup;
219			(*func)(func_arg, &entry);
220			krb5_dbe_free_contents(context, &entry);
221			(void) krb5_free_principal(context, principal);
222			if (princ_name)
223			    free(princ_name);
224			break;
225		    }
226		    (void) krb5_free_principal(context, principal);
227		    if (princ_name)
228			free(princ_name);
229		}
230		ldap_value_free(values);
231	    }
232	} /* end of for (ent= ... */
233	ldap_msgfree(result);
234    } /* end of for (tree= ... */
235
236cleanup:
237    if (filter)
238	free (filter);
239
240    for (;ntree; --ntree)
241	if (subtree[ntree-1])
242	    free (subtree[ntree-1]);
243
244    /* Solaris Kerberos: fix memory leak */
245    if (subtree != NULL) {
246	free(subtree);
247    }
248    krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle);
249    return st;
250}
251
252
253/*
254 * delete a principal from the directory.
255 */
256krb5_error_code
257krb5_ldap_delete_principal(context, searchfor, nentries)
258    krb5_context context;
259    krb5_const_principal searchfor;
260    int *nentries;		/* how many found & deleted */
261{
262    char                      *user=NULL, *DN=NULL, *strval[10] = {NULL};
263    LDAPMod                   **mods=NULL;
264    LDAP                      *ld=NULL;
265    int 	              j=0, ptype=0, pcount=0;
266    unsigned int	      attrsetmask=0;
267    krb5_error_code           st=0;
268    krb5_boolean              singleentry=FALSE;
269    KEY                       *secretkey=NULL;
270    kdb5_dal_handle           *dal_handle=NULL;
271    krb5_ldap_context         *ldap_context=NULL;
272    krb5_ldap_server_handle   *ldap_server_handle=NULL;
273    krb5_db_entry             entries;
274    krb5_boolean              more=0;
275
276    /* Clear the global error string */
277    krb5_clear_error_message(context);
278
279    SETUP_CONTEXT();
280    /* get the principal info */
281    if ((st=krb5_ldap_get_principal(context, searchfor, &entries, nentries, &more)) != 0 || *nentries == 0)
282	goto cleanup;
283
284    if (((st=krb5_get_princ_type(context, &entries, &(ptype))) != 0) ||
285	((st=krb5_get_attributes_mask(context, &entries, &(attrsetmask))) != 0) ||
286	((st=krb5_get_princ_count(context, &entries, &(pcount))) != 0) ||
287	((st=krb5_get_userdn(context, &entries, &(DN))) != 0))
288	goto cleanup;
289
290    if (DN == NULL) {
291	st = EINVAL;
292	krb5_set_error_message(context, st, gettext("DN information missing"));
293	goto cleanup;
294    }
295
296    GET_HANDLE();
297
298    if (ptype == KDB_STANDALONE_PRINCIPAL_OBJECT) {
299        st = ldap_delete_ext_s(ld, DN, NULL, NULL);
300        if (st != LDAP_SUCCESS) {
301            st = set_ldap_error (context, st, OP_DEL);
302            goto cleanup;
303        }
304    } else {
305	if (((st=krb5_unparse_name(context, searchfor, &user)) != 0)
306	    || ((st=krb5_ldap_unparse_principal_name(user)) != 0))
307	    goto cleanup;
308
309	memset(strval, 0, sizeof(strval));
310	strval[0] = user;
311	if ((st=krb5_add_str_mem_ldap_mod(&mods, "krbprincipalname", LDAP_MOD_DELETE,
312					  strval)) != 0)
313	    goto cleanup;
314
315	singleentry = (pcount == 1) ? TRUE: FALSE;
316	if (singleentry == FALSE) {
317	    if (secretkey != NULL) {
318		if ((st=krb5_add_ber_mem_ldap_mod(&mods, "krbprincipalkey", LDAP_MOD_DELETE | LDAP_MOD_BVALUES,
319						  secretkey->keys)) != 0)
320		    goto cleanup;
321	    }
322	} else {
323	    /*
324	     * If the Kerberos user principal to be deleted happens to be the last one associated
325	     * with the directory user object, then it is time to delete the other kerberos
326	     * specific attributes like krbmaxticketlife, i.e, unkerberize the directory user.
327	     * From the attrsetmask value, identify the attributes set on the directory user
328	     * object and delete them.
329	     * NOTE: krbsecretkey attribute has per principal entries. There can be chances that the
330	     * other principals' keys are exisiting/left-over. So delete all the values.
331	     */
332	    while (attrsetmask) {
333		if (attrsetmask & 1) {
334		    if ((st=krb5_add_str_mem_ldap_mod(&mods, attributes_set[j], LDAP_MOD_DELETE,
335						      NULL)) != 0)
336			goto cleanup;
337		}
338		attrsetmask >>= 1;
339		++j;
340	    }
341
342	    /* the same should be done with the objectclass attributes */
343	    {
344		char *attrvalues[] = {"krbticketpolicyaux", "krbprincipalaux", NULL};
345/*		char *attrvalues[] = {"krbpwdpolicyrefaux", "krbticketpolicyaux", "krbprincipalaux", NULL};  */
346		int p, q, r=0, amask=0;
347
348		if ((st=checkattributevalue(ld, DN, "objectclass", attrvalues, &amask)) != 0)
349		    goto cleanup;
350		memset(strval, 0, sizeof(strval));
351		for (p=1, q=0; p<=4; p<<=1, ++q)
352		    if (p & amask)
353			strval[r++] = attrvalues[q];
354		strval[r] = NULL;
355		if (r > 0) {
356		    if ((st=krb5_add_str_mem_ldap_mod(&mods, "objectclass", LDAP_MOD_DELETE,
357						      strval)) != 0)
358			goto cleanup;
359		}
360	    }
361	}
362	st=ldap_modify_ext_s(ld, DN, mods, NULL, NULL);
363	if (st != LDAP_SUCCESS) {
364	    st = set_ldap_error(context, st, OP_MOD);
365	    goto cleanup;
366	}
367    }
368
369cleanup:
370    if (user)
371	free (user);
372
373    if (DN)
374	free (DN);
375
376    if (secretkey != NULL) {
377	int i=0;
378	while (i < secretkey->nkey) {
379	    free (secretkey->keys[i]->bv_val);
380	    free (secretkey->keys[i]);
381	    ++i;
382	}
383	free (secretkey->keys);
384	free (secretkey);
385    }
386
387    if (st == 0)
388	krb5_ldap_free_principal(context, &entries, *nentries);
389
390    ldap_mods_free(mods, 1);
391    krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle);
392    return st;
393}
394
395
396/*
397 * Function: krb5_ldap_unparse_principal_name
398 *
399 * Purpose: Removes '\\' that comes before every occurence of '@'
400 *          in the principal name component.
401 *
402 * Arguments:
403 *       user_name     (input/output)      Principal name
404 *
405 */
406
407krb5_error_code
408krb5_ldap_unparse_principal_name(char *user_name)
409{
410    char *tmp_princ_name=NULL, *princ_name=NULL, *tmp=NULL;
411    int l=0;
412    krb5_error_code st=0;
413
414    if (strstr(user_name, "\\@")) {
415
416	tmp_princ_name = strdup(user_name);
417	if (!tmp_princ_name) {
418	    st = ENOMEM;
419	    goto cleanup;
420	}
421	tmp = tmp_princ_name;
422
423	princ_name = (char *) malloc (strlen(user_name));
424	if (!princ_name) {
425	    st = ENOMEM;
426	    goto cleanup;
427	}
428	memset(princ_name, 0, strlen(user_name));
429
430	l = 0;
431	while (*tmp_princ_name) {
432	    if ((*tmp_princ_name == '\\') && (*(tmp_princ_name+1) == '@')) {
433		tmp_princ_name += 1;
434	    } else {
435		*(princ_name + l) = *tmp_princ_name++;
436		l++;
437	    }
438	}
439
440	memset(user_name, 0, strlen(user_name));
441	/*LINTED*/
442	sprintf(user_name, "%s", princ_name);
443    }
444
445cleanup:
446    if (tmp) {
447	free(tmp);
448	tmp = NULL;
449    }
450
451    if (princ_name) {
452	free(princ_name);
453	princ_name = NULL;
454    }
455
456    return st;
457}
458
459
460/*
461 * Function: krb5_ldap_parse_principal_name
462 *
463 * Purpose: Inserts '\\' before every occurence of '@'
464 *          in the principal name component.
465 *
466 * Arguments:
467 *       i_princ_name     (input)      Principal name without '\\'
468 *       o_princ_name     (output)     Principal name with '\\'
469 *
470 * Note: The caller has to free the memory allocated for o_princ_name.
471 */
472
473krb5_error_code
474krb5_ldap_parse_principal_name(i_princ_name, o_princ_name)
475    char              *i_princ_name;
476    char              **o_princ_name;
477{
478    char *tmp_princ_name = NULL, *princ_name = NULL, *at_rlm_name = NULL;
479    int l = 0, m = 0, tmp_princ_name_len = 0, princ_name_len = 0, at_count = 0;
480    krb5_error_code st = 0;
481
482    at_rlm_name = strrchr(i_princ_name, '@');
483
484    if (!at_rlm_name) {
485	*o_princ_name = strdup(i_princ_name);
486	if (!o_princ_name) {
487	    st = ENOMEM;
488	    goto cleanup;
489	}
490    } else {
491	tmp_princ_name_len = at_rlm_name - i_princ_name;
492
493	tmp_princ_name = (char *) malloc ((unsigned) tmp_princ_name_len + 1);
494	if (!tmp_princ_name) {
495	    st = ENOMEM;
496	    goto cleanup;
497	}
498	memset(tmp_princ_name, 0, (unsigned) tmp_princ_name_len + 1);
499	memcpy(tmp_princ_name, i_princ_name, (unsigned) tmp_princ_name_len);
500
501	l = 0;
502	while (tmp_princ_name[l]) {
503	    if (tmp_princ_name[l++] == '@')
504		at_count++;
505	}
506
507	princ_name_len = strlen(i_princ_name) + at_count + 1;
508	princ_name = (char *) malloc ((unsigned) princ_name_len);
509	if (!princ_name) {
510	    st = ENOMEM;
511	    goto cleanup;
512	}
513	memset(princ_name, 0, (unsigned) princ_name_len);
514
515	l = 0;
516	m = 0;
517	while (tmp_princ_name[l]) {
518	    if (tmp_princ_name[l] == '@') {
519		princ_name[m++]='\\';
520	    }
521	    princ_name[m++]=tmp_princ_name[l++];
522	}
523	/* Solaris Kerberos: using strlcat for safety */
524	strlcat(princ_name, at_rlm_name, princ_name_len);
525
526	*o_princ_name = princ_name;
527    }
528
529cleanup:
530
531    if (tmp_princ_name) {
532	free(tmp_princ_name);
533	tmp_princ_name = NULL;
534    }
535
536    return st;
537}
538