1 /*
2  * lib/kdb/kdb_ldap/ldap_services.c
3  *
4  * Copyright (c) 2004-2005, Novell, Inc.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions are met:
9  *
10  *   * Redistributions of source code must retain the above copyright notice,
11  *       this list of conditions and the following disclaimer.
12  *   * Redistributions in binary form must reproduce the above copyright
13  *       notice, this list of conditions and the following disclaimer in the
14  *       documentation and/or other materials provided with the distribution.
15  *   * The copyright holder's name is not used to endorse or promote products
16  *       derived from this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  * POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 #include "ldap_main.h"
32 #include "kdb_ldap.h"
33 #include "ldap_services.h"
34 #include "ldap_err.h"
35 #include <libintl.h>
36 
37 #if defined(HAVE_EDIRECTORY)
38 
39 static char *realmcontclass[] = {"krbRealmContainer", NULL};
40 
41 /*
42  * create the service object from Directory
43  */
44 
45 krb5_error_code
krb5_ldap_create_service(context,service,mask)46 krb5_ldap_create_service(context, service, mask)
47     krb5_context	        context;
48     krb5_ldap_service_params    *service;
49     int                         mask;
50 {
51     int                         i=0, j=0;
52     krb5_error_code             st=0;
53     LDAP                        *ld=NULL;
54     char                        **rdns=NULL, *realmattr=NULL, *strval[3]={NULL};
55     LDAPMod                     **mods=NULL;
56     kdb5_dal_handle             *dal_handle=NULL;
57     krb5_ldap_context           *ldap_context=NULL;
58     krb5_ldap_server_handle     *ldap_server_handle=NULL;
59     char                        errbuf[1024];
60 
61     /* validate the input parameter */
62     if (service == NULL || service->servicedn == NULL) {
63 	st = EINVAL;
64 	krb5_set_error_message (context, st, gettext("Service DN NULL"));
65 	goto cleanup;
66     }
67 
68     SETUP_CONTEXT();
69     GET_HANDLE();
70 
71     /* identify the class that the object should belong to. This depends on the servicetype */
72     memset(strval, 0, sizeof(strval));
73     strval[0] = "krbService";
74     if (service->servicetype == LDAP_KDC_SERVICE) {
75 	strval[1] = "krbKdcService";
76 	realmattr = "krbKdcServers";
77     } else if (service->servicetype == LDAP_ADMIN_SERVICE) {
78 	strval[1] = "krbAdmService";
79 	realmattr = "krbAdmServers";
80     } else if (service->servicetype == LDAP_PASSWD_SERVICE) {
81 	strval[1] = "krbPwdService";
82 	realmattr = "krbPwdServers";
83     } else {
84 	strval[1] = "krbKdcService";
85 	realmattr = "krbKdcServers";
86     }
87     if ((st=krb5_add_str_mem_ldap_mod(&mods, "objectclass", LDAP_MOD_ADD, strval)) != 0)
88 	goto cleanup;
89 
90     rdns = ldap_explode_dn(service->servicedn, 1);
91     if (rdns == NULL) {
92 	st = LDAP_INVALID_DN_SYNTAX;
93 	goto cleanup;
94     }
95     memset(strval, 0, sizeof(strval));
96     strval[0] = rdns[0];
97     if ((st=krb5_add_str_mem_ldap_mod(&mods, "cn", LDAP_MOD_ADD, strval)) != 0)
98 	goto cleanup;
99 
100     if (mask & LDAP_SERVICE_SERVICEFLAG) {
101 	if ((st=krb5_add_int_mem_ldap_mod(&mods, "krbserviceflags", LDAP_MOD_ADD,
102 					  service->krbserviceflags)) != 0)
103 	    goto cleanup;
104     }
105 
106     if (mask & LDAP_SERVICE_HOSTSERVER) {
107 	if (service->krbhostservers != NULL) {
108 	    if ((st=krb5_add_str_mem_ldap_mod(&mods, "krbhostserver", LDAP_MOD_ADD,
109 					      service->krbhostservers)) != 0)
110 		goto cleanup;
111 	} else {
112 	    st = EINVAL;
113 	    krb5_set_error_message (context, st, gettext("'krbhostserver' argument invalid"));
114 	    goto cleanup;
115 	}
116     }
117 
118     if (mask & LDAP_SERVICE_REALMREFERENCE) {
119 	if (service->krbrealmreferences != NULL) {
120 	    unsigned int realmmask=0;
121 
122 	    /* check for the validity of the values */
123 	    for (j=0; service->krbrealmreferences[j] != NULL; ++j) {
124 		st = checkattributevalue(ld, service->krbrealmreferences[j], "ObjectClass",
125 					 realmcontclass, &realmmask);
126 		CHECK_CLASS_VALIDITY(st, realmmask, "realm object value: ");
127 	    }
128 	    if ((st=krb5_add_str_mem_ldap_mod(&mods, "krbrealmreferences", LDAP_MOD_ADD,
129 					      service->krbrealmreferences)) != 0)
130 		goto cleanup;
131 	} else {
132 	    st = EINVAL;
133 	    krb5_set_error_message (context, st, gettext("Server has no 'krbrealmreferences'"));
134 	    goto cleanup;
135 	}
136     }
137 
138     /* ldap add operation */
139     if ((st=ldap_add_ext_s(ld, service->servicedn, mods, NULL, NULL)) != LDAP_SUCCESS) {
140 	st = set_ldap_error (context, st, OP_ADD);
141 	goto cleanup;
142     }
143 
144     /*
145      * If the service created has realm/s associated with it, then the realm should be updated
146      * to have a reference to the service object just created.
147      */
148     if (mask & LDAP_SERVICE_REALMREFERENCE) {
149 	for (i=0; service->krbrealmreferences[i]; ++i) {
150 	    if ((st=updateAttribute(ld, service->krbrealmreferences[i], realmattr,
151 				    service->servicedn)) != 0) {
152 		snprintf (errbuf, sizeof(errbuf), gettext("Error adding 'krbRealmReferences' to %s: "),
153 			 service->krbrealmreferences[i]);
154 		prepend_err_str (context, errbuf, st, st);
155 		/* delete service object, status ignored intentionally */
156 		ldap_delete_ext_s(ld, service->servicedn, NULL, NULL);
157 		goto cleanup;
158 	    }
159 	}
160     }
161 
162 cleanup:
163 
164     if (rdns)
165 	ldap_value_free (rdns);
166 
167     ldap_mods_free(mods, 1);
168     krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle);
169     return st;
170 }
171 
172 
173 /*
174  * modify the service object from Directory
175  */
176 
177 krb5_error_code
krb5_ldap_modify_service(context,service,mask)178 krb5_ldap_modify_service(context, service, mask)
179     krb5_context	        context;
180     krb5_ldap_service_params    *service;
181     int                         mask;
182 {
183     int                         i=0, j=0, count=0;
184     krb5_error_code             st=0;
185     LDAP                        *ld=NULL;
186     char                        **values=NULL, *attr[] = { "krbRealmReferences", NULL};
187     char                        *realmattr=NULL;
188     char                        **oldrealmrefs=NULL, **newrealmrefs=NULL;
189     LDAPMod                     **mods=NULL;
190     LDAPMessage                 *result=NULL, *ent=NULL;
191     kdb5_dal_handle             *dal_handle=NULL;
192     krb5_ldap_context           *ldap_context=NULL;
193     krb5_ldap_server_handle     *ldap_server_handle=NULL;
194 
195     /* validate the input parameter */
196     if (service == NULL || service->servicedn == NULL) {
197 	st = EINVAL;
198 	krb5_set_error_message (context, st, gettext("Service DN is NULL"));
199 	goto cleanup;
200     }
201 
202     SETUP_CONTEXT();
203     GET_HANDLE();
204 
205     if (mask & LDAP_SERVICE_SERVICEFLAG) {
206 	if ((st=krb5_add_int_mem_ldap_mod(&mods, "krbserviceflags", LDAP_MOD_REPLACE,
207 					  service->krbserviceflags)) != 0)
208 	    goto cleanup;
209     }
210 
211     if (mask & LDAP_SERVICE_HOSTSERVER) {
212 	if (service->krbhostservers != NULL) {
213 	    if ((st=krb5_add_str_mem_ldap_mod(&mods, "krbhostserver", LDAP_MOD_REPLACE,
214 					      service->krbhostservers)) != 0)
215 		goto cleanup;
216 	} else {
217 	    st = EINVAL;
218 	    krb5_set_error_message (context, st, gettext("'krbhostserver' value invalid"));
219 	    goto cleanup;
220 	}
221     }
222 
223     if (mask & LDAP_SERVICE_REALMREFERENCE) {
224 	if (service->krbrealmreferences != NULL) {
225 	    unsigned int realmmask=0;
226 
227 	    /* check for the validity of the values */
228 	    for (j=0; service->krbrealmreferences[j]; ++j) {
229 		st = checkattributevalue(ld, service->krbrealmreferences[j], "ObjectClass",
230 					 realmcontclass, &realmmask);
231 		CHECK_CLASS_VALIDITY(st, realmmask, "realm object value: ");
232 	    }
233 	    if ((st=krb5_add_str_mem_ldap_mod(&mods, "krbrealmreferences", LDAP_MOD_REPLACE,
234 					      service->krbrealmreferences)) != 0)
235 		goto cleanup;
236 
237 
238 	    /* get the attribute of the realm to be set */
239 	    if (service->servicetype == LDAP_KDC_SERVICE)
240 		realmattr = "krbKdcServers";
241 	    else if (service->servicetype == LDAP_ADMIN_SERVICE)
242 		realmattr = "krbAdmservers";
243 	    else if (service->servicetype == LDAP_PASSWD_SERVICE)
244 		realmattr = "krbPwdServers";
245 	    else
246 		realmattr = "krbKdcServers";
247 
248 	    /* read the existing list of krbRealmreferences. this will needed  */
249 	    if ((st = ldap_search_ext_s (ld,
250 					 service->servicedn,
251 					 LDAP_SCOPE_BASE,
252 					 0,
253 					 attr,
254 					 0,
255 					 NULL,
256 					 NULL,
257 					 NULL,
258 					 0,
259 					 &result)) != LDAP_SUCCESS) {
260 		st = set_ldap_error (context, st, OP_SEARCH);
261 		goto cleanup;
262 	    }
263 
264 	    ent = ldap_first_entry(ld, result);
265 	    if (ent) {
266 		if ((values=ldap_get_values(ld, ent, "krbRealmReferences")) != NULL) {
267 		    count = ldap_count_values(values);
268 		    if ((st=copy_arrays(values, &oldrealmrefs, count)) != 0)
269 			goto cleanup;
270 		    ldap_value_free(values);
271 		}
272 	    }
273 	    ldap_msgfree(result);
274 	} else {
275 	    st = EINVAL;
276 	    krb5_set_error_message (context, st, gettext("'krbRealmReferences' value invalid"));
277 	    goto cleanup;
278 	}
279     }
280 
281     /* ldap modify operation */
282     if ((st=ldap_modify_ext_s(ld, service->servicedn, mods, NULL, NULL)) != LDAP_SUCCESS) {
283 	st = set_ldap_error (context, st, OP_MOD);
284 	goto cleanup;
285     }
286 
287     /*
288      * If the service modified had realm/s associations changed, then the realm should be
289      * updated to reflect the changes.
290      */
291 
292     if (mask & LDAP_SERVICE_REALMREFERENCE) {
293 	/* get the count of the new list of krbrealmreferences */
294 	for (i=0; service->krbrealmreferences[i]; ++i)
295 	    ;
296 
297 	/* make a new copy of the krbrealmreferences */
298 	if ((st=copy_arrays(service->krbrealmreferences, &newrealmrefs, i)) != 0)
299 	    goto cleanup;
300 
301 	/* find the deletions/additions to the list of krbrealmreferences */
302 	if (disjoint_members(oldrealmrefs, newrealmrefs) != 0)
303 	    goto cleanup;
304 
305 	/* see if some of the attributes have to be deleted */
306 	if (oldrealmrefs) {
307 
308 	    /* update the dn represented by the attribute that is to be deleted */
309 	    for (i=0; oldrealmrefs[i]; ++i)
310 		if ((st=deleteAttribute(ld, oldrealmrefs[i], realmattr, service->servicedn)) != 0) {
311 		    prepend_err_str (context, gettext("Error deleting realm attribute:"), st, st);
312 		    goto cleanup;
313 		}
314 	}
315 
316 	/* see if some of the attributes have to be added */
317 	for (i=0; newrealmrefs[i]; ++i)
318 	    if ((st=updateAttribute(ld, newrealmrefs[i], realmattr, service->servicedn)) != 0) {
319 		prepend_err_str (context, gettext("Error updating realm attribute: "), st, st);
320 		goto cleanup;
321 	    }
322     }
323 
324 cleanup:
325 
326     if (oldrealmrefs) {
327 	for (i=0; oldrealmrefs[i]; ++i)
328 	    free (oldrealmrefs[i]);
329 	free (oldrealmrefs);
330     }
331 
332     if (newrealmrefs) {
333 	for (i=0; newrealmrefs[i]; ++i)
334 	    free (newrealmrefs[i]);
335 	free (newrealmrefs);
336     }
337 
338     ldap_mods_free(mods, 1);
339     krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle);
340     return st;
341 }
342 
343 
344 krb5_error_code
krb5_ldap_delete_service(context,service,servicedn)345 krb5_ldap_delete_service(context, service, servicedn)
346     krb5_context                context;
347     krb5_ldap_service_params    *service;
348     char                        *servicedn;
349 {
350     krb5_error_code             st = 0;
351     LDAP                        *ld=NULL;
352     kdb5_dal_handle             *dal_handle=NULL;
353     krb5_ldap_context           *ldap_context=NULL;
354     krb5_ldap_server_handle     *ldap_server_handle=NULL;
355 
356     SETUP_CONTEXT();
357     GET_HANDLE();
358 
359     st = ldap_delete_ext_s(ld, servicedn, NULL, NULL);
360     if (st != 0) {
361 	st = set_ldap_error (context, st, OP_DEL);
362     }
363 
364     /* NOTE: This should be removed now as the backlinks are going off in OpenLDAP */
365     /* time to delete krbrealmreferences. This is only for OpenLDAP */
366 #ifndef HAVE_EDIRECTORY
367     {
368 	int                         i=0;
369 	char                        *attr=NULL;
370 
371 	if (service) {
372 	    if (service->krbrealmreferences) {
373 		if (service->servicetype == LDAP_KDC_SERVICE)
374 		    attr = "krbkdcservers";
375 		else if (service->servicetype == LDAP_ADMIN_SERVICE)
376 		    attr = "krbadmservers";
377 		else if (service->servicetype == LDAP_PASSWD_SERVICE)
378 		    attr = "krbpwdservers";
379 
380 		for (i=0; service->krbrealmreferences[i]; ++i) {
381 		    deleteAttribute(ld, service->krbrealmreferences[i], attr, servicedn);
382 		}
383 	    }
384 	}
385     }
386 #endif
387 
388 cleanup:
389 
390     krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle);
391     return st;
392 }
393 
394 
395 /*
396  * This function lists service objects from Directory
397  */
398 
399 krb5_error_code
krb5_ldap_list_services(context,containerdn,services)400 krb5_ldap_list_services(context, containerdn, services)
401     krb5_context	        context;
402     char                        *containerdn;
403     char                        ***services;
404 {
405     return (krb5_ldap_list(context, services, "krbService", containerdn));
406 }
407 
408 /*
409  * This function reads the service object from Directory
410  */
411 krb5_error_code
krb5_ldap_read_service(context,servicedn,service,omask)412 krb5_ldap_read_service(context, servicedn, service, omask)
413     krb5_context	        context;
414     char                        *servicedn;
415     krb5_ldap_service_params    **service;
416     int                         *omask;
417 {
418     char                        **values=NULL;
419     int                         i=0, count=0, objectmask=0;
420     krb5_error_code             st=0, tempst=0;
421     LDAPMessage                 *result=NULL,*ent=NULL;
422     char                        *attributes[] = {"krbHostServer", "krbServiceflags",
423 						 "krbRealmReferences", "objectclass", NULL};
424     char                        *attrvalues[] = {"krbService", NULL};
425     krb5_ldap_service_params    *lservice=NULL;
426     krb5_ldap_context           *ldap_context=NULL;
427     kdb5_dal_handle             *dal_handle=NULL;
428     krb5_ldap_server_handle     *ldap_server_handle=NULL;
429     LDAP                        *ld = NULL;
430 
431     /* validate the input parameter */
432     if (servicedn == NULL) {
433 	st = EINVAL;
434 	krb5_set_error_message (context, st, gettext("Service DN NULL"));
435 	goto cleanup;
436     }
437 
438     SETUP_CONTEXT();
439     GET_HANDLE();
440 
441     *omask = 0;
442 
443     /* the policydn object should be of the krbService object class */
444     st = checkattributevalue(ld, servicedn, "objectClass", attrvalues, &objectmask);
445     CHECK_CLASS_VALIDITY(st, objectmask, "service object value: ");
446 
447     /* Initialize service structure */
448     lservice =(krb5_ldap_service_params *) calloc(1, sizeof(krb5_ldap_service_params));
449     if (lservice == NULL) {
450 	st = ENOMEM;
451 	goto cleanup;
452     }
453 
454     /* allocate tl_data structure to store MASK information */
455     lservice->tl_data = calloc (1, sizeof(*lservice->tl_data));
456     if (lservice->tl_data == NULL) {
457 	st = ENOMEM;
458 	goto cleanup;
459     }
460     lservice->tl_data->tl_data_type = KDB_TL_USER_INFO;
461 
462     LDAP_SEARCH(servicedn, LDAP_SCOPE_BASE, "(objectclass=krbService)", attributes);
463 
464     lservice->servicedn = strdup(servicedn);
465     CHECK_NULL(lservice->servicedn);
466 
467     ent=ldap_first_entry(ld, result);
468     if (ent != NULL) {
469 
470 	if ((values=ldap_get_values(ld, ent, "krbServiceFlags")) != NULL) {
471 	    lservice->krbserviceflags = atoi(values[0]);
472 	    *omask |= LDAP_SERVICE_SERVICEFLAG;
473 	    ldap_value_free(values);
474 	}
475 
476 	if ((values=ldap_get_values(ld, ent, "krbHostServer")) != NULL) {
477 	    count = ldap_count_values(values);
478 	    if ((st=copy_arrays(values, &(lservice->krbhostservers), count)) != 0)
479 		goto cleanup;
480 	    *omask |= LDAP_SERVICE_HOSTSERVER;
481 	    ldap_value_free(values);
482 	}
483 
484 	if ((values=ldap_get_values(ld, ent, "krbRealmReferences")) != NULL) {
485 	    count = ldap_count_values(values);
486 	    if ((st=copy_arrays(values, &(lservice->krbrealmreferences), count)) != 0)
487 		goto cleanup;
488 	    *omask |= LDAP_SERVICE_REALMREFERENCE;
489 	    ldap_value_free(values);
490 	}
491 
492 	if ((values=ldap_get_values(ld, ent, "objectClass")) != NULL) {
493 	    for (i=0; values[i]; ++i) {
494 		if (strcasecmp(values[i], "krbKdcService") == 0) {
495 		    lservice->servicetype = LDAP_KDC_SERVICE;
496 		    break;
497 		}
498 
499 		if (strcasecmp(values[i], "krbAdmService") == 0) {
500 		    lservice->servicetype = LDAP_ADMIN_SERVICE;
501 		    break;
502 		}
503 
504 		if (strcasecmp(values[i], "krbPwdService") == 0) {
505 		    lservice->servicetype = LDAP_PASSWD_SERVICE;
506 		    break;
507 		}
508 	    }
509 	    ldap_value_free(values);
510 	}
511     }
512     ldap_msgfree(result);
513 
514 cleanup:
515     if (st != 0) {
516 	krb5_ldap_free_service(context, lservice);
517 	*service = NULL;
518     } else {
519 	store_tl_data(lservice->tl_data, KDB_TL_MASK, omask);
520 	*service = lservice;
521     }
522 
523     krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle);
524     return st;
525 }
526 
527 /*
528  * This function frees the krb5_ldap_service_params structure members.
529  */
530 
531 krb5_error_code
krb5_ldap_free_service(context,service)532 krb5_ldap_free_service(context, service)
533     krb5_context                context;
534     krb5_ldap_service_params    *service;
535 {
536     int                         i=0;
537 
538     if (service == NULL)
539 	return 0;
540 
541     if (service->servicedn)
542 	free (service->servicedn);
543 
544     if (service->krbrealmreferences) {
545 	for (i=0; service->krbrealmreferences[i]; ++i)
546 	    free (service->krbrealmreferences[i]);
547 	free (service->krbrealmreferences);
548     }
549 
550     if (service->krbhostservers) {
551 	for (i=0; service->krbhostservers[i]; ++i)
552 	    free (service->krbhostservers[i]);
553 	free (service->krbhostservers);
554     }
555 
556     if (service->tl_data) {
557 	if (service->tl_data->tl_data_contents)
558 	    free (service->tl_data->tl_data_contents);
559 	free (service->tl_data);
560     }
561 
562     free (service);
563     return 0;
564 }
565 
566 krb5_error_code
krb5_ldap_set_service_passwd(context,service,passwd)567 krb5_ldap_set_service_passwd(context, service, passwd)
568     krb5_context                context;
569     char                        *service;
570     char                        *passwd;
571 {
572     krb5_error_code             st=0;
573     LDAPMod                     **mods=NULL;
574     char                        *password[2] = {NULL};
575     LDAP                        *ld=NULL;
576     krb5_ldap_context           *ldap_context=NULL;
577     kdb5_dal_handle             *dal_handle=NULL;
578     krb5_ldap_server_handle     *ldap_server_handle=NULL;
579 
580     password[0] = passwd;
581 
582     SETUP_CONTEXT();
583     GET_HANDLE();
584 
585     if ((st=krb5_add_str_mem_ldap_mod(&mods, "userPassword", LDAP_MOD_REPLACE, password)) != 0)
586 	goto cleanup;
587 
588     st = ldap_modify_ext_s(ld, service, mods, NULL, NULL);
589     if (st) {
590 	st = set_ldap_error (context, st, OP_MOD);
591     }
592 
593 cleanup:
594     ldap_mods_free(mods, 1);
595     krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle);
596     return st;
597 }
598 #endif
599