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, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2003 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 /* 30 * Routines to handle getgr* calls in nscd 31 */ 32 33 #include <assert.h> 34 #include <errno.h> 35 #include <memory.h> 36 #include <signal.h> 37 #include <stdio.h> 38 #include <stdlib.h> 39 #include <string.h> 40 #include <sys/door.h> 41 #include <sys/stat.h> 42 #include <sys/time.h> 43 #include <sys/types.h> 44 #include <sys/wait.h> 45 #include <thread.h> 46 #include <unistd.h> 47 #include <ucred.h> 48 #include <nss_common.h> 49 50 #include <getxby_door.h> 51 #include "server_door.h" 52 #include "nscd.h" 53 54 static hash_t *uid_hash; 55 static hash_t *nam_hash; 56 static mutex_t group_lock = DEFAULTMUTEX; 57 static waiter_t group_wait; 58 59 static void getgr_gidkeepalive(int keep, int interval); 60 static void getgr_namekeepalive(int keep, int interval); 61 static int update_gr_bucket(nsc_bucket_t **old, nsc_bucket_t *new, 62 int callnumber); 63 static nsc_bucket_t *fixbuffer(nsc_return_t *in, int maxlen); 64 static void do_findgids(nsc_bucket_t *ptr, int *table, int gid); 65 static void do_findgnams(nsc_bucket_t *ptr, int *table, char *gnam); 66 static void do_invalidate(nsc_bucket_t **ptr, int callnumber); 67 static void getgr_invalidate_unlocked(void); 68 69 70 void 71 getgr_init(void) 72 { 73 uid_hash = make_ihash(current_admin.group.nsc_suggestedsize); 74 nam_hash = make_hash(current_admin.group.nsc_suggestedsize); 75 76 } 77 78 static void 79 do_invalidate(nsc_bucket_t **ptr, int callnumber) 80 { 81 if (*ptr != NULL && *ptr != (nsc_bucket_t *)-1) { 82 /* leave pending calls alone */ 83 update_gr_bucket(ptr, NULL, callnumber); 84 } 85 } 86 87 static void 88 do_findgids(nsc_bucket_t *ptr, int *table, int gid) 89 { 90 91 /* 92 * be careful with ptr - it may be -1 or NULL. 93 */ 94 if (ptr != NULL && ptr != (nsc_bucket_t *)-1) { 95 insertn(table, ptr->nsc_hits, gid); 96 } 97 } 98 99 static void 100 do_findgnams(nsc_bucket_t *ptr, int *table, char *gnam) 101 { 102 103 /* 104 * be careful with ptr - it may be -1 or NULL. 105 */ 106 107 if (ptr != NULL && ptr != (nsc_bucket_t *)-1) { 108 char *tmp = (char *)insertn(table, ptr->nsc_hits, 109 (int)strdup(gnam)); 110 if (tmp != (char *)-1) 111 free(tmp); 112 } 113 } 114 115 void 116 getgr_revalidate(void) 117 { 118 for (;;) { 119 int slp; 120 int interval; 121 int count; 122 123 slp = current_admin.group.nsc_pos_ttl; 124 125 if (slp < 60) { 126 slp = 60; 127 } 128 129 if ((count = current_admin.group.nsc_keephot) != 0) { 130 interval = (slp / 2)/count; 131 if (interval == 0) interval = 1; 132 sleep(slp * 2 / 3); 133 getgr_gidkeepalive(count, interval); 134 getgr_namekeepalive(count, interval); 135 } else { 136 sleep(slp); 137 } 138 } 139 } 140 141 static void 142 getgr_gidkeepalive(int keep, int interval) 143 { 144 int *table; 145 nsc_data_t ping; 146 int i; 147 148 if (!keep) 149 return; 150 151 table = maken(keep); 152 mutex_lock(&group_lock); 153 operate_hash(uid_hash, do_findgids, (char *)table); 154 mutex_unlock(&group_lock); 155 156 for (i = 1; i <= keep; i++) { 157 ping.nsc_call.nsc_callnumber = GETGRGID; 158 if ((ping.nsc_call.nsc_u.gid = table[keep + 1 + i]) == -1) 159 continue; /* unused slot in table */ 160 launch_update(&ping.nsc_call); 161 sleep(interval); 162 } 163 free(table); 164 } 165 166 static void 167 getgr_namekeepalive(int keep, int interval) 168 { 169 int *table; 170 union { 171 nsc_data_t ping; 172 char space[sizeof (nsc_data_t) + NSCDMAXNAMELEN]; 173 } u; 174 175 int i; 176 177 if (!keep) 178 return; 179 180 table = maken(keep); 181 mutex_lock(&group_lock); 182 operate_hash(nam_hash, do_findgnams, (char *)table); 183 mutex_unlock(&group_lock); 184 185 for (i = 1; i <= keep; i++) { 186 char *tmp; 187 u.ping.nsc_call.nsc_callnumber = GETGRNAM; 188 189 if ((tmp = (char *)table[keep + 1 + i]) == (char *)-1) 190 continue; /* unused slot in table */ 191 192 strcpy(u.ping.nsc_call.nsc_u.name, tmp); 193 194 launch_update(&u.ping.nsc_call); 195 sleep(interval); 196 } 197 198 for (i = 1; i <= keep; i++) { 199 char *tmp; 200 if ((tmp = (char *)table[keep + 1 + i]) != (char *)-1) 201 free(tmp); 202 } 203 204 free(table); 205 } 206 207 208 /* 209 * This routine marks all entries as invalid 210 * 211 */ 212 213 void 214 getgr_invalidate(void) 215 { 216 mutex_lock(&group_lock); 217 getgr_invalidate_unlocked(); 218 mutex_unlock(&group_lock); 219 } 220 221 static void 222 getgr_invalidate_unlocked(void) 223 { 224 operate_hash_addr(nam_hash, do_invalidate, (char *)GETGRNAM); 225 operate_hash_addr(uid_hash, do_invalidate, (char *)GETGRGID); 226 current_admin.group.nsc_invalidate_count++; 227 } 228 229 void 230 getgr_lookup(nsc_return_t *out, int maxsize, nsc_call_t *in, time_t now) 231 { 232 int out_of_date; 233 nsc_bucket_t *retb; 234 char **bucket; 235 236 static time_t lastmod; 237 238 int bufferspace = maxsize - sizeof (nsc_return_t); 239 240 if (current_admin.group.nsc_enabled == 0) { 241 out->nsc_return_code = NOSERVER; 242 out->nsc_bufferbytesused = sizeof (*out); 243 return; 244 } 245 246 mutex_lock(&group_lock); 247 248 if (current_admin.group.nsc_check_files) { 249 struct stat buf; 250 251 if (stat("/etc/group", &buf) < 0) { 252 /*EMPTY*/; 253 } else if (lastmod == 0) { 254 lastmod = buf.st_mtime; 255 } else if (lastmod < buf.st_mtime) { 256 getgr_invalidate_unlocked(); 257 lastmod = buf.st_mtime; 258 } 259 } 260 261 if (current_admin.debug_level >= DBG_ALL) { 262 if (MASKUPDATEBIT(in->nsc_callnumber) == GETGRGID) { 263 logit("getgr_lookup: looking for gid %d\n", 264 in->nsc_u.gid); 265 } else { 266 logit("getgr_lookup: looking for name %s\n", 267 in->nsc_u.name); 268 } 269 } 270 271 for (;;) { 272 if (MASKUPDATEBIT(in->nsc_callnumber) == GETGRGID) { 273 bucket = get_hash(uid_hash, (char *)in->nsc_u.gid); 274 } else { 275 if (strlen(in->nsc_u.name) > NSCDMAXNAMELEN) { 276 ucred_t *uc = NULL; 277 278 if (door_ucred(&uc) != 0) { 279 logit("getgr_lookup: Name too long, " 280 "but no user credential: %s\n", 281 strerror(errno)); 282 } else { 283 logit("getgr_lookup: Name too long " 284 "from pid %d uid %d\n", 285 ucred_getpid(uc), 286 ucred_getruid(uc)); 287 ucred_free(uc); 288 } 289 290 out->nsc_errno = NSS_NOTFOUND; 291 out->nsc_return_code = NOTFOUND; 292 out->nsc_bufferbytesused = sizeof (*out); 293 goto getout; 294 } 295 bucket = get_hash(nam_hash, in->nsc_u.name); 296 } 297 298 if (*bucket == (char *)-1) { /* pending lookup */ 299 if (get_clearance(in->nsc_callnumber) != 0) { 300 /* 301 * no threads available 302 * cannot process now 303 */ 304 out->nsc_return_code = NOSERVER; 305 out->nsc_bufferbytesused = sizeof (*out); 306 current_admin.group.nsc_throttle_count++; 307 goto getout; 308 } 309 nscd_wait(&group_wait, &group_lock, bucket); 310 release_clearance(in->nsc_callnumber); 311 continue; /* go back and relookup hash bucket */ 312 } 313 break; 314 } 315 316 /* 317 * check for no name_service mode 318 */ 319 320 if (*bucket == NULL && current_admin.avoid_nameservice) { 321 out->nsc_return_code = NOTFOUND; 322 out->nsc_bufferbytesused = sizeof (*out); 323 } else if ((*bucket == NULL) || /* New entry in name service */ 324 (in->nsc_callnumber & UPDATEBIT) || /* needs updating */ 325 (out_of_date = (!current_admin.avoid_nameservice && 326 (current_admin.group.nsc_old_data_ok == 0) && 327 (((nsc_bucket_t *)*bucket)->nsc_timestamp < now)))) { 328 /* time has expired */ 329 int saved_errno; 330 int saved_hits = 0; 331 struct group *p; 332 333 if (get_clearance(in->nsc_callnumber) != 0) { 334 /* no threads available */ 335 out->nsc_return_code = NOSERVER; 336 /* cannot process now */ 337 out->nsc_bufferbytesused = sizeof (*out); 338 current_admin.group.nsc_throttle_count++; 339 goto getout; 340 } 341 342 if (*bucket != NULL) { 343 saved_hits = ((nsc_bucket_t *)*bucket)->nsc_hits; 344 } 345 346 /* 347 * block any threads accessing this bucket if data is 348 * non-existent out of date 349 */ 350 351 if (*bucket == NULL || out_of_date) { 352 update_gr_bucket((nsc_bucket_t **)bucket, 353 (nsc_bucket_t *)-1, 354 in->nsc_callnumber); 355 } else { 356 /* 357 * if still not -1 bucket we are doing update... 358 * mark to prevent pileups of threads if the name 359 * service is hanging.... 360 */ 361 ((nsc_bucket_t *)(*bucket))->nsc_status |= 362 ST_UPDATE_PENDING; 363 /* cleared by deletion of old data */ 364 } 365 mutex_unlock(&group_lock); 366 367 if (MASKUPDATEBIT(in->nsc_callnumber) == GETGRGID) { 368 p = _uncached_getgrgid_r(in->nsc_u.gid, &out->nsc_u.grp, 369 out->nsc_u.buff + sizeof (struct group), 370 bufferspace); 371 saved_errno = errno; 372 } else { 373 p = _uncached_getgrnam_r(in->nsc_u.name, 374 &out->nsc_u.grp, 375 out->nsc_u.buff + sizeof (struct group), 376 bufferspace); 377 saved_errno = errno; 378 } 379 380 mutex_lock(&group_lock); 381 382 release_clearance(in->nsc_callnumber); 383 384 if (p == NULL) { /* data not found */ 385 if (current_admin.debug_level >= DBG_CANT_FIND) { 386 if (MASKUPDATEBIT(in->nsc_callnumber) == 387 GETGRGID) { 388 logit("getgr_lookup: nscd COULDN'T FIND gid %d\n", 389 in->nsc_u.gid); 390 } else { 391 logit("getgr_lookup: nscd COULDN'T FIND group name %s\n", 392 in->nsc_u.name); 393 } 394 } 395 396 397 if (!(UPDATEBIT & in->nsc_callnumber)) 398 current_admin.group.nsc_neg_cache_misses++; 399 400 retb = (nsc_bucket_t *)malloc(sizeof (nsc_bucket_t)); 401 402 retb->nsc_refcount = 1; 403 retb->nsc_data.nsc_bufferbytesused = 404 sizeof (nsc_return_t); 405 retb->nsc_data.nsc_return_code = NOTFOUND; 406 retb->nsc_data.nsc_errno = saved_errno; 407 memcpy(out, &retb->nsc_data, 408 retb->nsc_data.nsc_bufferbytesused); 409 update_gr_bucket((nsc_bucket_t **)bucket, 410 retb, 411 in->nsc_callnumber); 412 goto getout; 413 } else { 414 if (current_admin.debug_level >= DBG_ALL) { 415 if (MASKUPDATEBIT(in->nsc_callnumber) == 416 GETGRGID) { 417 logit("getgr_lookup: nscd FOUND gid %d\n", 418 in->nsc_u.gid); 419 } else { 420 logit("getgr_lookup: nscd FOUND group name %s\n", 421 in->nsc_u.name); 422 } 423 } 424 if (!(UPDATEBIT & in->nsc_callnumber)) 425 current_admin.group.nsc_pos_cache_misses++; 426 427 retb = fixbuffer(out, bufferspace); 428 update_gr_bucket((nsc_bucket_t **)bucket, 429 retb, 430 in->nsc_callnumber); 431 if (saved_hits) 432 retb->nsc_hits = saved_hits; 433 } 434 } else { /* found entry in cache */ 435 retb = (nsc_bucket_t *)*bucket; 436 437 retb->nsc_hits++; 438 439 memcpy(out, &(retb->nsc_data), 440 retb->nsc_data.nsc_bufferbytesused); 441 442 if (out->nsc_return_code == SUCCESS) { 443 if (!(UPDATEBIT & in->nsc_callnumber)) 444 current_admin.group.nsc_pos_cache_hits++; 445 if (current_admin.debug_level >= DBG_ALL) { 446 if (MASKUPDATEBIT(in->nsc_callnumber) == 447 GETGRGID) { 448 logit("getgr_lookup: found gid %d in cache\n", 449 in->nsc_u.gid); 450 } else { 451 logit("getgr_lookup: found name %s in cache\n", 452 in->nsc_u.name); 453 } 454 } 455 } else { 456 if (!(UPDATEBIT & in->nsc_callnumber)) 457 current_admin.group.nsc_neg_cache_hits++; 458 if (current_admin.debug_level >= DBG_ALL) { 459 if (MASKUPDATEBIT(in->nsc_callnumber) == 460 GETGRGID) { 461 logit("getgr_lookup: %d marked as NOT FOUND in cache.\n", 462 in->nsc_u.gid); 463 } else { 464 logit("getgr_lookup: %s marked as NOT FOUND in cache.\n", 465 in->nsc_u.name); 466 } 467 } 468 } 469 470 if ((retb->nsc_timestamp < now) && 471 !(in->nsc_callnumber & UPDATEBIT) && 472 !(retb->nsc_status & ST_UPDATE_PENDING)) { 473 logit("launch update since time = %d\n", retb->nsc_timestamp); 474 retb->nsc_status |= ST_UPDATE_PENDING; 475 /* cleared by deletion of old data */ 476 launch_update(in); 477 } 478 } 479 480 getout: 481 482 mutex_unlock(&group_lock); 483 } 484 485 /*ARGSUSED*/ 486 static int 487 update_gr_bucket(nsc_bucket_t **old, nsc_bucket_t *new, int callnumber) 488 { 489 if (*old != NULL && *old != (nsc_bucket_t *)-1) { /* old data exists */ 490 free(*old); 491 current_admin.group.nsc_entries--; 492 } 493 494 /* 495 * we can do this before reseting *old since we're holding the lock 496 */ 497 498 else if (*old == (nsc_bucket_t *)-1) { 499 nscd_signal(&group_wait, (char **)old); 500 } 501 502 503 *old = new; 504 505 if ((new != NULL) && (new != (nsc_bucket_t *)-1)) { 506 /* real data, not just update pending or invalidate */ 507 508 new->nsc_hits = 1; 509 new->nsc_status = 0; 510 new->nsc_refcount = 1; 511 current_admin.group.nsc_entries++; 512 513 if (new->nsc_data.nsc_return_code == SUCCESS) { 514 new->nsc_timestamp = time(NULL) + 515 current_admin.group.nsc_pos_ttl; 516 } else { 517 new->nsc_timestamp = time(NULL) + 518 current_admin.group.nsc_neg_ttl; 519 } 520 } 521 return (0); 522 } 523 524 525 /*ARGSUSED*/ 526 static nsc_bucket_t * 527 fixbuffer(nsc_return_t *in, int maxlen) 528 { 529 int group_members; 530 int i; 531 nsc_bucket_t *retb; 532 nsc_return_t *out; 533 char *dest; 534 int offset; 535 int strs; 536 char **members; 537 int pwlen; 538 539 /* 540 * find out the size of the data block we're going to need 541 */ 542 543 strs = 0; 544 strs += 1 + strlen(in->nsc_u.grp.gr_name); 545 pwlen = strlen(in->nsc_u.grp.gr_passwd); 546 if (pwlen < 4) 547 pwlen = 4; 548 strs += 1 + pwlen; 549 550 group_members = 0; 551 while (in->nsc_u.grp.gr_mem[group_members]) { 552 strs += 1 + strlen(in->nsc_u.grp.gr_mem[group_members]); 553 group_members++; 554 } 555 556 strs += (group_members+1) * sizeof (char *); 557 558 /* 559 * allocate it and copy it in 560 * code doesn't assume packing order in original buffer 561 */ 562 563 if ((retb = (nsc_bucket_t *)malloc(sizeof (*retb) + strs)) == NULL) { 564 return (NULL); 565 } 566 567 out = &(retb->nsc_data); 568 out->nsc_bufferbytesused = strs + ((int)&out->nsc_u.grp - (int)out) + 569 sizeof (struct group); 570 out->nsc_return_code = SUCCESS; 571 out->nsc_errno = 0; 572 573 574 out->nsc_u.grp.gr_gid = in->nsc_u.grp.gr_gid; 575 576 dest = retb->nsc_data.nsc_u.buff + sizeof (struct group); 577 offset = (int)dest; 578 579 members = (char **)dest; 580 out->nsc_u.grp.gr_mem = (char **)(dest - offset); 581 dest += (group_members+1) * sizeof (char *); 582 583 584 strcpy(dest, in->nsc_u.grp.gr_name); 585 strs = 1 + strlen(in->nsc_u.grp.gr_name); 586 out->nsc_u.grp.gr_name = dest - offset; 587 dest += strs; 588 589 strcpy(dest, in->nsc_u.grp.gr_passwd); 590 strs = 1 + pwlen; 591 out->nsc_u.grp.gr_passwd = dest - offset; 592 dest += strs; 593 594 for (i = 0; i < group_members; i++) { 595 members[i] = dest - offset; 596 strcpy(dest, in->nsc_u.grp.gr_mem[i]); 597 strs = 1 + strlen(in->nsc_u.grp.gr_mem[i]); 598 dest += strs; 599 } 600 members[i] = NULL; /* null terminate list */ 601 memcpy(in, out, out->nsc_bufferbytesused); 602 603 return (retb); 604 } 605 606 void 607 getgr_uid_reaper() 608 { 609 nsc_reaper("gr_uid", uid_hash, ¤t_admin.group, &group_lock); 610 } 611 612 void 613 getgr_nam_reaper() 614 { 615 nsc_reaper("gr_nam", nam_hash, ¤t_admin.group, &group_lock); 616 } 617