1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #include <malloc.h> 27 #include <synch.h> 28 #include <syslog.h> 29 #include <rpcsvc/ypclnt.h> 30 #include <rpcsvc/yp_prot.h> 31 #include <pthread.h> 32 #include <ctype.h> 33 #include <stdlib.h> 34 #include <stdio.h> 35 #include <signal.h> 36 #include <sys/stat.h> 37 #include <assert.h> 38 #include "ad_common.h" 39 40 static pthread_mutex_t statelock = PTHREAD_MUTEX_INITIALIZER; 41 static nssad_state_t state = {0}; 42 43 static void 44 nssad_cfg_free_props(nssad_prop_t *props) 45 { 46 if (props->domain_name != NULL) { 47 free(props->domain_name); 48 props->domain_name = NULL; 49 } 50 if (props->domain_controller != NULL) { 51 free(props->domain_controller); 52 props->domain_controller = NULL; 53 } 54 } 55 56 static int 57 nssad_cfg_discover_props(const char *domain, ad_disc_t ad_ctx, 58 nssad_prop_t *props) 59 { 60 ad_disc_refresh(ad_ctx); 61 if (ad_disc_set_DomainName(ad_ctx, domain) != 0) 62 return (-1); 63 if (props->domain_controller == NULL) 64 props->domain_controller = 65 ad_disc_get_DomainController(ad_ctx, AD_DISC_PREFER_SITE, 66 NULL); 67 return (0); 68 } 69 70 static int 71 nssad_cfg_reload_ad(nssad_prop_t *props, adutils_ad_t **ad) 72 { 73 int i; 74 adutils_ad_t *new; 75 76 if (props->domain_controller == NULL || 77 props->domain_controller[0].host[0] == '\0') 78 return (0); 79 if (adutils_ad_alloc(&new, props->domain_name, 80 ADUTILS_AD_DATA) != ADUTILS_SUCCESS) 81 return (-1); 82 for (i = 0; props->domain_controller[i].host[0] != '\0'; i++) { 83 if (adutils_add_ds(new, 84 props->domain_controller[i].host, 85 props->domain_controller[i].port) != ADUTILS_SUCCESS) { 86 adutils_ad_free(&new); 87 return (-1); 88 } 89 } 90 91 if (*ad != NULL) 92 adutils_ad_free(ad); 93 *ad = new; 94 return (0); 95 } 96 97 static 98 int 99 update_dirs(idmap_ad_disc_ds_t **value, idmap_ad_disc_ds_t **new) 100 { 101 if (*value == *new) 102 return (0); 103 104 if (*value != NULL && *new != NULL && 105 ad_disc_compare_ds(*value, *new) == 0) { 106 free(*new); 107 *new = NULL; 108 return (0); 109 } 110 111 if (*value) 112 free(*value); 113 *value = *new; 114 *new = NULL; 115 return (1); 116 } 117 118 static 119 int 120 nssad_cfg_refresh(nssad_cfg_t *cp) 121 { 122 nssad_prop_t props; 123 124 (void) ad_disc_SubnetChanged(cp->ad_ctx); 125 (void) memset(&props, 0, sizeof (props)); 126 if (nssad_cfg_discover_props(cp->props.domain_name, cp->ad_ctx, 127 &props) < 0) 128 return (-1); 129 if (update_dirs(&cp->props.domain_controller, 130 &props.domain_controller)) { 131 if (cp->props.domain_controller != NULL && 132 cp->props.domain_controller[0].host[0] != '\0') 133 (void) nssad_cfg_reload_ad(&cp->props, &cp->ad); 134 } 135 return (0); 136 } 137 138 static void 139 nssad_cfg_destroy(nssad_cfg_t *cp) 140 { 141 if (cp != NULL) { 142 (void) pthread_rwlock_destroy(&cp->lock); 143 ad_disc_fini(cp->ad_ctx); 144 nssad_cfg_free_props(&cp->props); 145 adutils_ad_free(&cp->ad); 146 free(cp); 147 } 148 } 149 150 static nssad_cfg_t * 151 nssad_cfg_create(const char *domain) 152 { 153 nssad_cfg_t *cp; 154 155 if ((cp = calloc(1, sizeof (*cp))) == NULL) 156 return (NULL); 157 if (pthread_rwlock_init(&cp->lock, NULL) != 0) { 158 free(cp); 159 return (NULL); 160 } 161 adutils_set_log(-1, TRUE, FALSE); 162 if ((cp->ad_ctx = ad_disc_init()) == NULL) 163 goto errout; 164 if ((cp->props.domain_name = strdup(domain)) == NULL) 165 goto errout; 166 if (nssad_cfg_discover_props(domain, cp->ad_ctx, &cp->props) < 0) 167 goto errout; 168 if (nssad_cfg_reload_ad(&cp->props, &cp->ad) < 0) 169 goto errout; 170 return (cp); 171 errout: 172 nssad_cfg_destroy(cp); 173 return (NULL); 174 } 175 176 #define hex_char(n) "0123456789abcdef"[n & 0xf] 177 178 int 179 _ldap_filter_name(char *filter_name, const char *name, int filter_name_size) 180 { 181 char *end = filter_name + filter_name_size; 182 char c; 183 184 for (; *name; name++) { 185 c = *name; 186 switch (c) { 187 case '*': 188 case '(': 189 case ')': 190 case '\\': 191 if (end <= filter_name + 3) 192 return (-1); 193 *filter_name++ = '\\'; 194 *filter_name++ = hex_char(c >> 4); 195 *filter_name++ = hex_char(c & 0xf); 196 break; 197 default: 198 if (end <= filter_name + 1) 199 return (-1); 200 *filter_name++ = c; 201 break; 202 } 203 } 204 if (end <= filter_name) 205 return (-1); 206 *filter_name = '\0'; 207 return (0); 208 } 209 210 static 211 nss_status_t 212 map_adrc2nssrc(adutils_rc adrc) 213 { 214 if (adrc == ADUTILS_SUCCESS) 215 return ((nss_status_t)NSS_SUCCESS); 216 if (adrc == ADUTILS_ERR_NOTFOUND) 217 errno = 0; 218 return ((nss_status_t)NSS_NOTFOUND); 219 } 220 221 /* ARGSUSED */ 222 nss_status_t 223 _nss_ad_marshall_data(ad_backend_ptr be, nss_XbyY_args_t *argp) 224 { 225 int stat; 226 227 if (argp->buf.result == NULL) { 228 /* 229 * This suggests that the process (e.g. nscd) expects 230 * nssad to return the data in native file format in 231 * argp->buf.buffer i.e. no need to marshall the data. 232 */ 233 argp->returnval = argp->buf.buffer; 234 argp->returnlen = strlen(argp->buf.buffer); 235 return ((nss_status_t)NSS_STR_PARSE_SUCCESS); 236 } 237 238 if (argp->str2ent == NULL) 239 return ((nss_status_t)NSS_STR_PARSE_PARSE); 240 241 stat = (*argp->str2ent)(be->buffer, be->buflen, 242 argp->buf.result, argp->buf.buffer, argp->buf.buflen); 243 244 if (stat == NSS_STR_PARSE_SUCCESS) { 245 argp->returnval = argp->buf.result; 246 argp->returnlen = 1; /* irrelevant */ 247 } 248 return ((nss_status_t)stat); 249 } 250 251 nss_status_t 252 _nss_ad_sanitize_status(ad_backend_ptr be, nss_XbyY_args_t *argp, 253 nss_status_t stat) 254 { 255 if (be->buffer != NULL) { 256 free(be->buffer); 257 be->buffer = NULL; 258 be->buflen = 0; 259 be->db_type = NSS_AD_DB_NONE; 260 } 261 262 if (stat == NSS_STR_PARSE_SUCCESS) { 263 return ((nss_status_t)NSS_SUCCESS); 264 } else if (stat == NSS_STR_PARSE_PARSE) { 265 argp->returnval = 0; 266 return ((nss_status_t)NSS_NOTFOUND); 267 } else if (stat == NSS_STR_PARSE_ERANGE) { 268 argp->erange = 1; 269 return ((nss_status_t)NSS_NOTFOUND); 270 } 271 return ((nss_status_t)NSS_UNAVAIL); 272 } 273 274 /* ARGSUSED */ 275 static 276 nssad_cfg_t * 277 get_cfg(const char *domain) 278 { 279 nssad_cfg_t *cp, *lru, *prev; 280 281 /* 282 * Note about the queue: 283 * 284 * The queue is used to hold our per domain 285 * configs. The queue is limited to CFG_QUEUE_MAX_SIZE. 286 * If the queue increases beyond that point we toss 287 * out the LRU entry. The entries are inserted into 288 * the queue at state.qtail and the LRU entry is 289 * removed from state.qhead. state.qnext points 290 * from the qtail to the qhead. Everytime a config 291 * is accessed it is moved to qtail. 292 */ 293 294 (void) pthread_mutex_lock(&statelock); 295 296 for (cp = state.qtail, prev = NULL; cp != NULL; 297 prev = cp, cp = cp->qnext) { 298 if (cp->props.domain_name == NULL || 299 strcasecmp(cp->props.domain_name, domain) != 0) 300 continue; 301 302 /* Found config for the given domain. */ 303 304 if (state.qtail != cp) { 305 /* 306 * Move the entry to the tail of the queue. 307 * This way the LRU entry can be found at 308 * the head of the queue. 309 */ 310 prev->qnext = cp->qnext; 311 if (state.qhead == cp) 312 state.qhead = prev; 313 cp->qnext = state.qtail; 314 state.qtail = cp; 315 } 316 317 if (ad_disc_get_TTL(cp->ad_ctx) == 0) { 318 /* 319 * If there are expired items in the 320 * config, grab the write lock and 321 * refresh the config. 322 */ 323 (void) pthread_rwlock_wrlock(&cp->lock); 324 if (nssad_cfg_refresh(cp) < 0) { 325 (void) pthread_rwlock_unlock(&cp->lock); 326 (void) pthread_mutex_unlock(&statelock); 327 return (NULL); 328 } 329 (void) pthread_rwlock_unlock(&cp->lock); 330 } 331 332 /* Return the config found */ 333 (void) pthread_rwlock_rdlock(&cp->lock); 334 (void) pthread_mutex_unlock(&statelock); 335 return (cp); 336 } 337 338 /* Create new config entry for the domain */ 339 if ((cp = nssad_cfg_create(domain)) == NULL) { 340 (void) pthread_mutex_unlock(&statelock); 341 return (NULL); 342 } 343 344 /* Add it to the queue */ 345 state.qcount++; 346 if (state.qtail == NULL) { 347 state.qtail = state.qhead = cp; 348 (void) pthread_rwlock_rdlock(&cp->lock); 349 (void) pthread_mutex_unlock(&statelock); 350 return (cp); 351 } 352 cp->qnext = state.qtail; 353 state.qtail = cp; 354 355 /* If the queue has exceeded its size, remove the LRU entry */ 356 if (state.qcount >= CFG_QUEUE_MAX_SIZE) { 357 /* Detach the lru entry and destroy */ 358 lru = state.qhead; 359 if (pthread_rwlock_trywrlock(&lru->lock) == 0) { 360 for (prev = state.qtail; prev != NULL; 361 prev = prev->qnext) { 362 if (prev->qnext != lru) 363 continue; 364 state.qhead = prev; 365 prev->qnext = NULL; 366 state.qcount--; 367 (void) pthread_rwlock_unlock(&lru->lock); 368 nssad_cfg_destroy(lru); 369 break; 370 } 371 (void) assert(prev != NULL); 372 } 373 } 374 375 (void) pthread_rwlock_rdlock(&cp->lock); 376 (void) pthread_mutex_unlock(&statelock); 377 return (cp); 378 } 379 380 381 /* ARGSUSED */ 382 static 383 nss_status_t 384 ad_lookup(const char *filter, const char **attrs, 385 const char *domain, adutils_result_t **result) 386 { 387 int retries = 0; 388 adutils_rc rc, brc; 389 adutils_query_state_t *qs; 390 nssad_cfg_t *cp; 391 392 retry: 393 if ((cp = get_cfg(domain)) == NULL) 394 return ((nss_status_t)NSS_NOTFOUND); 395 396 rc = adutils_lookup_batch_start(cp->ad, 1, NULL, NULL, &qs); 397 (void) pthread_rwlock_unlock(&cp->lock); 398 if (rc != ADUTILS_SUCCESS) 399 goto out; 400 401 rc = adutils_lookup_batch_add(qs, filter, attrs, domain, result, &brc); 402 if (rc != ADUTILS_SUCCESS) { 403 adutils_lookup_batch_release(&qs); 404 goto out; 405 } 406 407 rc = adutils_lookup_batch_end(&qs); 408 if (rc != ADUTILS_SUCCESS) 409 goto out; 410 rc = brc; 411 412 out: 413 if (rc == ADUTILS_ERR_RETRIABLE_NET_ERR && 414 retries++ < ADUTILS_DEF_NUM_RETRIES) 415 goto retry; 416 return (map_adrc2nssrc(rc)); 417 } 418 419 420 /* ARGSUSED */ 421 nss_status_t 422 _nss_ad_lookup(ad_backend_ptr be, nss_XbyY_args_t *argp, 423 const char *database, const char *searchfilter, 424 const char *dname, int *try_idmap) 425 { 426 nss_status_t stat; 427 428 *try_idmap = 0; 429 430 /* Clear up results if any */ 431 (void) adutils_freeresult(&be->result); 432 433 /* Lookup AD */ 434 stat = ad_lookup(searchfilter, be->attrs, dname, &be->result); 435 if (stat != NSS_SUCCESS) { 436 argp->returnval = 0; 437 *try_idmap = 1; 438 return (stat); 439 } 440 441 /* Map AD object(s) to string in native file format */ 442 stat = be->adobj2str(be, argp); 443 if (stat == NSS_STR_PARSE_SUCCESS) 444 stat = _nss_ad_marshall_data(be, argp); 445 return (_nss_ad_sanitize_status(be, argp, stat)); 446 } 447 448 static 449 void 450 clean_state() 451 { 452 nssad_cfg_t *cp, *curr; 453 454 (void) pthread_mutex_lock(&statelock); 455 for (cp = state.qtail; cp != NULL; ) { 456 curr = cp; 457 cp = cp->qnext; 458 nssad_cfg_destroy(curr); 459 } 460 (void) memset(&state, 0, sizeof (state)); 461 (void) pthread_mutex_unlock(&statelock); 462 } 463 464 static 465 void 466 _clean_ad_backend(ad_backend_ptr be) 467 { 468 if (be->tablename != NULL) 469 free(be->tablename); 470 if (be->buffer != NULL) { 471 free(be->buffer); 472 be->buffer = NULL; 473 } 474 free(be); 475 } 476 477 478 /* 479 * _nss_ad_destr frees allocated memory before exiting this nsswitch shared 480 * backend library. This function is called before returning control back to 481 * nsswitch. 482 */ 483 /*ARGSUSED*/ 484 nss_status_t 485 _nss_ad_destr(ad_backend_ptr be, void *a) 486 { 487 (void) _clean_ad_backend(be); 488 clean_state(); 489 return ((nss_status_t)NSS_SUCCESS); 490 } 491 492 493 /*ARGSUSED*/ 494 nss_status_t 495 _nss_ad_setent(ad_backend_ptr be, void *a) 496 { 497 return ((nss_status_t)NSS_UNAVAIL); 498 } 499 500 501 /*ARGSUSED*/ 502 nss_status_t 503 _nss_ad_endent(ad_backend_ptr be, void *a) 504 { 505 return ((nss_status_t)NSS_UNAVAIL); 506 } 507 508 509 /*ARGSUSED*/ 510 nss_status_t 511 _nss_ad_getent(ad_backend_ptr be, void *a) 512 { 513 return ((nss_status_t)NSS_UNAVAIL); 514 } 515 516 517 nss_backend_t * 518 _nss_ad_constr(ad_backend_op_t ops[], int nops, char *tablename, 519 const char **attrs, fnf adobj2str) 520 { 521 ad_backend_ptr be; 522 523 if ((be = (ad_backend_ptr) calloc(1, sizeof (*be))) == NULL) 524 return (NULL); 525 if ((be->tablename = (char *)strdup(tablename)) == NULL) { 526 free(be); 527 return (NULL); 528 } 529 be->ops = ops; 530 be->nops = (nss_dbop_t)nops; 531 be->attrs = attrs; 532 be->adobj2str = adobj2str; 533 (void) memset(&state, 0, sizeof (state)); 534 return ((nss_backend_t *)be); 535 } 536