xref: /illumos-gate/usr/src/cmd/nscd/gethost.c (revision f841f6ad96ea6675d6c6b35c749eaac601799fdf)
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 gethost* 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 <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 *addr_hash;
55 static hash_t *hnam_hash;
56 static mutex_t  host_lock = DEFAULTMUTEX;
57 static waiter_t host_wait;
58 
59 static void gethost_addrkeepalive(int keep, int interval);
60 static void gethost_invalidate_unlocked(void);
61 static void gethost_namekeepalive(int keep, int interval);
62 static int addr_to_int(char *addr);
63 static int int_to_addr(int h);
64 static void update_host_bucket(nsc_bucket_t **old, nsc_bucket_t *new,
65     int callnumber);
66 static nsc_bucket_t *fixbuffer(nsc_return_t *in, int maxlen);
67 static void do_findhaddrs(nsc_bucket_t *ptr, int *table, int intaddr);
68 static void do_findhnams(nsc_bucket_t *ptr, int *table, char *name);
69 static void do_invalidate(nsc_bucket_t **ptr, int callnumber);
70 
71 static int
72 addr_to_int(char *addr)
73 {
74 	union {
75 		char data[4];
76 		int  hashval;
77 	} u;
78 
79 /*
80  * following code is byte order dependant, but since all we use this for
81  * is hashing this works out just fine.
82  */
83 	u.data[0] = *addr++;
84 	u.data[1] = *addr++;
85 	u.data[2] = *addr++;
86 	u.data[3] = *addr++;
87 
88 	return (u.hashval);
89 }
90 
91 static int
92 int_to_addr(int h)
93 {
94 	union {
95 		char data[4];
96 		int  hashval;
97 	} u;
98 
99 /*
100  * following code is byte order dependant, but since all we use this for
101  * is hashing this works out just fine.
102  */
103 	u.hashval = h;
104 	return (* ((int *)u.data));
105 }
106 
107 void
108 gethost_init(void)
109 {
110 	addr_hash = make_ihash(current_admin.host.nsc_suggestedsize);
111 	hnam_hash = make_hash(current_admin.host.nsc_suggestedsize);
112 }
113 
114 static void
115 do_invalidate(nsc_bucket_t ** ptr, int callnumber)
116 {
117 	if (*ptr != NULL && *ptr != (nsc_bucket_t *)-1) {
118 		/* leave pending calls alone */
119 		update_host_bucket(ptr, NULL, callnumber);
120 	}
121 }
122 
123 static void
124 do_findhnams(nsc_bucket_t *ptr, int *table, char *name)
125 {
126 	/*
127 	 * be careful with ptr - it may be -1 or NULL.
128 	 */
129 
130 	if (ptr != NULL && ptr != (nsc_bucket_t *)-1) {
131 		/* leave pending calls alone */
132 		char *tmp = (char *)insertn(table, ptr->nsc_hits,
133 			(int)strdup(name));
134 		if (tmp != (char *)-1)
135 			free(tmp);
136 	}
137 }
138 
139 static void
140 do_findhaddrs(nsc_bucket_t *ptr, int *table, int intaddr)
141 {
142 	if (ptr != NULL && ptr != (nsc_bucket_t *)-1) {
143 		/* leave pending calls alone */
144 		insertn(table, ptr->nsc_hits, int_to_addr(intaddr));
145 	}
146 }
147 
148 void
149 gethost_revalidate(void)
150 {
151 	for (;;) {
152 		int slp;
153 		int interval;
154 		int count;
155 
156 		slp = current_admin.host.nsc_pos_ttl;
157 
158 		if (slp < 60)
159 			slp = 60;
160 		count = current_admin.host.nsc_keephot;
161 		if (count != 0) {
162 			interval = (slp/2)/count;
163 			if (interval == 0) interval = 1;
164 			sleep(slp*2/3);
165 			gethost_namekeepalive(count, interval);
166 			gethost_addrkeepalive(count, interval);
167 		} else {
168 			sleep(slp);
169 		}
170 	}
171 }
172 
173 static void
174 gethost_namekeepalive(int keep, int interval)
175 {
176 	int *table;
177 	union {
178 		nsc_data_t  ping;
179 		char space[sizeof (nsc_data_t) + NSCDMAXNAMELEN];
180 	} u;
181 
182 	int i;
183 
184 	if (!keep)
185 		return;
186 
187 	table = maken(keep);
188 	mutex_lock(&host_lock);
189 	operate_hash(hnam_hash, do_findhnams, (char *)table);
190 	mutex_unlock(&host_lock);
191 
192 	for (i = 1; i <= keep; i++) {
193 		char *tmp;
194 		u.ping.nsc_call.nsc_callnumber = GETHOSTBYNAME;
195 
196 		if ((tmp = (char *)table[keep + 1 + i]) == (char *)-1)
197 			continue; /* unused slot in table */
198 		if (current_admin.debug_level >= DBG_ALL)
199 			logit("keepalive: reviving host %s\n", tmp);
200 		strcpy(u.ping.nsc_call.nsc_u.name, tmp);
201 
202 		launch_update(&u.ping.nsc_call);
203 		sleep(interval);
204 	}
205 
206 	for (i = 1; i <= keep; i++) {
207 		char *tmp;
208 		if ((tmp = (char *)table[keep + 1 + i]) != (char *)-1)
209 			free(tmp);
210 	}
211 
212 	free(table);
213 }
214 
215 static void
216 gethost_addrkeepalive(int keep, int interval)
217 {
218 	int *table;
219 	union {
220 		nsc_data_t  ping;
221 		char space[sizeof (nsc_data_t) + 80];
222 	} u;
223 
224 	int i;
225 
226 	if (!keep)
227 		return;
228 
229 	table = maken(keep);
230 	mutex_lock(&host_lock);
231 	operate_hash(addr_hash, do_findhaddrs, (char *)table);
232 	mutex_unlock(&host_lock);
233 
234 	for (i = 1; i <= keep; i++) {
235 		int tmp;
236 		u.ping.nsc_call.nsc_callnumber = GETHOSTBYADDR;
237 
238 		if ((tmp = table[keep + 1 + i]) == -1)
239 			continue; /* unused slot in table */
240 		u.ping.nsc_call.nsc_u.addr.a_type = AF_INET;
241 		u.ping.nsc_call.nsc_u.addr.a_length = sizeof (int);
242 		memcpy(u.ping.nsc_call.nsc_u.addr.a_data, &tmp, sizeof (int));
243 		launch_update(&u.ping.nsc_call);
244 		sleep(interval);
245 	}
246 
247 	free(table);
248 }
249 
250 /*
251  *   This routine marks all entries as invalid
252  *
253  */
254 void
255 gethost_invalidate(void)
256 {
257 	mutex_lock(&host_lock);
258 	gethost_invalidate_unlocked();
259 	mutex_unlock(&host_lock);
260 }
261 
262 static void
263 gethost_invalidate_unlocked(void)
264 {
265 	operate_hash_addr(hnam_hash, do_invalidate, (char *)GETHOSTBYNAME);
266 	operate_hash_addr(addr_hash, do_invalidate, (char *)GETHOSTBYADDR);
267 	current_admin.host.nsc_invalidate_count++;
268 }
269 
270 void
271 gethost_lookup(nsc_return_t *out, int maxsize, nsc_call_t *in, time_t now)
272 {
273 	int		out_of_date;
274 	nsc_bucket_t	*retb;
275 	char 		**bucket;
276 
277 	static time_t	lastmod;
278 
279 	int bufferspace = maxsize - sizeof (nsc_return_t);
280 
281 	if (current_admin.host.nsc_enabled == 0) {
282 		out->nsc_return_code = NOSERVER;
283 		out->nsc_bufferbytesused = sizeof (*out);
284 		return;
285 	}
286 
287 	mutex_lock(&host_lock);
288 
289 	if (current_admin.host.nsc_check_files) {
290 		struct stat buf;
291 
292 		if (stat("/etc/hosts", &buf) < 0) {
293 			/*EMPTY*/;
294 		} else if (lastmod == 0) {
295 			lastmod = buf.st_mtime;
296 		} else if (lastmod < buf.st_mtime) {
297 			gethost_invalidate_unlocked();
298 			lastmod = buf.st_mtime;
299 		}
300 	}
301 
302 
303 	if (current_admin.debug_level >= DBG_ALL) {
304 		if (MASKUPDATEBIT(in->nsc_callnumber) == GETHOSTBYADDR) {
305 			logit("gethost_lookup: looking for address %s\n",
306 			inet_ntoa(*((struct in_addr *)in->nsc_u.addr.a_data)));
307 		} else {
308 			logit("gethost_lookup: looking for hostname %s\n",
309 				in->nsc_u.name);
310 		}
311 	}
312 
313 	for (;;) {
314 		if (MASKUPDATEBIT(in->nsc_callnumber) == GETHOSTBYADDR) {
315 			bucket = get_hash(addr_hash,
316 				(char *)addr_to_int(in->nsc_u.addr.a_data));
317 		} else { /* bounce excessively long requests */
318 			if (strlen(in->nsc_u.name) > NSCDMAXNAMELEN) {
319 				ucred_t *uc = NULL;
320 
321 				if (door_ucred(&uc) != 0) {
322 					logit("gethost_lookup: Name too long, "
323 					    "but no user credential: %s\n",
324 					    strerror(errno));
325 				} else {
326 					logit("gethost_lookup: Name too long "
327 					    "from pid %d uid %d\n",
328 					    ucred_getpid(uc),
329 					    ucred_getruid(uc));
330 					ucred_free(uc);
331 				}
332 
333 				out->nsc_errno = NSS_NOTFOUND;
334 				out->nsc_return_code = NOTFOUND;
335 				out->nsc_bufferbytesused = sizeof (*out);
336 				goto getout;
337 			}
338 			bucket = get_hash(hnam_hash, in->nsc_u.name);
339 		}
340 
341 		if (*bucket == (char *)-1) {	/* pending lookup */
342 			if (get_clearance(in->nsc_callnumber) != 0) {
343 				/* no threads available */
344 				out->nsc_return_code = NOSERVER;
345 				/* cannot process now */
346 				out->nsc_bufferbytesused = sizeof (*out);
347 				current_admin.host.nsc_throttle_count++;
348 				goto getout;
349 			}
350 			nscd_wait(&host_wait, &host_lock, bucket);
351 			release_clearance(in->nsc_callnumber);
352 			continue; /* go back and relookup hash bucket */
353 		}
354 		break;
355 	}
356 
357 	/*
358 	 * check for no name_service mode
359 	 */
360 
361 	if (*bucket == NULL && current_admin.avoid_nameservice) {
362 		out->nsc_return_code = NOTFOUND;
363 		out->nsc_bufferbytesused = sizeof (*out);
364 	} else if ((*bucket == NULL) ||	/* New entry in name service */
365 	    (in->nsc_callnumber & UPDATEBIT) || /* needs updating */
366 	    (out_of_date = (!current_admin.avoid_nameservice &&
367 		(current_admin.host.nsc_old_data_ok == 0) &&
368 		(((nsc_bucket_t *)*bucket)->nsc_timestamp < now)))) {
369 		/* time has expired */
370 		int saved_errno;
371 		int saved_hits = 0;
372 		struct hostent *p;
373 
374 		if (get_clearance(in->nsc_callnumber) != 0) {
375 			/* no threads available */
376 			out->nsc_return_code = NOSERVER;
377 			/* cannot process now */
378 			out->nsc_bufferbytesused = sizeof (*out);
379 			current_admin.host.nsc_throttle_count++;
380 			goto getout;
381 		}
382 
383 		if (*bucket != NULL) {
384 			saved_hits = ((nsc_bucket_t *)*bucket)->nsc_hits;
385 		}
386 
387 		/*
388 		 * block any threads accessing this bucket if data is
389 		 * non-existent or out of date
390 		 */
391 
392 		if (*bucket == NULL || out_of_date) {
393 			update_host_bucket((nsc_bucket_t **)bucket,
394 						(nsc_bucket_t *)-1,
395 						in->nsc_callnumber);
396 		} else {
397 		/*
398 		 * if still not -1 bucket we are doing update... mark
399 		 * to prevent pileups of threads if the name service
400 		 * is hanging....
401 		 */
402 			((nsc_bucket_t *)(*bucket))->nsc_status |=
403 						ST_UPDATE_PENDING;
404 			/* cleared by deletion of old data */
405 		}
406 		mutex_unlock(&host_lock);
407 
408 		if (MASKUPDATEBIT(in->nsc_callnumber) == GETHOSTBYADDR) {
409 			p = _uncached_gethostbyaddr_r(in->nsc_u.addr.a_data,
410 				in->nsc_u.addr.a_length,
411 				in->nsc_u.addr.a_type,
412 				&out->nsc_u.hst,
413 				out->nsc_u.buff+sizeof (struct hostent),
414 						bufferspace,
415 						&saved_errno);
416 		} else {
417 			p = _uncached_gethostbyname_r(in->nsc_u.name,
418 				&out->nsc_u.hst,
419 				out->nsc_u.buff+sizeof (struct hostent),
420 						bufferspace,
421 						&saved_errno);
422 		}
423 
424 		mutex_lock(&host_lock);
425 
426 		release_clearance(in->nsc_callnumber);
427 
428 		if (p == NULL) { /* data not found */
429 			if (current_admin.debug_level >= DBG_CANT_FIND) {
430 				if (MASKUPDATEBIT(in->nsc_callnumber) ==
431 					GETHOSTBYADDR) {
432 		logit("gethost_lookup: nscd COULDN'T FIND address %s\n",
433 			inet_ntoa(*((struct in_addr *)in->nsc_u.addr.a_data)));
434 				} else {
435 		logit("gethost_lookup: nscd COULDN'T FIND host name %s\n",
436 						in->nsc_u.name);
437 				}
438 			}
439 
440 			if (!(UPDATEBIT & in->nsc_callnumber))
441 				current_admin.host.nsc_neg_cache_misses++;
442 
443 			retb = (nsc_bucket_t *)malloc(sizeof (nsc_bucket_t));
444 
445 			retb->nsc_refcount = 1;
446 			retb->nsc_data.nsc_return_code = NOTFOUND;
447 			retb->nsc_data.nsc_bufferbytesused =
448 				sizeof (nsc_return_t);
449 			retb->nsc_data.nsc_errno = saved_errno;
450 			memcpy(out, &(retb->nsc_data),
451 				retb->nsc_data.nsc_bufferbytesused);
452 			update_host_bucket((nsc_bucket_t **)bucket, retb,
453 				in->nsc_callnumber);
454 			goto getout;
455 		} else {
456 			if (current_admin.debug_level >= DBG_ALL) {
457 				if (MASKUPDATEBIT(in->nsc_callnumber) ==
458 						GETHOSTBYADDR) {
459 				logit("gethost_lookup: nscd FOUND addr %s\n",
460 			inet_ntoa(*((struct in_addr *)in->nsc_u.addr.a_data)));
461 				} else {
462 			logit("gethost_lookup: nscd FOUND host name %s\n",
463 						in->nsc_u.name);
464 				}
465 			}
466 			if (!(UPDATEBIT & in->nsc_callnumber))
467 				current_admin.host.nsc_pos_cache_misses++;
468 
469 			retb = fixbuffer(out, bufferspace);
470 
471 			update_host_bucket((nsc_bucket_t **)bucket, retb,
472 				in->nsc_callnumber);
473 			if (saved_hits)
474 				retb->nsc_hits = saved_hits;
475 		}
476 	} else { 	/* found entry in cache */
477 		retb = (nsc_bucket_t *)*bucket;
478 
479 		retb->nsc_hits++;
480 
481 		memcpy(out, &(retb->nsc_data),
482 			retb->nsc_data.nsc_bufferbytesused);
483 
484 		if (out->nsc_return_code == SUCCESS) {
485 			if (!(UPDATEBIT & in->nsc_callnumber))
486 			    current_admin.host.nsc_pos_cache_hits++;
487 			if (current_admin.debug_level >= DBG_ALL) {
488 				if (MASKUPDATEBIT(in->nsc_callnumber) ==
489 					GETHOSTBYADDR) {
490 			logit("gethost_lookup: found address %s in cache\n",
491 			inet_ntoa(*((struct in_addr *)in->nsc_u.addr.a_data)));
492 				} else {
493 			logit("gethost_lookup: found host name %s in cache\n",
494 						in->nsc_u.name);
495 				}
496 			}
497 		} else {
498 			if (!(UPDATEBIT & in->nsc_callnumber))
499 			    current_admin.host.nsc_neg_cache_hits++;
500 			if (current_admin.debug_level >= DBG_ALL) {
501 				if (MASKUPDATEBIT(in->nsc_callnumber) ==
502 					GETHOSTBYADDR) {
503 		logit("gethost_lookup: %s marked as NOT FOUND in cache.\n",
504 			inet_ntoa(*((struct in_addr *)in->nsc_u.addr.a_data)));
505 				} else {
506 		logit("gethost_lookup: %s marked as NOT FOUND in cache.\n",
507 						in->nsc_u.name);
508 				}
509 			}
510 		}
511 
512 		if ((retb->nsc_timestamp < now) &&
513 			!(in->nsc_callnumber & UPDATEBIT) &&
514 			!(retb->nsc_status & ST_UPDATE_PENDING)) {
515 		logit("launch update since time = %d\n", retb->nsc_timestamp);
516 			/* cleared by deletion of old data */
517 			retb->nsc_status |= ST_UPDATE_PENDING;
518 			launch_update(in);
519 		}
520 	}
521 
522 getout:
523 
524 	mutex_unlock(&host_lock);
525 }
526 
527 /*ARGSUSED*/
528 static void
529 update_host_bucket(nsc_bucket_t **old, nsc_bucket_t *new, int callnumber)
530 {
531 	if (*old != NULL && *old != (nsc_bucket_t *)-1) {
532 		/* old data exists */
533 		free(*old);
534 		current_admin.host.nsc_entries--;
535 	}
536 
537 	/*
538 	 *  we can do this before reseting *old since we're holding the lock
539 	 */
540 
541 	else if (*old == (nsc_bucket_t *)-1) {
542 		nscd_signal(&host_wait, (char **)old);
543 	}
544 
545 
546 
547 	*old = new;
548 
549 	if ((new != NULL) &&
550 		(new != (nsc_bucket_t *)-1)) {
551 		/* real data, not just update pending or invalidate */
552 
553 		new->nsc_hits = 1;
554 		new->nsc_status = 0;
555 		new->nsc_refcount = 1;
556 		current_admin.host.nsc_entries++;
557 
558 		if (new->nsc_data.nsc_return_code == SUCCESS) {
559 			new->nsc_timestamp = time(NULL) +
560 				current_admin.host.nsc_pos_ttl;
561 		} else {
562 			new->nsc_timestamp = time(NULL) +
563 				current_admin.host.nsc_neg_ttl;
564 		}
565 	}
566 }
567 
568 
569 /*ARGSUSED*/
570 static nsc_bucket_t *
571 fixbuffer(nsc_return_t *in, int maxlen)
572 {
573 	nsc_return_t *out;
574 	nsc_bucket_t *retb;
575 	char *dest;
576 	char **aliaseslist;
577 	char **addrlist;
578 	int offset;
579 	int strs;
580 	int i;
581 	int numaliases;
582 	int numaddrs;
583 
584 	/*
585 	 *  find out the size of the data block we're going to need
586 	 */
587 
588 	strs = 1 + strlen(in->nsc_u.hst.h_name);
589 	for (numaliases = 0; in->nsc_u.hst.h_aliases[numaliases]; numaliases++)
590 		strs += 1 + strlen(in->nsc_u.hst.h_aliases[numaliases]);
591 	strs += sizeof (char *) * (numaliases+1);
592 	for (numaddrs = 0; in->nsc_u.hst.h_addr_list[numaddrs]; numaddrs++)
593 		strs += in->nsc_u.hst.h_length;
594 	strs += sizeof (char *) * (numaddrs+1+3);
595 
596 	/*
597 	 * allocate it and copy it in
598 	 * code doesn't assume packing order in original buffer
599 	 */
600 
601 	if ((retb = (nsc_bucket_t *)malloc(sizeof (*retb) + strs)) == NULL) {
602 		return (NULL);
603 	}
604 
605 	out = &(retb->nsc_data);
606 	out->nsc_bufferbytesused = sizeof (*in) + strs;
607 	out->nsc_return_code 	= SUCCESS;
608 	out->nsc_errno 		= 0;
609 
610 
611 	dest = retb->nsc_data.nsc_u.buff + sizeof (struct hostent);
612 
613 	offset = (int)dest;
614 
615 	/*
616 	 * allocat the h_aliases list and the h_addr_list first to align 'em.
617 	 */
618 	aliaseslist = (char **)dest;
619 
620 	dest += sizeof (char *) * (numaliases+1);
621 
622 	addrlist = (char **)dest;
623 
624 	dest += sizeof (char *) * (numaddrs+1);
625 
626 	strcpy(dest, in->nsc_u.hst.h_name);
627 	strs = 1 + strlen(in->nsc_u.hst.h_name);
628 	out->nsc_u.hst.h_name = dest - offset;
629 	dest += strs;
630 
631 
632 	/*
633 	 * fill out the h_aliases list
634 	 */
635 	for (i = 0; i < numaliases; i++) {
636 		strcpy(dest, in->nsc_u.hst.h_aliases[i]);
637 		strs = 1 + strlen(in->nsc_u.hst.h_aliases[i]);
638 		aliaseslist[i] = dest - offset;
639 		dest += strs;
640 	}
641 	aliaseslist[i] = 0;	/* null term ptr chain */
642 
643 	out->nsc_u.hst.h_aliases = (char **)((int)aliaseslist-offset);
644 
645 	/*
646 	 * fill out the h_addr list
647 	 */
648 
649 	dest = (char *)(((int)dest + 3) & ~3);
650 
651 	for (i = 0; i < numaddrs; i++) {
652 		memcpy(dest, in->nsc_u.hst.h_addr_list[i],
653 			in->nsc_u.hst.h_length);
654 		strs = in->nsc_u.hst.h_length;
655 		addrlist[i] = dest - offset;
656 		dest += strs;
657 		dest = (char *)(((int)dest + 3) & ~3);
658 	}
659 
660 	addrlist[i] = 0;	/* null term ptr chain */
661 
662 	out->nsc_u.hst.h_addr_list = (char **)((int)addrlist-offset);
663 
664 	out->nsc_u.hst.h_length = in->nsc_u.hst.h_length;
665 	out->nsc_u.hst.h_addrtype = in->nsc_u.hst.h_addrtype;
666 
667 	memcpy(in, &(retb->nsc_data), retb->nsc_data.nsc_bufferbytesused);
668 
669 	return (retb);
670 
671 }
672 
673 void
674 gethost_nam_reaper()
675 {
676 	nsc_reaper("gethost_nam", hnam_hash, &current_admin.host, &host_lock);
677 }
678 
679 void
680 gethost_addr_reaper()
681 {
682 	nsc_reaper("gethost_addr", addr_hash, &current_admin.host, &host_lock);
683 }
684