addrinfo cache handling in nscd.
[kopensolaris-gnu/glibc.git] / nscd / aicache.c
1 /* Cache handling for host lookup.
2    Copyright (C) 2004 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_gethostbyname2_r)
34   (const char *name, int af, struct hostent *host,
35    char *buffer, size_t buflen, int *errnop,
36    int *h_errnop);
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   uid_t oldeuid = 0;
62
63   /* We allocate all data in one memory block: the iov vector,
64      the response header and the dataset itself.  */
65   struct dataset
66   {
67     struct datahead head;
68     ai_response_header resp;
69     char strdata[0];
70   } *dataset = NULL;
71
72   if (__builtin_expect (debug_level > 0, 0))
73     {
74       if (he == NULL)
75         dbg_log (_("Haven't found \"%s\" in hosts cache!"), (char *) key);
76       else
77         dbg_log (_("Reloading \"%s\" in hosts cache!"), (char *) key);
78     }
79
80   if (db->secure)
81     {
82       oldeuid = geteuid ();
83       seteuid (uid);
84     }
85
86   static service_user *hosts_database;
87   service_user *nip = NULL;
88   int no_more;
89   int rc6 = 0;
90   int rc4 = 0;
91   int herrno = 0;
92
93   if (hosts_database != NULL)
94     {
95       nip = hosts_database;
96       no_more = 0;
97     }
98   else
99     no_more = __nss_database_lookup ("hosts", NULL,
100                                      "dns [!UNAVAIL=return] files", &nip);
101
102   if (__res_maybe_init (&_res, 0) == -1)
103             no_more = 1;
104
105   /* If we are looking for both IPv4 and IPv6 address we don't want
106      the lookup functions to automatically promote IPv4 addresses to
107      IPv6 addresses.  Currently this is decided by setting the
108      RES_USE_INET6 bit in _res.options.  */
109   int old_res_options = _res.options;
110   _res.options &= ~RES_USE_INET6;
111
112   size_t tmpbuf6len = 512;
113   char *tmpbuf6 = alloca (tmpbuf6len);
114   size_t tmpbuf4len = 0;
115   char *tmpbuf4 = NULL;
116   char *canon = NULL;
117   ssize_t total = 0;
118   char *key_copy = NULL;
119   bool alloca_used = false;
120
121   while (!no_more)
122     {
123       nss_gethostbyname2_r fct = __nss_lookup_function (nip,
124                                                         "gethostbyname2_r");
125       int status[2] = { NSS_STATUS_UNAVAIL, NSS_STATUS_UNAVAIL };
126
127       if (fct != NULL)
128         {
129  printf("fct=%p\n",fct);
130           struct hostent th[2];
131
132           /* Collect IPv6 information first.  */
133           while (1)
134             {
135               rc6 = 0;
136               status[0] = DL_CALL_FCT (fct, (key, AF_INET6, &th[0], tmpbuf6,
137                                              tmpbuf6len, &rc6, &herrno));
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               if (rc4 != ERANGE || herrno != NETDB_INTERNAL)
166                 break;
167               tmpbuf4 = extend_alloca (tmpbuf6, tmpbuf6len, 2 * tmpbuf6len);
168             }
169
170           if (rc4 != 0 || herrno == NETDB_INTERNAL)
171             goto out;
172
173           if (status[0] == NSS_STATUS_SUCCESS
174               || status[1] == NSS_STATUS_SUCCESS)
175             {
176               /* We found the data.  Count the addresses and the size.  */
177               int naddrs = 0;
178               size_t addrslen = 0;
179               for (int j = 0; j < 2; ++j)
180                 if (status[j] == NSS_STATUS_SUCCESS)
181                   for (int i = 0; th[j].h_addr_list[i] != NULL; ++i)
182                     {
183                       ++naddrs;
184                       addrslen += th[j].h_length;
185                     }
186
187               /* Determine the canonical name.  */
188               nss_getcanonname_r cfct;
189               cfct = __nss_lookup_function (nip, "getcanonname_r");
190               if (cfct != NULL)
191                 {
192                   const size_t max_fqdn_len = 256;
193                   char *buf = alloca (max_fqdn_len);
194                   char *s;
195                   int rc;
196
197                   if (DL_CALL_FCT (cfct, (key, buf, max_fqdn_len, &s, &rc,
198                                           &herrno)) == NSS_STATUS_SUCCESS)
199                     canon = s;
200                   else
201                     /* Set to name now to avoid using gethostbyaddr.  */
202                     canon = key;
203                 }
204               else
205                 {
206                   // XXX use gethostbyaddr
207                 }
208               size_t canonlen = canon == NULL ? 0 : (strlen (canon) + 1);
209
210               total = sizeof (*dataset) + naddrs + addrslen + canonlen;
211
212               /* Now we can allocate the data structure.  */
213               if (he == NULL)
214                 {
215                   dataset = (struct dataset *) mempool_alloc (db,
216                                                               total
217                                                               + req->key_len);
218                   if (dataset == NULL)
219                     ++db->head->addfailed;
220                 }
221
222               if (dataset == NULL)
223                 {
224                   /* We cannot permanently add the result in the moment.  But
225                      we can provide the result as is.  Store the data in some
226                      temporary memory.  */
227                   dataset = (struct dataset *) alloca (total + req->key_len);
228
229                   /* We cannot add this record to the permanent database.  */
230                   alloca_used = true;
231                 }
232
233               dataset->head.allocsize = total + req->key_len;
234               dataset->head.recsize = total - offsetof (struct dataset, resp);
235               dataset->head.notfound = false;
236               dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
237               dataset->head.usable = true;
238
239               /* Compute the timeout time.  */
240               dataset->head.timeout = time (NULL) + db->postimeout;
241
242               dataset->resp.version = NSCD_VERSION;
243               dataset->resp.found = 1;
244               dataset->resp.naddrs = naddrs;
245               dataset->resp.addrslen = addrslen;
246               dataset->resp.canonlen = canonlen;
247               dataset->resp.error = NETDB_SUCCESS;
248
249               char *addrs = (char *) (&dataset->resp + 1);
250               uint8_t *family = (uint8_t *) (addrs + addrslen);
251
252               for (int j = 0; j < 2; ++j)
253                 if (status[j] == NSS_STATUS_SUCCESS)
254                   for (int i = 0; th[j].h_addr_list[i] != NULL; ++i)
255                     {
256                       addrs = mempcpy (addrs, th[j].h_addr_list[i],
257                                        th[j].h_length);
258                       *family++ = th[j].h_addrtype;
259                     }
260
261               char *cp = family;
262               if (canon != NULL)
263                 cp = mempcpy (cp, canon, canonlen);
264
265               key_copy = memcpy (cp, key, req->key_len);
266
267               /* Now we can determine whether on refill we have to
268                  create a new record or not.  */
269               if (he != NULL)
270                 {
271                   assert (fd == -1);
272
273                   if (total + req->key_len == dh->allocsize
274                       && total - offsetof (struct dataset, resp) == dh->recsize
275                       && memcmp (&dataset->resp, dh->data,
276                                  dh->allocsize
277                                  - offsetof (struct dataset, resp)) == 0)
278                     {
279                       /* The data has not changed.  We will just bump the
280                          timeout value.  Note that the new record has been
281                          allocated on the stack and need not be freed.  */
282                       dh->timeout = dataset->head.timeout;
283                       ++dh->nreloads;
284                     }
285                   else
286                     {
287                       /* We have to create a new record.  Just allocate
288                          appropriate memory and copy it.  */
289                       struct dataset *newp
290                         = (struct dataset *) mempool_alloc (db,
291                                                             total
292                                                             + req->key_len);
293                       if (newp != NULL)
294                         {
295                           /* Adjust pointer into the memory block.  */
296                           key_copy = (char *) newp + (key_copy
297                                                       - (char *) dataset);
298
299                           dataset = memcpy (newp, dataset,
300                                             total + req->key_len);
301                           alloca_used = false;
302                         }
303
304                       /* Mark the old record as obsolete.  */
305                       dh->usable = false;
306                     }
307                 }
308               else
309                 {
310                   /* We write the dataset before inserting it to the
311                      database since while inserting this thread might
312                      block and so would unnecessarily let the receiver
313                      wait.  */
314                   assert (fd != -1);
315
316                   TEMP_FAILURE_RETRY (write (fd, &dataset->resp, total));
317                 }
318
319               goto out;
320             }
321
322         }
323
324       if (nss_next_action (nip, status[1]) == NSS_ACTION_RETURN)
325         break;
326
327       if (nip->next == NULL)
328         no_more = -1;
329       else
330         nip = nip->next;
331     }
332
333   /* No result found.  Create a negative result record.  */
334   if (he != NULL && rc4 == EAGAIN)
335     {
336       /* If we have an old record available but cannot find one now
337          because the service is not available we keep the old record
338          and make sure it does not get removed.  */
339       if (reload_count != UINT_MAX && dh->nreloads == reload_count)
340         /* Do not reset the value if we never not reload the record.  */
341         dh->nreloads = reload_count - 1;
342     }
343   else
344     {
345       /* We have no data.  This means we send the standard reply for
346          this case.  */
347       total = sizeof (notfound);
348
349       if (fd != -1)
350         TEMP_FAILURE_RETRY (write (fd, &notfound, total));
351
352       dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len);
353       /* If we cannot permanently store the result, so be it.  */
354       if (dataset != NULL)
355         {
356           dataset->head.allocsize = sizeof (struct dataset) + req->key_len;
357           dataset->head.recsize = total;
358           dataset->head.notfound = true;
359           dataset->head.nreloads = 0;
360           dataset->head.usable = true;
361
362           /* Compute the timeout time.  */
363           dataset->head.timeout = time (NULL) + db->negtimeout;
364
365           /* This is the reply.  */
366           memcpy (&dataset->resp, &notfound, total);
367
368           /* Copy the key data.  */
369           key_copy = memcpy (dataset->strdata, key, req->key_len);
370         }
371       else
372         ++db->head->addfailed;
373    }
374
375  out:
376   _res.options = old_res_options;
377
378   if (db->secure)
379     seteuid (oldeuid);
380
381   if (dataset != NULL && !alloca_used)
382     {
383       /* If necessary, we also propagate the data to disk.  */
384       if (db->persistent)
385         {
386           // XXX async OK?
387           uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
388           msync ((void *) pval,
389                  ((uintptr_t) dataset & pagesize_m1) + total + req->key_len,
390                  MS_ASYNC);
391         }
392
393       /* Now get the lock to safely insert the records.  */
394       pthread_rwlock_rdlock (&db->lock);
395
396       if (cache_add (req->type, key_copy, req->key_len, &dataset->head, true,
397                      db, uid) < 0)
398         /* Ensure the data can be recovered.  */
399         dataset->head.usable = false;
400
401       pthread_rwlock_unlock (&db->lock);
402
403       /* Mark the old entry as obsolete.  */
404       if (dh != NULL)
405         dh->usable = false;
406     }
407 }
408
409
410 void
411 addhstai (struct database_dyn *db, int fd, request_header *req, void *key,
412           uid_t uid)
413 {
414   addhstaiX (db, fd, req, key, uid, NULL, NULL);
415 }
416
417
418 void
419 readdhstai (struct database_dyn *db, struct hashentry *he, struct datahead *dh)
420 {
421   request_header req =
422     {
423       .type = GETAI,
424       .key_len = he->len
425     };
426
427   addhstaiX (db, -1, &req, db->data + he->key, he->owner, he, dh);
428 }