xref: /illumos-gate/usr/src/cmd/nscd/getpw.c (revision 7c478bd95313f5f23a4c958a745db2134aa03244)
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 getpw* calls in nscd
31  */
32 
33 #include <assert.h>
34 #include <errno.h>
35 #include <memory.h>
36 #include <signal.h>
37 #include <stdlib.h>
38 #include <stdio.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 <nss_common.h>
48 #include <ucred.h>
49 
50 #include "getxby_door.h"
51 #include "server_door.h"
52 
53 #include "nscd.h"
54 
55 static hash_t *uid_hash;
56 static hash_t *nam_hash;
57 static mutex_t  passwd_lock = DEFAULTMUTEX;
58 static waiter_t passwd_wait;
59 
60 static void getpw_invalidate_unlocked(void);
61 static void getpw_namekeepalive(int keep, int interval);
62 static void getpw_uidkeepalive(int keep, int interval);
63 static void update_pw_bucket(nsc_bucket_t **old, nsc_bucket_t *new,
64     int callnumber);
65 static nsc_bucket_t *fixbuffer(nsc_return_t *in, int maxlen);
66 static void do_findnams(nsc_bucket_t *ptr, int *table, char *name);
67 static void do_finduids(nsc_bucket_t *ptr, int *table, int uid);
68 static void do_invalidate(nsc_bucket_t **ptr, int callnumber);
69 
70 void
71 getpw_init(void)
72 {
73 	uid_hash = make_ihash(current_admin.passwd.nsc_suggestedsize);
74 	nam_hash = make_hash(current_admin.passwd.nsc_suggestedsize);
75 }
76 
77 static void
78 do_invalidate(nsc_bucket_t ** ptr, int callnumber)
79 {
80 	if (*ptr != NULL && *ptr != (nsc_bucket_t *)-1) {
81 		/* leave pending calls alone */
82 		update_pw_bucket(ptr, NULL, callnumber);
83 	}
84 }
85 
86 static void
87 do_finduids(nsc_bucket_t *ptr, int *table, int uid)
88 {
89 
90 	/*
91 	 * be careful with ptr - it may be -1 or NULL.
92 	 */
93 	if (ptr != NULL && ptr != (nsc_bucket_t *)-1) {
94 		insertn(table, ptr->nsc_hits, uid);
95 	}
96 }
97 
98 static void
99 do_findnams(nsc_bucket_t *ptr, int *table, char *name)
100 {
101 
102 	/*
103 	 * be careful with ptr - it may be -1 or NULL.
104 	 */
105 	if (ptr != NULL && ptr != (nsc_bucket_t *)-1) {
106 		char *tmp = (char *)insertn(table, ptr->nsc_hits,
107 			(int)strdup(name));
108 		if (tmp != (char *)-1)
109 			free(tmp);
110 	}
111 }
112 
113 
114 
115 void
116 getpw_revalidate(void)
117 {
118 	for (;;) {
119 		int slp;
120 		int interval;
121 		int count;
122 
123 		slp = current_admin.passwd.nsc_pos_ttl;
124 
125 		if (slp < 60) {
126 			slp = 60;
127 		}
128 
129 		if ((count = current_admin.passwd.nsc_keephot) != 0) {
130 			interval = (slp / 2)/count;
131 			if (interval == 0) interval = 1;
132 			sleep(slp * 2 / 3);
133 			getpw_uidkeepalive(count, interval);
134 			getpw_namekeepalive(count, interval);
135 		} else {
136 			sleep(slp);
137 		}
138 	}
139 }
140 
141 static void
142 getpw_uidkeepalive(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(&passwd_lock);
153 	operate_hash(uid_hash, do_finduids, (char *)table);
154 	mutex_unlock(&passwd_lock);
155 
156 	for (i = 1; i <= keep; i++) {
157 	    ping.nsc_call.nsc_callnumber = GETPWUID;
158 	    if ((ping.nsc_call.nsc_u.uid = 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 
167 static void
168 getpw_namekeepalive(int keep, int interval)
169 {
170 	int *table;
171 	union {
172 		nsc_data_t  ping;
173 		char space[sizeof (nsc_data_t) + NSCDMAXNAMELEN];
174 	} u;
175 
176 	int i;
177 
178 	if (!keep)
179 		return;
180 
181 	table = maken(keep);
182 	mutex_lock(&passwd_lock);
183 	operate_hash(nam_hash, do_findnams, (char *)table);
184 	mutex_unlock(&passwd_lock);
185 
186 	for (i = 1; i <= keep; i++) {
187 		char *tmp;
188 		u.ping.nsc_call.nsc_callnumber = GETPWNAM;
189 
190 		if ((tmp = (char *)table[keep + 1 + i]) == (char *)-1)
191 			continue; /* unused slot in table */
192 
193 		strcpy(u.ping.nsc_call.nsc_u.name, tmp);
194 
195 		launch_update(&u.ping.nsc_call);
196 		sleep(interval);
197 	}
198 
199 	for (i = 1; i <= keep; i++) {
200 		char *tmp;
201 		if ((tmp = (char *)table[keep + 1 + i]) != (char *)-1)
202 			free(tmp);
203 	}
204 
205 	free(table);
206 }
207 
208 
209 
210 
211 /*
212  *   This routine marks all entries as invalid
213  *
214  */
215 void
216 getpw_invalidate(void)
217 {
218 	mutex_lock(&passwd_lock);
219 	getpw_invalidate_unlocked();
220 	mutex_unlock(&passwd_lock);
221 }
222 
223 static void
224 getpw_invalidate_unlocked(void)
225 {
226 	operate_hash_addr(nam_hash, do_invalidate, (char *)GETPWNAM);
227 	operate_hash_addr(uid_hash, do_invalidate, (char *)GETPWUID);
228 	current_admin.passwd.nsc_invalidate_count++;
229 }
230 
231 void
232 getpw_lookup(nsc_return_t *out, int maxsize, nsc_call_t *in, time_t now)
233 {
234 	int		out_of_date;
235 	nsc_bucket_t	*retb;
236 	char 		**bucket;
237 
238 	static time_t	lastmod;
239 
240 	int bufferspace = maxsize - sizeof (nsc_return_t);
241 
242 	if (current_admin.passwd.nsc_enabled == 0) {
243 		out->nsc_return_code = NOSERVER;
244 		out->nsc_bufferbytesused = sizeof (*out);
245 		return;
246 	}
247 
248 	mutex_lock(&passwd_lock);
249 
250 	if (current_admin.passwd.nsc_check_files) {
251 		struct stat buf;
252 
253 		if (stat("/etc/passwd", &buf) < 0) {
254 			/*EMPTY*/;
255 		} else if (lastmod == 0) {
256 			lastmod = buf.st_mtime;
257 		} else if (lastmod < buf.st_mtime) {
258 			getpw_invalidate_unlocked();
259 			lastmod = buf.st_mtime;
260 		}
261 	}
262 
263 	if (current_admin.debug_level >= DBG_ALL) {
264 		if (MASKUPDATEBIT(in->nsc_callnumber) == GETPWUID) {
265 			logit("getpw_lookup: looking for uid %d\n",
266 				in->nsc_u.uid);
267 		} else {
268 			logit("getpw_lookup: looking for name %s\n",
269 				in->nsc_u.name);
270 		}
271 	}
272 
273 	for (;;) {
274 		if (MASKUPDATEBIT(in->nsc_callnumber) == GETPWUID) {
275 			bucket = get_hash(uid_hash, (char *)in->nsc_u.uid);
276 		} else { /* make reasonableness check here  */
277 			if (strlen(in->nsc_u.name) > NSCDMAXNAMELEN) {
278 				ucred_t *uc = NULL;
279 
280 				if (door_ucred(&uc) != 0) {
281 					logit("getpw_lookup: Name too long, "
282 					    "but no user credential: %s\n",
283 					    strerror(errno));
284 				} else {
285 
286 					logit("getpw_lookup: Name too long "
287 					    "from pid %d uid %d\n",
288 					    ucred_getpid(uc),
289 					    ucred_getruid(uc));
290 					ucred_free(uc);
291 				}
292 
293 
294 				out->nsc_errno = NSS_NOTFOUND;
295 				out->nsc_return_code = NOTFOUND;
296 				out->nsc_bufferbytesused = sizeof (*out);
297 				goto getout;
298 			}
299 			bucket = get_hash(nam_hash, in->nsc_u.name);
300 		}
301 
302 		if (*bucket == (char *)-1) {	/* pending lookup */
303 			if (get_clearance(in->nsc_callnumber) != 0) {
304 				/* no threads available */
305 				out->nsc_return_code = NOSERVER;
306 				/* cannot process now */
307 				out->nsc_bufferbytesused = sizeof (*out);
308 				current_admin.passwd.nsc_throttle_count++;
309 				goto getout;
310 			}
311 			nscd_wait(&passwd_wait, &passwd_lock, bucket);
312 			release_clearance(in->nsc_callnumber);
313 			continue; /* go back and relookup hash bucket */
314 		}
315 		break;
316 	}
317 
318 	/*
319 	 * check for no name_service mode
320 	 */
321 
322 	if (*bucket == NULL && current_admin.avoid_nameservice) {
323 		out->nsc_return_code = NOTFOUND;
324 		out->nsc_bufferbytesused = sizeof (*out);
325 	} else if (*bucket == NULL ||
326 	    (in->nsc_callnumber & UPDATEBIT) ||
327 	    (out_of_date = (!current_admin.avoid_nameservice &&
328 		(current_admin.passwd.nsc_old_data_ok == 0) &&
329 		(((nsc_bucket_t *)*bucket)->nsc_timestamp < now)))) {
330 		/*
331 		 * time has expired
332 		 */
333 		int saved_errno;
334 		int saved_hits = 0;
335 		struct passwd *p;
336 
337 		if (get_clearance(in->nsc_callnumber) != 0) {
338 			/* no threads available */
339 			out->nsc_return_code = NOSERVER;
340 			/* cannot process now */
341 			out->nsc_bufferbytesused = sizeof (*out);
342 			current_admin.passwd.nsc_throttle_count++;
343 			goto getout;
344 		}
345 		if (*bucket != NULL) {
346 			saved_hits = ((nsc_bucket_t *)*bucket)->nsc_hits;
347 		}
348 
349 		/*
350 		 *  block any threads accessing this bucket if data
351 		 *  is non-existent or out of date
352 		 */
353 
354 		if (*bucket == NULL || out_of_date) {
355 			update_pw_bucket((nsc_bucket_t **)bucket,
356 					(nsc_bucket_t *)-1,
357 					in->nsc_callnumber);
358 		} else {
359 			/*
360 			 * if still not -1 bucket we are doing
361 			 * update... mark to prevent pileups of threads if
362 			 * the name service is hanging..
363 			 */
364 			((nsc_bucket_t *)(*bucket))->nsc_status |=
365 				ST_UPDATE_PENDING;
366 			/* cleared by deletion of old data */
367 		}
368 		mutex_unlock(&passwd_lock);
369 
370 		if (MASKUPDATEBIT(in->nsc_callnumber) == GETPWUID) {
371 			p = _uncached_getpwuid_r(in->nsc_u.uid, &out->nsc_u.pwd,
372 				out->nsc_u.buff+sizeof (struct passwd),
373 				bufferspace);
374 			saved_errno = errno;
375 		} else {
376 			p = _uncached_getpwnam_r(in->nsc_u.name,
377 				&out->nsc_u.pwd,
378 				out->nsc_u.buff+sizeof (struct passwd),
379 				bufferspace);
380 			saved_errno = errno;
381 		}
382 
383 		mutex_lock(&passwd_lock);
384 
385 		release_clearance(in->nsc_callnumber);
386 
387 		if (p == NULL) { /* data not found */
388 			if (current_admin.debug_level >= DBG_CANT_FIND) {
389 				if (MASKUPDATEBIT(in->nsc_callnumber) ==
390 					GETPWUID) {
391 			logit("getpw_lookup: nscd COULDN'T FIND uid %d\n",
392 					in->nsc_u.uid);
393 				} else {
394 		logit("getpw_lookup: nscd COULDN'T FIND passwd name %s\n",
395 						in->nsc_u.name);
396 				}
397 			}
398 
399 			if (!(UPDATEBIT & in->nsc_callnumber))
400 			    current_admin.passwd.nsc_neg_cache_misses++;
401 
402 			retb = (nsc_bucket_t *)malloc(sizeof (nsc_bucket_t));
403 
404 			retb->nsc_refcount = 1;
405 			retb->nsc_data.nsc_bufferbytesused =
406 				sizeof (nsc_return_t);
407 			retb->nsc_data.nsc_return_code = NOTFOUND;
408 			retb->nsc_data.nsc_errno = saved_errno;
409 			memcpy(out, &retb->nsc_data,
410 				retb->nsc_data.nsc_bufferbytesused);
411 			update_pw_bucket((nsc_bucket_t **)bucket, retb,
412 				in->nsc_callnumber);
413 			goto getout;
414 		} else {
415 			if (current_admin.debug_level >= DBG_ALL) {
416 				if (MASKUPDATEBIT(in->nsc_callnumber) ==
417 					GETPWUID) {
418 				logit("getpw_lookup: nscd FOUND uid %d\n",
419 						in->nsc_u.uid);
420 				} else {
421 			logit("getpw_lookup: nscd FOUND passwd name %s\n",
422 						in->nsc_u.name);
423 				}
424 			}
425 			if (!(UPDATEBIT & in->nsc_callnumber))
426 			    current_admin.passwd.nsc_pos_cache_misses++;
427 
428 			retb = fixbuffer(out, bufferspace);
429 			update_pw_bucket((nsc_bucket_t **)bucket,
430 				retb, 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.passwd.nsc_pos_cache_hits++;
445 			if (current_admin.debug_level >= DBG_ALL) {
446 				if (MASKUPDATEBIT(in->nsc_callnumber) ==
447 					GETPWUID) {
448 			logit("getpw_lookup: found uid %d in cache\n",
449 						in->nsc_u.uid);
450 				} else {
451 			logit("getpw_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.passwd.nsc_neg_cache_hits++;
458 			if (current_admin.debug_level >= DBG_ALL) {
459 				if (MASKUPDATEBIT(in->nsc_callnumber) ==
460 					GETPWUID) {
461 		logit("getpw_lookup: %d marked as NOT FOUND in cache.\n",
462 						in->nsc_u.uid);
463 				} else {
464 		logit("getpw_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",
474 				retb->nsc_timestamp);
475 			retb->nsc_status |= ST_UPDATE_PENDING;
476 			/* cleared by deletion of old data */
477 			launch_update(in);
478 		}
479 	}
480 
481 getout:
482 
483 	mutex_unlock(&passwd_lock);
484 
485 	/*
486 	 *	secure mode check - blank out passwd if call sucessfull
487 	 *	and caller != effective id
488 	 */
489 	if ((current_admin.passwd.nsc_secure_mode != 0) &&
490 		(out->nsc_return_code == SUCCESS) &&
491 		!(UPDATEBIT & in->nsc_callnumber)) {
492 
493 		ucred_t *uc = NULL;
494 
495 		if (door_ucred(&uc) != 0) {
496 			perror("door_ucred");
497 		} else {
498 			if (ucred_geteuid(uc) != out->nsc_u.pwd.pw_uid) {
499 				/*
500 				 *  write *NP* into passwd field if
501 				 *  not already that way... we fixed
502 				 *  the buffer code so there's always room.
503 				 */
504 				int len;
505 
506 				char *foo = out->nsc_u.buff
507 					+ sizeof (struct passwd)
508 					+ (int)out->nsc_u.pwd.pw_passwd;
509 
510 				len = strlen(foo);
511 				if (len > 0 &&
512 				    strcmp(foo, "*NP*") != 0 &&
513 				    strcmp(foo, "x") != 0) {
514 					if (len < 5)
515 						len = 5;
516 					strncpy(foo, "*NP*", len);
517 					/*
518 					 * strncpy will
519 					 * blank all
520 					 */
521 				}
522 			}
523 			ucred_free(uc);
524 		}
525 	}
526 }
527 
528 /*ARGSUSED*/
529 static void
530 update_pw_bucket(nsc_bucket_t **old, nsc_bucket_t *new, int callnumber)
531 {
532 	if (*old != NULL && *old != (nsc_bucket_t *)-1) {
533 		/* old data exists */
534 		free(*old);
535 		current_admin.passwd.nsc_entries--;
536 	}
537 
538 	/*
539 	 *  we can do this before reseting *old since we're holding the lock
540 	 */
541 
542 	else if (*old == (nsc_bucket_t *)-1) {
543 		nscd_signal(&passwd_wait, (char **)old);
544 	}
545 
546 
547 
548 	*old = new;
549 
550 	if ((new != NULL) &&
551 		(new != (nsc_bucket_t *)-1)) {
552 		/* real data, not just update pending or invalidate */
553 
554 		new->nsc_hits = 1;
555 		new->nsc_status = 0;
556 		new->nsc_refcount = 1;
557 		current_admin.passwd.nsc_entries++;
558 
559 		if (new->nsc_data.nsc_return_code == SUCCESS) {
560 			new->nsc_timestamp = time(NULL) +
561 				current_admin.passwd.nsc_pos_ttl;
562 		} else {
563 			new->nsc_timestamp = time(NULL) +
564 				current_admin.passwd.nsc_neg_ttl;
565 		}
566 	}
567 }
568 
569 
570 /*ARGSUSED*/
571 static nsc_bucket_t *
572 fixbuffer(nsc_return_t *in, int maxlen)
573 {
574 	nsc_bucket_t *retb;
575 	char *dest;
576 
577 	nsc_return_t  *out;
578 	int offset;
579 	int strs;
580 	int pwlen;
581 
582 	/*
583 	 * find out the size of the data block we're going to need
584 	 */
585 
586 	strs = 0;
587 	strs += 1 + strlen(in->nsc_u.pwd.pw_name);
588 	pwlen = strlen(in->nsc_u.pwd.pw_passwd);
589 	if (pwlen < 4)
590 	    pwlen = 4;
591 	strs += 1 + pwlen;
592 	strs += 1 + strlen(in->nsc_u.pwd.pw_age);
593 	strs += 1 + strlen(in->nsc_u.pwd.pw_comment);
594 	strs += 1 + strlen(in->nsc_u.pwd.pw_gecos);
595 	strs += 1 + strlen(in->nsc_u.pwd.pw_dir);
596 	strs += 1 + strlen(in->nsc_u.pwd.pw_shell);
597 
598 
599 	/*
600 	 * allocate it and copy it in
601 	 * code doesn't assume packing order in original buffer
602 	 */
603 
604 	if ((retb = (nsc_bucket_t *)malloc(sizeof (*retb) + strs)) == NULL) {
605 		return (NULL);
606 	}
607 
608 	out = &(retb->nsc_data);
609 
610 
611 
612 	out->nsc_bufferbytesused = sizeof (*in) + strs;
613 	out->nsc_return_code 	= SUCCESS;
614 	out->nsc_errno 		= 0;
615 
616 	out->nsc_u.pwd.pw_uid = in->nsc_u.pwd.pw_uid;
617 	out->nsc_u.pwd.pw_gid = in->nsc_u.pwd.pw_gid;
618 
619 	dest = retb->nsc_data.nsc_u.buff + sizeof (struct passwd);
620 
621 	offset = (int)dest;
622 
623 	strcpy(dest, in->nsc_u.pwd.pw_name);
624 	strs = 1 + strlen(in->nsc_u.pwd.pw_name);
625 	out->nsc_u.pwd.pw_name = dest - offset;
626 	dest += strs;
627 
628 	strcpy(dest, in->nsc_u.pwd.pw_passwd);
629 	strs = 1 + pwlen;
630 	out->nsc_u.pwd.pw_passwd = dest - offset;
631 	dest += strs;
632 
633 	strcpy(dest, in->nsc_u.pwd.pw_age);
634 	strs = 1 + strlen(in->nsc_u.pwd.pw_age);
635 	out->nsc_u.pwd.pw_age = dest - offset;
636 	dest += strs;
637 
638 	strcpy(dest, in->nsc_u.pwd.pw_comment);
639 	strs = 1 + strlen(in->nsc_u.pwd.pw_comment);
640 	out->nsc_u.pwd.pw_comment = dest - offset;
641 	dest += strs;
642 
643 	strcpy(dest, in->nsc_u.pwd.pw_gecos);
644 	strs = 1 + strlen(in->nsc_u.pwd.pw_gecos);
645 	out->nsc_u.pwd.pw_gecos = dest - offset;
646 	dest += strs;
647 
648 	strcpy(dest, in->nsc_u.pwd.pw_dir);
649 	strs = 1 + strlen(in->nsc_u.pwd.pw_dir);
650 	out->nsc_u.pwd.pw_dir = dest - offset;
651 	dest += strs;
652 
653 	strcpy(dest, in->nsc_u.pwd.pw_shell);
654 	out->nsc_u.pwd.pw_shell = dest - offset;
655 
656 	memcpy(in, out, retb->nsc_data.nsc_bufferbytesused);
657 
658 
659 	return (retb);
660 
661 }
662 
663 void
664 getpw_uid_reaper()
665 {
666 	nsc_reaper("getpw_uid", uid_hash, &current_admin.passwd, &passwd_lock);
667 }
668 
669 void
670 getpw_nam_reaper()
671 {
672 	nsc_reaper("getpw_nam", nam_hash, &current_admin.passwd, &passwd_lock);
673 }
674