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