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