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 
42 struct timeval timelimit = {300, 0};  /* 5 minutes */
43 char     *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.  */
70 static 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 
86 void
krb5_dbe_free_contents(context,entry)87 krb5_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 
128 krb5_error_code
krb5_ldap_free_principal(kcontext,entries,nentries)129 krb5_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 
140 krb5_error_code
krb5_ldap_iterate(context,match_expr,func,func_arg,db_args)141 krb5_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 
236 cleanup:
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  */
256 krb5_error_code
krb5_ldap_delete_principal(context,searchfor,nentries)257 krb5_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 
369 cleanup:
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 
407 krb5_error_code
krb5_ldap_unparse_principal_name(char * user_name)408 krb5_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 
445 cleanup:
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 
473 krb5_error_code
krb5_ldap_parse_principal_name(i_princ_name,o_princ_name)474 krb5_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 
529 cleanup:
530 
531     if (tmp_princ_name) {
532 	free(tmp_princ_name);
533 	tmp_princ_name = NULL;
534     }
535 
536     return st;
537 }
538