xref: /illumos-gate/usr/src/cmd/nscd/getuser.c (revision 4de2612967d06c4fdbf524a62556a1e8118a006f)
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 getuser* 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 <user_attr.h>
51 
52 #include <getxby_door.h>
53 #include "server_door.h"
54 #include "nscd.h"
55 
56 extern userstr_t *_getusernam(const char *, userstr_t *, char *, int, int *);
57 
58 static hash_t *nam_hash;
59 static mutex_t  db_lock = DEFAULTMUTEX;
60 static waiter_t db_wait;
61 
62 static void getuser_namekeepalive(int keep, int interval);
63 static void update_user_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_findgnams(nsc_bucket_t *ptr, int *table, char *gnam);
67 static void do_invalidate(nsc_bucket_t **ptr, int callnumber);
68 static void getuser_invalidate_unlocked(void);
69 
70 void
71 getuser_init(void)
72 {
73 	nam_hash = make_hash(current_admin.user.nsc_suggestedsize);
74 }
75 
76 static void
77 do_invalidate(nsc_bucket_t ** ptr, int callnumber)
78 {
79 	if (*ptr != NULL && *ptr != (nsc_bucket_t *)-1) {
80 		/* leave pending calls alone */
81 		update_user_bucket(ptr, NULL, callnumber);
82 	}
83 }
84 
85 static void
86 do_findgnams(nsc_bucket_t *ptr, int *table, char *gnam)
87 {
88 
89 	/*
90 	 * be careful with ptr - it may be -1 or NULL.
91 	 */
92 
93 	if (ptr != NULL && ptr != (nsc_bucket_t *)-1) {
94 		char *tmp = (char *)insertn(table, ptr->nsc_hits,
95 		    (int)strdup(gnam));
96 		if (tmp != (char *)-1)
97 			free(tmp);
98 	}
99 }
100 
101 void
102 getuser_revalidate(void)
103 {
104 	for (;;) {
105 		int slp;
106 		int interval;
107 		int count;
108 
109 		slp = current_admin.user.nsc_pos_ttl;
110 
111 		if (slp < 60) {
112 			slp = 60;
113 		}
114 
115 		if ((count = current_admin.user.nsc_keephot) != 0) {
116 			interval = (slp / 2)/count;
117 			if (interval == 0) interval = 1;
118 			sleep(slp * 2 / 3);
119 			getuser_namekeepalive(count, interval);
120 		} else {
121 			sleep(slp);
122 		}
123 	}
124 }
125 
126 static void
127 getuser_namekeepalive(int keep, int interval)
128 {
129 	int *table;
130 	union {
131 		nsc_data_t  ping;
132 		char space[sizeof (nsc_data_t) + NSCDMAXNAMELEN];
133 	} u;
134 
135 	int i;
136 
137 	if (!keep)
138 		return;
139 
140 	table = maken(keep);
141 	mutex_lock(&db_lock);
142 	operate_hash(nam_hash, do_findgnams, (char *)table);
143 	mutex_unlock(&db_lock);
144 
145 	for (i = 1; i <= keep; i++) {
146 		char *tmp;
147 		u.ping.nsc_call.nsc_callnumber = GETUSERNAM;
148 
149 		if ((tmp = (char *)table[keep + 1 + i]) == (char *)-1)
150 			continue; /* unused slot in table */
151 
152 		strcpy(u.ping.nsc_call.nsc_u.name, tmp);
153 
154 		launch_update(&u.ping.nsc_call);
155 		sleep(interval);
156 	}
157 
158 	for (i = 1; i <= keep; i++) {
159 		char *tmp;
160 		if ((tmp = (char *)table[keep + 1 + i]) != (char *)-1)
161 			free(tmp);
162 	}
163 
164 	free(table);
165 }
166 
167 
168 /*
169  *   This routine marks all entries as invalid
170  *
171  */
172 
173 void
174 getuser_invalidate()
175 {
176 	mutex_lock(&db_lock);
177 	getuser_invalidate_unlocked();
178 	mutex_unlock(&db_lock);
179 }
180 
181 static void
182 getuser_invalidate_unlocked()
183 {
184 	operate_hash_addr(nam_hash, do_invalidate, (char *)GETUSERNAM);
185 	current_admin.user.nsc_invalidate_count++;
186 }
187 
188 void
189 getuser_lookup(nsc_return_t *out, int maxsize, nsc_call_t *in, time_t now)
190 {
191 	int		out_of_date;
192 	nsc_bucket_t	*retb;
193 	char 		**bucket;
194 
195 	static time_t	lastmod;
196 
197 	int bufferspace = maxsize - sizeof (nsc_return_t);
198 
199 	if (current_admin.user.nsc_enabled == 0) {
200 		out->nsc_return_code = NOSERVER;
201 		out->nsc_bufferbytesused = sizeof (*out);
202 		return;
203 	}
204 
205 	mutex_lock(&db_lock);
206 
207 	if (current_admin.user.nsc_check_files) {
208 		struct stat buf;
209 
210 		if (stat(USERATTR_FILENAME, &buf) < 0) {
211 			/*EMPTY*/;
212 		} else if (lastmod == 0) {
213 			lastmod = buf.st_mtime;
214 		} else if (lastmod < buf.st_mtime) {
215 			getuser_invalidate_unlocked();
216 			lastmod = buf.st_mtime;
217 		}
218 	}
219 
220 	if (current_admin.debug_level >= DBG_ALL) {
221 		logit("getuser_lookup: looking for name %s\n",
222 				in->nsc_u.name);
223 	}
224 
225 	for (;;) {
226 		if (attr_strlen(in->nsc_u.name) > NSCDMAXNAMELEN) {
227 			ucred_t *uc = NULL;
228 
229 			if (door_ucred(&uc) != 0) {
230 				logit("getuser_lookup: Name too long, "
231 				    "but no user credential: %s\n",
232 				    strerror(errno));
233 			} else {
234 				logit("getuser_lookup: Name too long "
235 				    "from pid %d uid %d\n",
236 				    ucred_getpid(uc),
237 				    ucred_getruid(uc));
238 				ucred_free(uc);
239 			}
240 
241 			out->nsc_errno = NSS_NOTFOUND;
242 			out->nsc_return_code = NOTFOUND;
243 			out->nsc_bufferbytesused = sizeof (*out);
244 			goto getout;
245 		}
246 		bucket = get_hash(nam_hash, in->nsc_u.name);
247 
248 		if (*bucket == (char *)-1) {	/* pending lookup */
249 			if (get_clearance(in->nsc_callnumber) != 0) {
250 			    /* no threads available */
251 				out->nsc_return_code = NOSERVER;
252 				    /* cannot process now */
253 				out->nsc_bufferbytesused =
254 				    sizeof (*out);
255 				current_admin.user.nsc_throttle_count++;
256 				goto getout;
257 			}
258 			nscd_wait(&db_wait, &db_lock, bucket);
259 			release_clearance(in->nsc_callnumber);
260 			continue; /* go back and relookup hash bucket */
261 		}
262 		break;
263 	}
264 
265 	/*
266 	 * check for no name_service mode
267 	 */
268 
269 	if (*bucket == NULL && current_admin.avoid_nameservice) {
270 		out->nsc_return_code = NOTFOUND;
271 		out->nsc_bufferbytesused = sizeof (*out);
272 	} else if ((*bucket == NULL) || /* New entry in name service */
273 	    (in->nsc_callnumber & UPDATEBIT) || /* needs updating */
274 	    (out_of_date = (!current_admin.avoid_nameservice &&
275 	    (current_admin.user.nsc_old_data_ok == 0) &&
276 	    (((nsc_bucket_t *)*bucket)->nsc_timestamp < now)))) {
277 		/* time has expired */
278 		int saved_errno;
279 		int saved_hits = 0;
280 		userstr_t *p;
281 
282 		if (get_clearance(in->nsc_callnumber) != 0) {
283 		    /* no threads available */
284 			out->nsc_return_code = NOSERVER;
285 			    /* cannot process now */
286 			out->nsc_bufferbytesused = sizeof (*out);
287 			current_admin.user.nsc_throttle_count++;
288 			goto getout;
289 		}
290 
291 		if (*bucket != NULL) {
292 			saved_hits =
293 			    ((nsc_bucket_t *)*bucket)->nsc_hits;
294 		}
295 
296 		/*
297 		 *  block any threads accessing this bucket if data is
298 		 *  non-existent out of date
299 		 */
300 
301 		if (*bucket == NULL || out_of_date) {
302 			update_user_bucket((nsc_bucket_t **)bucket,
303 			    (nsc_bucket_t *)-1, in->nsc_callnumber);
304 		} else {
305 		/*
306 		 * if still not -1 bucket we are doing update...
307 		 * mark to prevent
308 		 * pileups of threads if the name service is hanging....
309 		 */
310 			((nsc_bucket_t *)(*bucket))->nsc_status |=
311 			    ST_UPDATE_PENDING;
312 			/* cleared by deletion of old data */
313 		}
314 		mutex_unlock(&db_lock);
315 
316 		/*
317 		 * Call non-caching version in libnsl.
318 		 */
319 		p = _getusernam(in->nsc_u.name, &out->nsc_u.user,
320 		    out->nsc_u.buff + sizeof (userstr_t),
321 		    bufferspace, &errno);
322 		saved_errno = errno;
323 
324 		mutex_lock(&db_lock);
325 
326 		release_clearance(in->nsc_callnumber);
327 
328 		if (p == NULL) { /* data not found */
329 
330 			if (current_admin.debug_level >= DBG_CANT_FIND) {
331 		logit("getuser_lookup: nscd COULDN'T FIND user_attr name %s\n",
332 						in->nsc_u.name);
333 			}
334 
335 
336 			if (!(UPDATEBIT & in->nsc_callnumber))
337 			    current_admin.user.nsc_neg_cache_misses++;
338 
339 			retb = (nsc_bucket_t *)malloc(sizeof (nsc_bucket_t));
340 
341 			retb->nsc_refcount = 1;
342 			retb->nsc_data.nsc_bufferbytesused =
343 				sizeof (nsc_return_t);
344 			retb->nsc_data.nsc_return_code = NOTFOUND;
345 			retb->nsc_data.nsc_errno = saved_errno;
346 			memcpy(out, &retb->nsc_data,
347 			    retb->nsc_data.nsc_bufferbytesused);
348 			update_user_bucket((nsc_bucket_t **)bucket,
349 			    retb, in->nsc_callnumber);
350 			goto getout;
351 		} else {
352 			if (current_admin.debug_level >= DBG_ALL) {
353 		logit("getuser_lookup: nscd FOUND user_attr name %s\n",
354 						in->nsc_u.name);
355 			}
356 			if (!(UPDATEBIT & in->nsc_callnumber))
357 			    current_admin.user.nsc_pos_cache_misses++;
358 
359 			retb = fixbuffer(out, bufferspace);
360 			update_user_bucket((nsc_bucket_t **)bucket,
361 			    retb, in->nsc_callnumber);
362 			if (saved_hits)
363 				retb->nsc_hits = saved_hits;
364 		}
365 	} else { 	/* found entry in cache */
366 		retb = (nsc_bucket_t *)*bucket;
367 
368 		retb->nsc_hits++;
369 
370 		memcpy(out, &(retb->nsc_data),
371 		    retb->nsc_data.nsc_bufferbytesused);
372 
373 		if (out->nsc_return_code == SUCCESS) {
374 			if (!(UPDATEBIT & in->nsc_callnumber))
375 			    current_admin.user.nsc_pos_cache_hits++;
376 			if (current_admin.debug_level >= DBG_ALL) {
377 			logit("getuser_lookup: found name %s in cache\n",
378 						in->nsc_u.name);
379 			}
380 		} else {
381 			if (!(UPDATEBIT & in->nsc_callnumber))
382 			    current_admin.user.nsc_neg_cache_hits++;
383 			if (current_admin.debug_level >= DBG_ALL) {
384 		logit("getuser_lookup: %s marked as NOT FOUND in cache.\n",
385 						in->nsc_u.name);
386 			}
387 		}
388 
389 		if ((retb->nsc_timestamp < now) &&
390 		    !(in->nsc_callnumber & UPDATEBIT) &&
391 		    !(retb->nsc_status & ST_UPDATE_PENDING)) {
392 		logit("launch update since time = %d\n", retb->nsc_timestamp);
393 			retb->nsc_status |= ST_UPDATE_PENDING;
394 			/* cleared by deletion of old data */
395 			launch_update(in);
396 		}
397 	}
398 
399 getout:
400 	mutex_unlock(&db_lock);
401 }
402 
403 /*ARGSUSED*/
404 static void
405 update_user_bucket(nsc_bucket_t **old, nsc_bucket_t *new, int callnumber)
406 {
407 	if (*old != NULL && *old != (nsc_bucket_t *)-1) { /* old data exists */
408 		free(*old);
409 		current_admin.user.nsc_entries--;
410 	}
411 
412 	/*
413 	 *  we can do this before reseting *old since we're holding the lock
414 	 */
415 
416 	else if (*old == (nsc_bucket_t *)-1) {
417 		nscd_signal(&db_wait, (char **)old);
418 	}
419 
420 	*old = new;
421 
422 	if ((new != NULL) &&
423 	    (new != (nsc_bucket_t *)-1)) {
424 		/* real data, not just update pending or invalidate */
425 		new->nsc_hits = 1;
426 		new->nsc_status = 0;
427 		new->nsc_refcount = 1;
428 		current_admin.user.nsc_entries++;
429 
430 		if (new->nsc_data.nsc_return_code == SUCCESS) {
431 			new->nsc_timestamp = time(NULL) +
432 			    current_admin.user.nsc_pos_ttl;
433 		} else {
434 			new->nsc_timestamp = time(NULL) +
435 			    current_admin.user.nsc_neg_ttl;
436 		}
437 	}
438 }
439 
440 /*ARGSUSED*/
441 static nsc_bucket_t *
442 fixbuffer(nsc_return_t *in, int maxlen)
443 {
444 	nsc_bucket_t *retb;
445 	nsc_return_t *out;
446 	char 	*dest;
447 	int 	offset;
448 	int 	strs;
449 
450 	/*
451 	 * find out the size of the data block we're going to need
452 	 */
453 
454 	strs = attr_strlen(in->nsc_u.user.name) +
455 	    attr_strlen(in->nsc_u.user.qualifier) +
456 	    attr_strlen(in->nsc_u.user.res1) +
457 	    attr_strlen(in->nsc_u.user.res2) +
458 	    attr_strlen(in->nsc_u.user.attr) + USERATTR_DB_NCOL;
459 
460 	/*
461 	 * allocate it and copy it in
462 	 * code doesn't assume packing order in original buffer
463 	 */
464 
465 	if ((retb = (nsc_bucket_t *)malloc(sizeof (*retb) + strs)) == NULL) {
466 		return (NULL);
467 	}
468 
469 	out = &(retb->nsc_data);
470 	out->nsc_bufferbytesused = strs + ((int)&out->nsc_u.user - (int)out) +
471 	    sizeof (userstr_t);
472 	out->nsc_return_code 	= SUCCESS;
473 	out->nsc_errno 		= 0;
474 
475 	dest = retb->nsc_data.nsc_u.buff + sizeof (userstr_t);
476 	offset = (int)dest;
477 
478 	attr_strcpy(dest, in->nsc_u.user.name);
479 	strs = 1 + attr_strlen(in->nsc_u.user.name);
480 	out->nsc_u.user.name = dest - offset;
481 	dest += strs;
482 
483 	attr_strcpy(dest, in->nsc_u.user.qualifier);
484 	strs = 1 + attr_strlen(in->nsc_u.user.qualifier);
485 	out->nsc_u.user.qualifier = dest - offset;
486 	dest += strs;
487 
488 	attr_strcpy(dest, in->nsc_u.user.res1);
489 	strs = 1 + attr_strlen(in->nsc_u.user.res1);
490 	out->nsc_u.user.res1 = dest - offset;
491 	dest += strs;
492 
493 	attr_strcpy(dest, in->nsc_u.user.res2);
494 	strs = 1 + attr_strlen(in->nsc_u.user.res2);
495 	out->nsc_u.user.res2 = dest - offset;
496 	dest += strs;
497 
498 	attr_strcpy(dest, in->nsc_u.user.attr);
499 	out->nsc_u.user.attr = dest - offset;
500 
501 	memcpy(in, out, out->nsc_bufferbytesused);
502 
503 	return (retb);
504 }
505 
506 void
507 getuser_reaper(void)
508 {
509 	nsc_reaper("getuser", nam_hash, &current_admin.user, &db_lock);
510 }
511