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