getxby_door.c revision 2830:5228d1267a01
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/* 23 * Copyright 2006 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27#pragma ident "%Z%%M% %I% %E% SMI" 28 29#include "synonyms.h" 30#include <mtlib.h> 31#include <sys/types.h> 32#include <errno.h> 33#include <pwd.h> 34#include <nss_dbdefs.h> 35#include <stdio.h> 36#include <string.h> 37#include <synch.h> 38#include <sys/param.h> 39#include <fcntl.h> 40#include <unistd.h> 41#include <stdlib.h> 42#include <getxby_door.h> 43#include <sys/door.h> 44#include <procfs.h> 45#include <door.h> 46#include "libc.h" 47#include "tsd.h" 48#include "base_conversion.h" 49 50/* nss<->door hints */ 51static mutex_t hints_lock = DEFAULTMUTEX; 52static size_t door_bsize = 0; 53static size_t door_nbsize = 0; 54static int proc_is_cache = -1; 55 56/* library<->nscd door interaction apis */ 57 58/* 59 * 60 * Routine that actually performs the door call. 61 * Note that we cache a file descriptor. We do 62 * the following to prevent disasters: 63 * 64 * 1) Never use 0,1 or 2; if we get this from the open 65 * we dup it upwards. 66 * 67 * 2) Set the close on exec flags so descriptor remains available 68 * to child processes. 69 * 70 * 3) Verify that the door is still the same one we had before 71 * by using door_info on the client side. 72 * 73 * Note that we never close the file descriptor if it isn't one 74 * we allocated; we check this with door info. The rather tricky 75 * logic is designed to be fast in the normal case (fd is already 76 * allocated and is ok) while handling the case where the application 77 * closed it underneath us or where the nscd dies or re-execs itself 78 * and we're a multi-threaded application. Note that we cannot protect 79 * the application if it closes the fd and it is multi-threaded. 80 * 81 * int _nsc_trydoorcall(void *dptr, size_t *bufsize, size_t *actualsize); 82 * 83 * *dptr IN: points to arg buffer OUT: points to results buffer 84 * *bufsize IN: overall size of buffer OUT: overall size of buffer 85 * *actualsize IN: size of call data OUT: size of return data 86 * 87 * Note that *dptr may change if provided space as defined by *bufsize is 88 * inadequate. In this case the door call mmaps more space and places 89 * the answer there and sets dptr to contain a pointer to the space, which 90 * should be freed with munmap. 91 * 92 * Returns 0 if the door call reached the server, -1 if contact was not made. 93 * 94 */ 95 96/* 97 * Max size for list of db names supported by the private nscd 98 * No implied max here, any size will do, fixed size chosen to 99 * reduce yet another malloc 100 */ 101 102#define BD_BUFSIZE 1024 103#define BD_SEP ',' 104 105typedef struct _nsc_door_t { 106 int doorfd; 107 mutex_t door_lock; 108 door_info_t doori; 109} nsc_door_t; 110 111static nsc_door_t nsc_door[2] = { 112 { -1, DEFAULTMUTEX, { 0 } }, /* front (fattached) door */ 113 { -1, DEFAULTMUTEX, { 0 } }, /* back (private) door */ 114}; 115 116/* assumed to be locked by using nsc_door[1] mutex */ 117static char *nsc_db_buf = NULL; 118static char **nsc_db_list = NULL; 119 120/* 121 * Check for a valid and matching db in the list. 122 * assume list is in the locked state. 123 */ 124 125static int 126_nsc_use_backdoor(char *db) 127{ 128 char **ndb; 129 130 if (db && nsc_db_buf != NULL && nsc_db_list != NULL) { 131 for (ndb = nsc_db_list; *ndb; ndb++) { 132 if (strcmp(db, *ndb) == 0) 133 return (1); 134 } 135 } 136 return (0); 137} 138 139/* 140 * flush private db lists 141 */ 142static void 143_nsc_flush_private_db() 144{ 145 if (nsc_db_buf != NULL) { 146 libc_free((void *)nsc_db_buf); 147 nsc_db_buf = NULL; 148 } 149 if (nsc_db_list != NULL) { 150 libc_free((void *)nsc_db_list); 151 nsc_db_list = NULL; 152 } 153} 154 155/* 156 * init/update nsc_db_buf given buff containing list of 157 * db's to be processed by a private nscd. 158 * This function assumes it has a well formed string from nscd. 159 */ 160 161static int 162_nsc_init_private_db(char *dblist) 163{ 164 char *cp, **lp; 165 int buflen = 0; 166 int arrlen = 0; 167 168 if (dblist == NULL) 169 return (0); 170 171 /* reset db list */ 172 _nsc_flush_private_db(); 173 174 /* rebuild fresh list */ 175 buflen = strlen(dblist) + 1; 176 for (cp = dblist; *cp; cp++) 177 if (*cp == BD_SEP) 178 arrlen++; 179 if (cp == dblist) 180 return (0); 181 arrlen += 2; 182 nsc_db_buf = (char *)libc_malloc(buflen); 183 if (nsc_db_buf == (char *)NULL) 184 return (0); 185 nsc_db_list = (char **)libc_malloc(arrlen * sizeof (char *)); 186 if (nsc_db_list == (char **)NULL) { 187 libc_free((void *)nsc_db_buf); 188 nsc_db_buf = NULL; 189 return (0); 190 } 191 (void) memcpy(nsc_db_buf, dblist, buflen); 192 lp = nsc_db_list; 193 *lp++ = nsc_db_buf; 194 for (cp = nsc_db_buf; *cp; ) { 195 if (*cp == BD_SEP) { 196 *cp++ = '\0'; 197 *lp++ = cp; 198 } else 199 cp++; 200 } 201 *lp = NULL; 202 return (1); 203} 204 205/* 206 * _nsc_initdoor_fp attempts to validate the given door and 207 * confirm that it is still available for use. The options are: 208 * Front door: 209 * If it's not open, attempt to open or error 210 * If it's open attempt to validate. 211 * If it's not validatable, reset fd and try again. 212 * Other wise it open and validated, return success 213 * Per user (back) door: 214 * This door is passed to the client through th front door 215 * attempt to validate it. If it can't be validated, it 216 * must be reset. Then send a NSS_ALTRESET error, so nscd can 217 * forward another fd if desired. 218 */ 219 220static nss_status_t 221_nsc_initdoor_fp(nsc_door_t *dp) 222{ 223 224 door_info_t my_door; 225 226 if (dp == NULL) { 227 errno = ENOTCONN; 228 return (NSS_ERROR); 229 } 230 231 /* 232 * the first time in we try and open and validate the front door. 233 * A front door request may return an alternate private back door 234 * that the client should use instead. 235 * 236 * To validate a door the door must have been created with 237 * the name service door cookie. The front door is file 238 * attached, owned by root and readonly by user, group and 239 * other. If any of these validations fail we refuse to use 240 * the door. A back door is delivered from the front door 241 * via a door_desc_t, and have the same cooke notification. 242 */ 243 244 lmutex_lock(&dp->door_lock); 245 246try_again: 247 248 if (dp->doorfd == -1 && dp == &nsc_door[0]) { /* open front door */ 249 int tbc[3]; 250 int i; 251 252 dp->doorfd = open64(NAME_SERVICE_DOOR, O_RDONLY, 0); 253 if (dp->doorfd == -1) { 254 lmutex_unlock(&dp->door_lock); 255 return (NSS_ERROR); 256 } 257 258 /* 259 * dup up the file descriptor if we have 0 - 2 260 * to avoid problems with shells stdin/out/err 261 */ 262 i = 0; 263 264 while (dp->doorfd < 3) { /* we have a reserved fd */ 265 tbc[i++] = dp->doorfd; 266 if ((dp->doorfd = dup(dp->doorfd)) < 0) { 267 while (i--) 268 (void) close(tbc[i]); 269 dp->doorfd = -1; 270 lmutex_unlock(&dp->door_lock); 271 return (NSS_ERROR); 272 } 273 } 274 275 while (i--) 276 (void) close(tbc[i]); 277 278 /* 279 * mark this door descriptor as close on exec 280 */ 281 (void) fcntl(dp->doorfd, F_SETFD, FD_CLOEXEC); 282 if (__door_info(dp->doorfd, &dp->doori) < 0 || 283 (dp->doori.di_attributes & DOOR_REVOKED) || 284 dp->doori.di_data != (uintptr_t)NAME_SERVICE_DOOR_COOKIE) { 285 /* 286 * we should close doorfd because we just opened it 287 */ 288 (void) close(dp->doorfd); 289 dp->doorfd = -1; 290 (void) memset((void *)&dp->doori, 291 '\0', sizeof (door_info_t)); 292 lmutex_unlock(&dp->door_lock); 293 errno = ECONNREFUSED; 294 return (NSS_ERROR); 295 } 296 } else { 297 if (__door_info(dp->doorfd, &my_door) < 0 || 298 my_door.di_data != (uintptr_t)NAME_SERVICE_DOOR_COOKIE || 299 my_door.di_uniquifier != dp->doori.di_uniquifier) { 300 /* 301 * don't close it - 302 * someone else has clobbered fd 303 */ 304 dp->doorfd = -1; 305 (void) memset((void *)&dp->doori, 306 '\0', sizeof (door_info_t)); 307 if (dp == &nsc_door[1]) { /* reset back door */ 308 /* flush invalid db list */ 309 _nsc_flush_private_db(); 310 lmutex_unlock(&dp->door_lock); 311 return (NSS_ALTRESET); 312 } 313 goto try_again; 314 } 315 316 if (my_door.di_attributes & DOOR_REVOKED) { 317 (void) close(dp->doorfd); /* nscd exited .... */ 318 dp->doorfd = -1; /* try and restart connection */ 319 (void) memset((void *)&dp->doori, 320 '\0', sizeof (door_info_t)); 321 if (dp == &nsc_door[1]) { /* back door reset */ 322 /* flush invalid db list */ 323 _nsc_flush_private_db(); 324 lmutex_unlock(&dp->door_lock); 325 return (NSS_ALTRESET); 326 } 327 goto try_again; 328 } 329 } 330 331 lmutex_unlock(&dp->door_lock); 332 return (NSS_SUCCESS); 333} 334 335/* 336 * Try the door request once only, to the specified connection. 337 * return the results or error. 338 */ 339 340static nss_status_t 341_nsc_try1door(nsc_door_t *dp, void **dptr, size_t *ndata, 342 size_t *adata, int *pdesc) 343{ 344 door_arg_t param; 345 int ret; 346 nss_pheader_t *rp; 347 348 ret = _nsc_initdoor_fp(dp); 349 if (ret != NSS_SUCCESS) 350 return (ret); 351 352 param.rbuf = (char *)*dptr; 353 param.rsize = *ndata; 354 param.data_ptr = (char *)*dptr; 355 param.data_size = *adata; 356 param.desc_ptr = NULL; 357 param.desc_num = 0; 358 ret = __door_call(dp->doorfd, ¶m); 359 if (ret < 0) { 360 return (NSS_ERROR); 361 } 362 *adata = param.data_size; 363 *ndata = param.rsize; 364 *dptr = (void *)param.data_ptr; 365 rp = (nss_pheader_t *)((void *)param.rbuf); 366 if (pdesc != NULL && rp && rp->p_status == NSS_ALTRETRY && 367 param.desc_ptr != NULL && param.desc_num > 0) { 368 if ((param.desc_ptr->d_attributes & DOOR_DESCRIPTOR) && 369 param.desc_ptr->d_data.d_desc.d_descriptor >= 0 && 370 param.desc_ptr->d_data.d_desc.d_id != 0) { 371 /* have an alt descriptor */ 372 *pdesc = param.desc_ptr->d_data.d_desc.d_descriptor; 373 /* got a NSS_ALTRETRY command */ 374 return (NSS_ALTRETRY); 375 } 376 errno = EINVAL; 377 return (NSS_ERROR); /* other error? */ 378 } 379 if (*adata == 0 || *dptr == NULL) { 380 errno = ENOTCONN; 381 return (NSS_ERROR); 382 } 383 384 if (rp->p_status == NSS_ALTRESET || 385 rp->p_status == NSS_ALTRETRY || 386 rp->p_status == NSS_TRYLOCAL) 387 return (rp->p_status); 388 389 return (NSS_SUCCESS); 390} 391 392/* 393 * Backwards compatible API 394 */ 395 396nss_status_t 397_nsc_trydoorcall(void **dptr, size_t *ndata, size_t *adata) 398{ 399 return (_nsc_try1door(&nsc_door[0], dptr, ndata, adata, NULL)); 400} 401 402/* 403 * Send the request to the designated door, based on the supplied db 404 * Retry on the alternate door fd if possible. 405 */ 406 407nss_status_t 408_nsc_trydoorcall_ext(void **dptr, size_t *ndata, size_t *adata) 409{ 410 int ret = NSS_ALTRETRY; 411 nsc_door_t *frontd = &nsc_door[0]; 412 nsc_door_t *backd = &nsc_door[1]; 413 int fd; 414 415 nss_pheader_t *ph, ph_save; 416 char *dbl; 417 char *db = NULL; 418 nss_dbd_t *dbd; 419 int fb2frontd = 0; 420 int reset_frontd = 0; 421 422 ph = (nss_pheader_t *)*dptr; 423 dbd = (nss_dbd_t *)((void *)((char *)ph + ph->dbd_off)); 424 if (dbd->o_name != 0) 425 db = (char *)dbd + dbd->o_name; 426 ph_save = *ph; 427 428 while (ret == NSS_ALTRETRY || ret == NSS_ALTRESET) { 429 /* try private (back) door first if it exists and applies */ 430 if (db != NULL && backd->doorfd > 0 && fb2frontd == 0 && 431 _nsc_use_backdoor(db)) { 432 ret = _nsc_try1door(backd, dptr, ndata, adata, NULL); 433 if (ret == NSS_ALTRESET) { 434 /* 435 * received NSS_ALTRESET command, 436 * retry on front door 437 */ 438 lmutex_lock(&backd->door_lock); 439 backd->doorfd = -1; 440 (void) memset((void *)&backd->doori, 441 '\0', sizeof (door_info_t)); 442 /* flush now invalid db list */ 443 _nsc_flush_private_db(); 444 lmutex_unlock(&backd->door_lock); 445 continue; 446 } else if (ret == NSS_ALTRETRY) { 447 /* 448 * received NSS_ALTRETRY command, 449 * fall back and retry on front door 450 */ 451 fb2frontd = 1; 452 *ph = ph_save; 453 /* 454 * tell the front door server, this is 455 * a fallback call 456 */ 457 ph->p_status = NSS_ALTRETRY; 458 continue; 459 } 460 461 /* return the result or error */ 462 break; 463 } 464 465 /* try the front door */ 466 fd = -1; 467 ret = _nsc_try1door(frontd, dptr, ndata, adata, &fd); 468 469 if (ret != NSS_ALTRETRY) { 470 /* 471 * got a success or failure result. 472 * but front door should never send NSS_ALTRESET 473 */ 474 if (ret == NSS_ALTRESET) 475 /* reset the front door */ 476 reset_frontd = 1; 477 else 478 /* 479 * not NSS_ALTRETRY and not NSS_ALTRESET 480 * return the result or error 481 */ 482 break; 483 } else if (fb2frontd == 1) { 484 /* 485 * front door should never send NSS_ALTRETRY 486 * in a fallback call. Reset the front door. 487 */ 488 reset_frontd = 1; 489 } 490 491 if (reset_frontd == 1) { 492 lmutex_lock(&frontd->door_lock); 493 frontd->doorfd = -1; 494 (void) memset((void *)&frontd->doori, 495 '\0', sizeof (door_info_t)); 496 lmutex_unlock(&frontd->door_lock); 497 /* error out */ 498 ret = NSS_ERROR; 499 break; 500 } 501 502 /* process NSS_ALTRETRY request from front door */ 503 if (fd < 0) 504 continue; /* no new door given, try again */ 505 506 /* update and try alternate door */ 507 lmutex_lock(&backd->door_lock); 508 if (backd->doorfd >= 0) { 509 /* unexpected open alt door - clean up, continue */ 510 _nsc_flush_private_db(); 511 (void) close(backd->doorfd); 512 } 513 514 /* set up back door fd */ 515 backd->doorfd = fd; 516 517 /* set up back door db list */ 518 ph = (nss_pheader_t *)*dptr; 519 dbl = ((char *)ph) + ph->data_off; 520 521 if (_nsc_init_private_db(dbl) == 0) { 522 /* could not init db list, try again */ 523 (void) close(backd->doorfd); 524 backd->doorfd = -1; 525 lmutex_unlock(&backd->door_lock); 526 continue; 527 } 528 if (door_info(backd->doorfd, &backd->doori) < 0 || 529 (backd->doori.di_attributes & DOOR_REVOKED) || 530 backd->doori.di_data != 531 (uintptr_t)NAME_SERVICE_DOOR_COOKIE) { 532 /* doorfd bad, or must not really be open */ 533 (void) close(backd->doorfd); 534 backd->doorfd = -1; 535 (void) memset((void *)&backd->doori, 536 '\0', sizeof (door_info_t)); 537 } 538 (void) fcntl(backd->doorfd, F_SETFD, FD_CLOEXEC); 539 lmutex_unlock(&backd->door_lock); 540 /* NSS_ALTRETRY new back door */ 541 *ph = ph_save; 542 } 543 return (ret); 544} 545 546/* 547 * Get the current (but growable) buffer size for a NSS2 packet. 548 * Heuristic algorithm used: 549 * 1) Make sure it's at least NSS_BUFLEN_DOOR in length (16k default) 550 * 2) if an incoming user buffer is > larger than the current size 551 * Make the buffer at least NSS_BUFLEN_DOOR/2+user buffer size 552 * This should account for any reasonable nss_pheader, keys 553 * extended area etc. 554 * 3) keep the prototype/debugging (private)NSS_BUFLEN option 555 * to change any preconfigured value if needed(?) 556 */ 557 558static size_t 559_nsc_getdoorbsize(size_t min_size) 560{ 561 if (!door_bsize) { 562 lmutex_lock(&hints_lock); 563 if (!door_bsize) { 564 /* future work - get nscd hint & use hint size */ 565 door_bsize = ROUND_UP(door_bsize, NSS_BUFSIZ); 566 if (door_bsize < NSS_BUFLEN_DOOR) { 567 door_bsize = NSS_BUFLEN_DOOR; 568 } 569 } 570 lmutex_unlock(&hints_lock); 571 } 572 if (min_size && door_bsize < (min_size + NSS_BUFLEN_DOOR/2)) { 573 lmutex_lock(&hints_lock); 574 if (door_bsize < (min_size + NSS_BUFLEN_DOOR/2)) { 575 min_size += NSS_BUFLEN_DOOR; 576 door_bsize = ROUND_UP(min_size, NSS_BUFSIZ); 577 } 578 lmutex_unlock(&hints_lock); 579 } 580 return (door_bsize); 581} 582 583static void 584_nsc_freedbuf(void *arg) 585{ 586 nss_XbyY_buf_t *tsdbuf = arg; 587 588 if (tsdbuf != NULL && tsdbuf->buffer != NULL) { 589 lfree(tsdbuf->buffer, (size_t)tsdbuf->buflen); 590 tsdbuf->result = NULL; 591 tsdbuf->buffer = NULL; 592 tsdbuf->buflen = 0; 593 } 594} 595 596/* 597 * _nsc_getdoorbuf - return the client side per thread door buffer 598 * Elsewhere, it is assumed that the header is 0'd upon return from here. 599 */ 600 601int 602_nsc_getdoorbuf(void **doorptr, size_t *bufsize) 603{ 604 nss_XbyY_buf_t *tsdbuf; 605 char *bp; 606 size_t dsize; 607 608 if (doorptr == NULL || bufsize == NULL) 609 return (-1); 610 611 /* Get thread specific pointer to door buffer */ 612 tsdbuf = tsdalloc(_T_DOORBUF, sizeof (nss_XbyY_buf_t), _nsc_freedbuf); 613 if (tsdbuf == NULL) 614 return (-1); 615 616 /* if door buffer does not exist create it */ 617 if (tsdbuf->buffer == NULL) { 618 dsize = _nsc_getdoorbsize(*bufsize); 619 620 /* setup a door buffer with a total length of dsize */ 621 bp = lmalloc(dsize); 622 if (bp == NULL) 623 return (-1); 624 tsdbuf->buffer = bp; 625 tsdbuf->buflen = dsize; 626 } else { 627 /* check old buffer size and resize if needed */ 628 if (*bufsize) { 629 dsize = _nsc_getdoorbsize(*bufsize); 630 if (tsdbuf->buflen < dsize) { 631 lfree(tsdbuf->buffer, (size_t)tsdbuf->buflen); 632 bp = lmalloc(dsize); 633 if (bp == NULL) 634 return (-1); 635 tsdbuf->buffer = bp; 636 tsdbuf->buflen = dsize; 637 } 638 } 639 /* freshly malloc'd door bufs are 0'd */ 640 /* 0 header for now. Zero entire buf(?) TDB */ 641 (void) memset((void *)tsdbuf->buffer, 0, 642 (size_t)sizeof (nss_pheader_t)); 643 644 } 645 *doorptr = (void *)tsdbuf->buffer; 646 *bufsize = tsdbuf->buflen; 647 return (0); 648} 649 650void 651_nsc_resizedoorbuf(size_t bsize) 652{ 653 /* signal to update if new door size is desired */ 654 lmutex_lock(&hints_lock); 655 if (bsize > door_bsize && door_nbsize < bsize) 656 door_nbsize = bsize; 657 lmutex_unlock(&hints_lock); 658} 659 660/* 661 * Check uid and /proc/PID/psinfo to see if this process is nscd 662 * If it is set the appropriate flags and allow policy reconfiguration. 663 */ 664int 665_nsc_proc_is_cache() 666{ 667 psinfo_t pinfo; 668 char fname[128]; 669 int ret; 670 int fd; 671 672 if (proc_is_cache >= 0) 673 return (proc_is_cache); 674 lmutex_lock(&hints_lock); 675 if (proc_is_cache >= 0) { 676 lmutex_unlock(&hints_lock); 677 return (proc_is_cache); 678 } 679 proc_is_cache = 0; 680 /* It can't be nscd if it's not running as root... */ 681 if (getuid() != 0) { 682 lmutex_unlock(&hints_lock); 683 return (0); 684 } 685 ret = snprintf(fname, 128, "/proc/%d/psinfo", getpid()); 686 if (ret > 0 && ret < 128) { 687 if ((fd = open(fname, O_RDONLY)) > 0) { 688 ret = read(fd, &pinfo, sizeof (psinfo_t)); 689 (void) close(fd); 690 if (ret == sizeof (psinfo_t) && 691 (strcmp(pinfo.pr_fname, "nscd") == 0)) { 692 /* process runs as root and is named nscd */ 693 /* that's good enough for now */ 694 proc_is_cache = 1; 695 } 696 } 697 } 698 lmutex_unlock(&hints_lock); 699 return (proc_is_cache); 700} 701