Use pthread_seteuid_np instead of seteuid.
[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_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   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       pthread_seteuid_np (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   int32_t ttl = UINT32_MAX;
118   ssize_t total = 0;
119   char *key_copy = NULL;
120   bool alloca_used = false;
121
122   while (!no_more)
123     {
124       int status[2] = { NSS_STATUS_UNAVAIL, NSS_STATUS_UNAVAIL };
125
126       /* Prefer the function which also returns the TTL and canonical name.  */
127       nss_gethostbyname3_r fct = __nss_lookup_function (nip,
128                                                         "gethostbyname3_r");
129       if (fct == NULL)
130         fct = __nss_lookup_function (nip, "gethostbyname2_r");
131
132       if (fct != NULL)
133         {
134           struct hostent th[2];
135
136           /* Collect IPv6 information first.  */
137           while (1)
138             {
139               rc6 = 0;
140               status[0] = DL_CALL_FCT (fct, (key, AF_INET6, &th[0], tmpbuf6,
141                                              tmpbuf6len, &rc6, &herrno,
142                                              &ttl, &canon));
143               if (rc6 != ERANGE || herrno != NETDB_INTERNAL)
144                 break;
145               tmpbuf6 = extend_alloca (tmpbuf6, tmpbuf6len, 2 * tmpbuf6len);
146             }
147
148           if (rc6 != 0 && herrno == NETDB_INTERNAL)
149             goto out;
150
151           /* If the IPv6 lookup has been successful do not use the
152              buffer used in that lookup, use a new one.  */
153           if (status[0] == NSS_STATUS_SUCCESS && rc6 == 0)
154             {
155               tmpbuf4len = 512;
156               tmpbuf4 = alloca (tmpbuf4len);
157             }
158           else
159             {
160               tmpbuf4len = tmpbuf6len;
161               tmpbuf4 = tmpbuf6;
162             }
163
164           /* Next collect IPv4 information first.  */
165           while (1)
166             {
167               rc4 = 0;
168               status[1] = DL_CALL_FCT (fct, (key, AF_INET, &th[1], tmpbuf4,
169                                              tmpbuf4len, &rc4, &herrno,
170                                              ttl == UINT32_MAX ? &ttl : NULL,
171                                              canon == NULL ? &canon : NULL));
172               if (rc4 != ERANGE || herrno != NETDB_INTERNAL)
173                 break;
174               tmpbuf4 = extend_alloca (tmpbuf4, tmpbuf4len, 2 * tmpbuf4len);
175             }
176
177           if (rc4 != 0 || herrno == NETDB_INTERNAL)
178             goto out;
179
180           if (status[0] == NSS_STATUS_SUCCESS
181               || status[1] == NSS_STATUS_SUCCESS)
182             {
183               /* We found the data.  Count the addresses and the size.  */
184               int naddrs = 0;
185               size_t addrslen = 0;
186               for (int j = 0; j < 2; ++j)
187                 if (status[j] == NSS_STATUS_SUCCESS)
188                   for (int i = 0; th[j].h_addr_list[i] != NULL; ++i)
189                     {
190                       ++naddrs;
191                       addrslen += th[j].h_length;
192                     }
193
194               if (canon == NULL)
195                 {
196                   /* Determine the canonical name.  */
197                   nss_getcanonname_r cfct;
198                   cfct = __nss_lookup_function (nip, "getcanonname_r");
199                   if (cfct != NULL)
200                     {
201                       const size_t max_fqdn_len = 256;
202                       char *buf = alloca (max_fqdn_len);
203                       char *s;
204                       int rc;
205
206                       if (DL_CALL_FCT (cfct, (key, buf, max_fqdn_len, &s, &rc,
207                                               &herrno)) == NSS_STATUS_SUCCESS)
208                         canon = s;
209                       else
210                         /* Set to name now to avoid using gethostbyaddr.  */
211                         canon = key;
212                     }
213                   else
214                     {
215                       struct hostent *he = NULL;
216                       int herrno;
217                       struct hostent he_mem;
218                       void *addr;
219                       size_t addrlen;
220                       int addrfamily;
221
222                       if (status[1] == NSS_STATUS_SUCCESS)
223                         {
224                           addr = th[1].h_addr_list[0];
225                           addrlen = sizeof (struct in_addr);
226                           addrfamily = AF_INET;
227                         }
228                       else
229                         {
230                           addr = th[0].h_addr_list[0];
231                           addrlen = sizeof (struct in6_addr);
232                           addrfamily = AF_INET6;
233                         }
234
235                       size_t tmpbuflen = 512;
236                       char *tmpbuf = alloca (tmpbuflen);
237                       int rc;
238                       while (1)
239                         {
240                           rc = __gethostbyaddr_r (addr, addrlen, addrfamily,
241                                                   &he_mem, tmpbuf, tmpbuflen,
242                                                   &he, &herrno);
243                           if (rc != ERANGE || herrno != NETDB_INTERNAL)
244                             break;
245                           tmpbuf = extend_alloca (tmpbuf, tmpbuflen,
246                                                   tmpbuflen * 2);
247                         }
248
249                       if (rc == 0)
250                         {
251                           if (he != NULL)
252                             canon = he->h_name;
253                           else
254                             canon = key;
255                         }
256                     }
257                 }
258               size_t canonlen = canon == NULL ? 0 : (strlen (canon) + 1);
259
260               total = sizeof (*dataset) + naddrs + addrslen + canonlen;
261
262               /* Now we can allocate the data structure.  */
263               if (he == NULL)
264                 {
265                   dataset = (struct dataset *) mempool_alloc (db,
266                                                               total
267                                                               + req->key_len);
268                   if (dataset == NULL)
269                     ++db->head->addfailed;
270                 }
271
272               if (dataset == NULL)
273                 {
274                   /* We cannot permanently add the result in the moment.  But
275                      we can provide the result as is.  Store the data in some
276                      temporary memory.  */
277                   dataset = (struct dataset *) alloca (total + req->key_len);
278
279                   /* We cannot add this record to the permanent database.  */
280                   alloca_used = true;
281                 }
282
283               dataset->head.allocsize = total + req->key_len;
284               dataset->head.recsize = total - offsetof (struct dataset, resp);
285               dataset->head.notfound = false;
286               dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
287               dataset->head.usable = true;
288
289               /* Compute the timeout time.  */
290               dataset->head.timeout = time (NULL) + MIN (db->postimeout, ttl);
291
292               dataset->resp.version = NSCD_VERSION;
293               dataset->resp.found = 1;
294               dataset->resp.naddrs = naddrs;
295               dataset->resp.addrslen = addrslen;
296               dataset->resp.canonlen = canonlen;
297               dataset->resp.error = NETDB_SUCCESS;
298
299               char *addrs = (char *) (&dataset->resp + 1);
300               uint8_t *family = (uint8_t *) (addrs + addrslen);
301
302               for (int j = 0; j < 2; ++j)
303                 if (status[j] == NSS_STATUS_SUCCESS)
304                   for (int i = 0; th[j].h_addr_list[i] != NULL; ++i)
305                     {
306                       addrs = mempcpy (addrs, th[j].h_addr_list[i],
307                                        th[j].h_length);
308                       *family++ = th[j].h_addrtype;
309                     }
310
311               char *cp = family;
312               if (canon != NULL)
313                 cp = mempcpy (cp, canon, canonlen);
314
315               key_copy = memcpy (cp, key, req->key_len);
316
317               /* Now we can determine whether on refill we have to
318                  create a new record or not.  */
319               if (he != NULL)
320                 {
321                   assert (fd == -1);
322
323                   if (total + req->key_len == dh->allocsize
324                       && total - offsetof (struct dataset, resp) == dh->recsize
325                       && memcmp (&dataset->resp, dh->data,
326                                  dh->allocsize
327                                  - offsetof (struct dataset, resp)) == 0)
328                     {
329                       /* The data has not changed.  We will just bump the
330                          timeout value.  Note that the new record has been
331                          allocated on the stack and need not be freed.  */
332                       dh->timeout = dataset->head.timeout;
333                       ++dh->nreloads;
334                     }
335                   else
336                     {
337                       /* We have to create a new record.  Just allocate
338                          appropriate memory and copy it.  */
339                       struct dataset *newp
340                         = (struct dataset *) mempool_alloc (db,
341                                                             total
342                                                             + req->key_len);
343                       if (newp != NULL)
344                         {
345                           /* Adjust pointer into the memory block.  */
346                           key_copy = (char *) newp + (key_copy
347                                                       - (char *) dataset);
348
349                           dataset = memcpy (newp, dataset,
350                                             total + req->key_len);
351                           alloca_used = false;
352                         }
353
354                       /* Mark the old record as obsolete.  */
355                       dh->usable = false;
356                     }
357                 }
358               else
359                 {
360                   /* We write the dataset before inserting it to the
361                      database since while inserting this thread might
362                      block and so would unnecessarily let the receiver
363                      wait.  */
364                   assert (fd != -1);
365
366                   TEMP_FAILURE_RETRY (write (fd, &dataset->resp, total));
367                 }
368
369               goto out;
370             }
371
372         }
373
374       if (nss_next_action (nip, status[1]) == NSS_ACTION_RETURN)
375         break;
376
377       if (nip->next == NULL)
378         no_more = -1;
379       else
380         nip = nip->next;
381     }
382
383   /* No result found.  Create a negative result record.  */
384   if (he != NULL && rc4 == EAGAIN)
385     {
386       /* If we have an old record available but cannot find one now
387          because the service is not available we keep the old record
388          and make sure it does not get removed.  */
389       if (reload_count != UINT_MAX && dh->nreloads == reload_count)
390         /* Do not reset the value if we never not reload the record.  */
391         dh->nreloads = reload_count - 1;
392     }
393   else
394     {
395       /* We have no data.  This means we send the standard reply for
396          this case.  */
397       total = sizeof (notfound);
398
399       if (fd != -1)
400         TEMP_FAILURE_RETRY (write (fd, &notfound, total));
401
402       dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len);
403       /* If we cannot permanently store the result, so be it.  */
404       if (dataset != NULL)
405         {
406           dataset->head.allocsize = sizeof (struct dataset) + req->key_len;
407           dataset->head.recsize = total;
408           dataset->head.notfound = true;
409           dataset->head.nreloads = 0;
410           dataset->head.usable = true;
411
412           /* Compute the timeout time.  */
413           dataset->head.timeout = time (NULL) + db->negtimeout;
414
415           /* This is the reply.  */
416           memcpy (&dataset->resp, &notfound, total);
417
418           /* Copy the key data.  */
419           key_copy = memcpy (dataset->strdata, key, req->key_len);
420         }
421       else
422         ++db->head->addfailed;
423    }
424
425  out:
426   _res.options = old_res_options;
427
428   if (db->secure)
429     pthread_seteuid_np (oldeuid);
430
431   if (dataset != NULL && !alloca_used)
432     {
433       /* If necessary, we also propagate the data to disk.  */
434       if (db->persistent)
435         {
436           // XXX async OK?
437           uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
438           msync ((void *) pval,
439                  ((uintptr_t) dataset & pagesize_m1) + total + req->key_len,
440                  MS_ASYNC);
441         }
442
443       /* Now get the lock to safely insert the records.  */
444       pthread_rwlock_rdlock (&db->lock);
445
446       if (cache_add (req->type, key_copy, req->key_len, &dataset->head, true,
447                      db, uid) < 0)
448         /* Ensure the data can be recovered.  */
449         dataset->head.usable = false;
450
451       pthread_rwlock_unlock (&db->lock);
452
453       /* Mark the old entry as obsolete.  */
454       if (dh != NULL)
455         dh->usable = false;
456     }
457 }
458
459
460 void
461 addhstai (struct database_dyn *db, int fd, request_header *req, void *key,
462           uid_t uid)
463 {
464   addhstaiX (db, fd, req, key, uid, NULL, NULL);
465 }
466
467
468 void
469 readdhstai (struct database_dyn *db, struct hashentry *he, struct datahead *dh)
470 {
471   request_header req =
472     {
473       .type = GETAI,
474       .key_len = he->len
475     };
476
477   addhstaiX (db, -1, &req, db->data + he->key, he->owner, he, dh);
478 }