]> git.ipfire.org Git - thirdparty/glibc.git/blob - nscd/aicache.c
* nscd/aicache.c (addhstaiX): If reported TTL is zero don't cache
[thirdparty/glibc.git] / nscd / aicache.c
1 /* Cache handling for host lookup.
2 Copyright (C) 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4 Contributed by Ulrich Drepper <drepper@redhat.com>, 2004.
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License version 2 as
8 published by the Free Software Foundation.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software Foundation,
17 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
18
19 #include <assert.h>
20 #include <errno.h>
21 #include <libintl.h>
22 #include <netdb.h>
23 #include <string.h>
24 #include <time.h>
25 #include <unistd.h>
26 #include <sys/mman.h>
27
28 #include "dbg_log.h"
29 #include "nscd.h"
30 #ifdef HAVE_SENDFILE
31 # include <kernel-features.h>
32 #endif
33
34
35 typedef enum nss_status (*nss_gethostbyname3_r)
36 (const char *name, int af, struct hostent *host,
37 char *buffer, size_t buflen, int *errnop,
38 int *h_errnop, int32_t *, char **);
39 typedef enum nss_status (*nss_getcanonname_r)
40 (const char *name, char *buffer, size_t buflen, char **result,
41 int *errnop, int *h_errnop);
42
43
44 static const ai_response_header notfound =
45 {
46 .version = NSCD_VERSION,
47 .found = 0,
48 .naddrs = 0,
49 .addrslen = 0,
50 .canonlen = 0,
51 .error = 0
52 };
53
54
55 static void
56 addhstaiX (struct database_dyn *db, int fd, request_header *req,
57 void *key, uid_t uid, struct hashentry *he, struct datahead *dh)
58 {
59 /* Search for the entry matching the key. Please note that we don't
60 look again in the table whether the dataset is now available. We
61 simply insert it. It does not matter if it is in there twice. The
62 pruning function only will look at the timestamp. */
63
64 /* We allocate all data in one memory block: the iov vector,
65 the response header and the dataset itself. */
66 struct dataset
67 {
68 struct datahead head;
69 ai_response_header resp;
70 char strdata[0];
71 } *dataset = NULL;
72
73 if (__builtin_expect (debug_level > 0, 0))
74 {
75 if (he == NULL)
76 dbg_log (_("Haven't found \"%s\" in hosts cache!"), (char *) key);
77 else
78 dbg_log (_("Reloading \"%s\" in hosts cache!"), (char *) key);
79 }
80
81 static service_user *hosts_database;
82 service_user *nip = NULL;
83 int no_more;
84 int rc6 = 0;
85 int rc4 = 0;
86 int herrno = 0;
87
88 if (hosts_database != NULL)
89 {
90 nip = hosts_database;
91 no_more = 0;
92 }
93 else
94 no_more = __nss_database_lookup ("hosts", NULL,
95 "dns [!UNAVAIL=return] files", &nip);
96
97 if (__res_maybe_init (&_res, 0) == -1)
98 no_more = 1;
99
100 /* If we are looking for both IPv4 and IPv6 address we don't want
101 the lookup functions to automatically promote IPv4 addresses to
102 IPv6 addresses. Currently this is decided by setting the
103 RES_USE_INET6 bit in _res.options. */
104 int old_res_options = _res.options;
105 _res.options &= ~RES_USE_INET6;
106
107 size_t tmpbuf6len = 512;
108 char *tmpbuf6 = alloca (tmpbuf6len);
109 size_t tmpbuf4len = 0;
110 char *tmpbuf4 = NULL;
111 char *canon = NULL;
112 int32_t ttl = UINT32_MAX;
113 ssize_t total = 0;
114 char *key_copy = NULL;
115 bool alloca_used = false;
116
117 while (!no_more)
118 {
119 int status[2] = { NSS_STATUS_UNAVAIL, NSS_STATUS_UNAVAIL };
120
121 /* Prefer the function which also returns the TTL and canonical name. */
122 nss_gethostbyname3_r fct = __nss_lookup_function (nip,
123 "gethostbyname3_r");
124 if (fct == NULL)
125 fct = __nss_lookup_function (nip, "gethostbyname2_r");
126
127 if (fct != NULL)
128 {
129 struct hostent th[2];
130
131 /* Collect IPv6 information first. */
132 while (1)
133 {
134 rc6 = 0;
135 status[0] = DL_CALL_FCT (fct, (key, AF_INET6, &th[0], tmpbuf6,
136 tmpbuf6len, &rc6, &herrno,
137 &ttl, &canon));
138 if (rc6 != ERANGE || herrno != NETDB_INTERNAL)
139 break;
140 tmpbuf6 = extend_alloca (tmpbuf6, tmpbuf6len, 2 * tmpbuf6len);
141 }
142
143 if (rc6 != 0 && herrno == NETDB_INTERNAL)
144 goto out;
145
146 /* If the IPv6 lookup has been successful do not use the
147 buffer used in that lookup, use a new one. */
148 if (status[0] == NSS_STATUS_SUCCESS && rc6 == 0)
149 {
150 tmpbuf4len = 512;
151 tmpbuf4 = alloca (tmpbuf4len);
152 }
153 else
154 {
155 tmpbuf4len = tmpbuf6len;
156 tmpbuf4 = tmpbuf6;
157 }
158
159 /* Next collect IPv4 information first. */
160 while (1)
161 {
162 rc4 = 0;
163 status[1] = DL_CALL_FCT (fct, (key, AF_INET, &th[1], tmpbuf4,
164 tmpbuf4len, &rc4, &herrno,
165 ttl == UINT32_MAX ? &ttl : NULL,
166 canon == NULL ? &canon : NULL));
167 if (rc4 != ERANGE || herrno != NETDB_INTERNAL)
168 break;
169 tmpbuf4 = extend_alloca (tmpbuf4, tmpbuf4len, 2 * tmpbuf4len);
170 }
171
172 if (rc4 != 0 || herrno == NETDB_INTERNAL)
173 goto out;
174
175 if (status[0] == NSS_STATUS_SUCCESS
176 || status[1] == NSS_STATUS_SUCCESS)
177 {
178 /* We found the data. Count the addresses and the size. */
179 int naddrs = 0;
180 size_t addrslen = 0;
181 for (int j = 0; j < 2; ++j)
182 if (status[j] == NSS_STATUS_SUCCESS)
183 for (int i = 0; th[j].h_addr_list[i] != NULL; ++i)
184 {
185 ++naddrs;
186 addrslen += th[j].h_length;
187 }
188
189 if (canon == NULL)
190 {
191 /* Determine the canonical name. */
192 nss_getcanonname_r cfct;
193 cfct = __nss_lookup_function (nip, "getcanonname_r");
194 if (cfct != NULL)
195 {
196 const size_t max_fqdn_len = 256;
197 char *buf = alloca (max_fqdn_len);
198 char *s;
199 int rc;
200
201 if (DL_CALL_FCT (cfct, (key, buf, max_fqdn_len, &s, &rc,
202 &herrno)) == NSS_STATUS_SUCCESS)
203 canon = s;
204 else
205 /* Set to name now to avoid using gethostbyaddr. */
206 canon = key;
207 }
208 else
209 {
210 struct hostent *he = NULL;
211 int herrno;
212 struct hostent he_mem;
213 void *addr;
214 size_t addrlen;
215 int addrfamily;
216
217 if (status[1] == NSS_STATUS_SUCCESS)
218 {
219 addr = th[1].h_addr_list[0];
220 addrlen = sizeof (struct in_addr);
221 addrfamily = AF_INET;
222 }
223 else
224 {
225 addr = th[0].h_addr_list[0];
226 addrlen = sizeof (struct in6_addr);
227 addrfamily = AF_INET6;
228 }
229
230 size_t tmpbuflen = 512;
231 char *tmpbuf = alloca (tmpbuflen);
232 int rc;
233 while (1)
234 {
235 rc = __gethostbyaddr_r (addr, addrlen, addrfamily,
236 &he_mem, tmpbuf, tmpbuflen,
237 &he, &herrno);
238 if (rc != ERANGE || herrno != NETDB_INTERNAL)
239 break;
240 tmpbuf = extend_alloca (tmpbuf, tmpbuflen,
241 tmpbuflen * 2);
242 }
243
244 if (rc == 0)
245 {
246 if (he != NULL)
247 canon = he->h_name;
248 else
249 canon = key;
250 }
251 }
252 }
253 size_t canonlen = canon == NULL ? 0 : (strlen (canon) + 1);
254
255 total = sizeof (*dataset) + naddrs + addrslen + canonlen;
256
257 /* Now we can allocate the data structure. If the TTL
258 of the entry is reported as zero do not cache the
259 entry at all. */
260 if (ttl != 0 && he == NULL)
261 {
262 dataset = (struct dataset *) mempool_alloc (db,
263 total
264 + req->key_len);
265 if (dataset == NULL)
266 ++db->head->addfailed;
267 }
268
269 if (dataset == NULL)
270 {
271 /* We cannot permanently add the result in the moment. But
272 we can provide the result as is. Store the data in some
273 temporary memory. */
274 dataset = (struct dataset *) alloca (total + req->key_len);
275
276 /* We cannot add this record to the permanent database. */
277 alloca_used = true;
278 }
279
280 dataset->head.allocsize = total + req->key_len;
281 dataset->head.recsize = total - offsetof (struct dataset, resp);
282 dataset->head.notfound = false;
283 dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
284 dataset->head.usable = true;
285
286 /* Compute the timeout time. */
287 dataset->head.timeout = time (NULL) + MIN (db->postimeout, ttl);
288
289 dataset->resp.version = NSCD_VERSION;
290 dataset->resp.found = 1;
291 dataset->resp.naddrs = naddrs;
292 dataset->resp.addrslen = addrslen;
293 dataset->resp.canonlen = canonlen;
294 dataset->resp.error = NETDB_SUCCESS;
295
296 char *addrs = (char *) (&dataset->resp + 1);
297 uint8_t *family = (uint8_t *) (addrs + addrslen);
298
299 for (int j = 0; j < 2; ++j)
300 if (status[j] == NSS_STATUS_SUCCESS)
301 for (int i = 0; th[j].h_addr_list[i] != NULL; ++i)
302 {
303 addrs = mempcpy (addrs, th[j].h_addr_list[i],
304 th[j].h_length);
305 *family++ = th[j].h_addrtype;
306 }
307
308 void *cp = family;
309 if (canon != NULL)
310 cp = mempcpy (cp, canon, canonlen);
311
312 key_copy = memcpy (cp, key, req->key_len);
313
314 /* Now we can determine whether on refill we have to
315 create a new record or not. */
316 if (he != NULL)
317 {
318 assert (fd == -1);
319
320 if (total + req->key_len == dh->allocsize
321 && total - offsetof (struct dataset, resp) == dh->recsize
322 && memcmp (&dataset->resp, dh->data,
323 dh->allocsize
324 - offsetof (struct dataset, resp)) == 0)
325 {
326 /* The data has not changed. We will just bump the
327 timeout value. Note that the new record has been
328 allocated on the stack and need not be freed. */
329 dh->timeout = dataset->head.timeout;
330 ++dh->nreloads;
331 }
332 else
333 {
334 /* We have to create a new record. Just allocate
335 appropriate memory and copy it. */
336 struct dataset *newp
337 = (struct dataset *) mempool_alloc (db,
338 total
339 + req->key_len);
340 if (newp != NULL)
341 {
342 /* Adjust pointer into the memory block. */
343 key_copy = (char *) newp + (key_copy
344 - (char *) dataset);
345
346 dataset = memcpy (newp, dataset,
347 total + req->key_len);
348 alloca_used = false;
349 }
350
351 /* Mark the old record as obsolete. */
352 dh->usable = false;
353 }
354 }
355 else
356 {
357 /* We write the dataset before inserting it to the
358 database since while inserting this thread might
359 block and so would unnecessarily let the receiver
360 wait. */
361 assert (fd != -1);
362
363 #ifdef HAVE_SENDFILE
364 if (__builtin_expect (db->mmap_used, 1) && !alloca_used)
365 {
366 assert (db->wr_fd != -1);
367 assert ((char *) &dataset->resp > (char *) db->data);
368 assert ((char *) &dataset->resp - (char *) db->head
369 + total
370 <= (sizeof (struct database_pers_head)
371 + db->head->module * sizeof (ref_t)
372 + db->head->data_size));
373 ssize_t written;
374 written = sendfileall (fd, db->wr_fd,
375 (char *) &dataset->resp
376 - (char *) db->head, total);
377 # ifndef __ASSUME_SENDFILE
378 if (written == -1 && errno == ENOSYS)
379 goto use_write;
380 # endif
381 }
382 else
383 # ifndef __ASSUME_SENDFILE
384 use_write:
385 # endif
386 #endif
387 writeall (fd, &dataset->resp, total);
388 }
389
390 goto out;
391 }
392
393 }
394
395 if (nss_next_action (nip, status[1]) == NSS_ACTION_RETURN)
396 break;
397
398 if (nip->next == NULL)
399 no_more = -1;
400 else
401 nip = nip->next;
402 }
403
404 /* No result found. Create a negative result record. */
405 if (he != NULL && rc4 == EAGAIN)
406 {
407 /* If we have an old record available but cannot find one now
408 because the service is not available we keep the old record
409 and make sure it does not get removed. */
410 if (reload_count != UINT_MAX && dh->nreloads == reload_count)
411 /* Do not reset the value if we never not reload the record. */
412 dh->nreloads = reload_count - 1;
413 }
414 else
415 {
416 /* We have no data. This means we send the standard reply for
417 this case. */
418 total = sizeof (notfound);
419
420 if (fd != -1)
421 TEMP_FAILURE_RETRY (send (fd, &notfound, total, MSG_NOSIGNAL));
422
423 dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len);
424 /* If we cannot permanently store the result, so be it. */
425 if (dataset != NULL)
426 {
427 dataset->head.allocsize = sizeof (struct dataset) + req->key_len;
428 dataset->head.recsize = total;
429 dataset->head.notfound = true;
430 dataset->head.nreloads = 0;
431 dataset->head.usable = true;
432
433 /* Compute the timeout time. */
434 dataset->head.timeout = time (NULL) + db->negtimeout;
435
436 /* This is the reply. */
437 memcpy (&dataset->resp, &notfound, total);
438
439 /* Copy the key data. */
440 key_copy = memcpy (dataset->strdata, key, req->key_len);
441 }
442 else
443 ++db->head->addfailed;
444 }
445
446 out:
447 _res.options = old_res_options;
448
449 if (dataset != NULL && !alloca_used)
450 {
451 /* If necessary, we also propagate the data to disk. */
452 if (db->persistent)
453 {
454 // XXX async OK?
455 uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
456 msync ((void *) pval,
457 ((uintptr_t) dataset & pagesize_m1) + total + req->key_len,
458 MS_ASYNC);
459 }
460
461 /* Now get the lock to safely insert the records. */
462 pthread_rwlock_rdlock (&db->lock);
463
464 if (cache_add (req->type, key_copy, req->key_len, &dataset->head, true,
465 db, uid) < 0)
466 /* Ensure the data can be recovered. */
467 dataset->head.usable = false;
468
469 pthread_rwlock_unlock (&db->lock);
470
471 /* Mark the old entry as obsolete. */
472 if (dh != NULL)
473 dh->usable = false;
474 }
475 }
476
477
478 void
479 addhstai (struct database_dyn *db, int fd, request_header *req, void *key,
480 uid_t uid)
481 {
482 addhstaiX (db, fd, req, key, uid, NULL, NULL);
483 }
484
485
486 void
487 readdhstai (struct database_dyn *db, struct hashentry *he, struct datahead *dh)
488 {
489 request_header req =
490 {
491 .type = GETAI,
492 .key_len = he->len
493 };
494
495 addhstaiX (db, -1, &req, db->data + he->key, he->owner, he, dh);
496 }