Use NSS_STATUS_TRYAGAIN to indicate insufficient buffer (BZ #16878)
[platform/upstream/linaro-glibc.git] / nscd / netgroupcache.c
1 /* Cache handling for netgroup lookup.
2    Copyright (C) 2011-2014 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4    Contributed by Ulrich Drepper <drepper@gmail.com>, 2011.
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 as published
8    by the Free Software Foundation; version 2 of the License, or
9    (at your option) any later version.
10
11    This program 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
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, see <http://www.gnu.org/licenses/>.  */
18
19 #include <alloca.h>
20 #include <assert.h>
21 #include <errno.h>
22 #include <libintl.h>
23 #include <stdbool.h>
24 #include <unistd.h>
25 #include <sys/mman.h>
26
27 #include "../inet/netgroup.h"
28 #include "nscd.h"
29 #include "dbg_log.h"
30
31 #include <kernel-features.h>
32
33
34 /* This is the standard reply in case the service is disabled.  */
35 static const netgroup_response_header disabled =
36 {
37   .version = NSCD_VERSION,
38   .found = -1,
39   .nresults = 0,
40   .result_len = 0
41 };
42
43 /* This is the struct describing how to write this record.  */
44 const struct iovec netgroup_iov_disabled =
45 {
46   .iov_base = (void *) &disabled,
47   .iov_len = sizeof (disabled)
48 };
49
50
51 /* This is the standard reply in case we haven't found the dataset.  */
52 static const netgroup_response_header notfound =
53 {
54   .version = NSCD_VERSION,
55   .found = 0,
56   .nresults = 0,
57   .result_len = 0
58 };
59
60
61 struct dataset
62 {
63   struct datahead head;
64   netgroup_response_header resp;
65   char strdata[0];
66 };
67
68 /* Sends a notfound message and prepares a notfound dataset to write to the
69    cache.  Returns true if there was enough memory to allocate the dataset and
70    returns the dataset in DATASETP, total bytes to write in TOTALP and the
71    timeout in TIMEOUTP.  KEY_COPY is set to point to the copy of the key in the
72    dataset. */
73 static bool
74 do_notfound (struct database_dyn *db, int fd, request_header *req,
75                const char *key, struct dataset **datasetp, ssize_t *totalp,
76                time_t *timeoutp, char **key_copy)
77 {
78   struct dataset *dataset;
79   ssize_t total;
80   time_t timeout;
81   bool cacheable = false;
82
83   total = sizeof (notfound);
84   timeout = time (NULL) + db->negtimeout;
85
86   if (fd != -1)
87     TEMP_FAILURE_RETRY (send (fd, &notfound, total, MSG_NOSIGNAL));
88
89   dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len, 1);
90   /* If we cannot permanently store the result, so be it.  */
91   if (dataset != NULL)
92     {
93       timeout = datahead_init_neg (&dataset->head,
94                                    sizeof (struct dataset) + req->key_len,
95                                    total, db->negtimeout);
96
97       /* This is the reply.  */
98       memcpy (&dataset->resp, &notfound, total);
99
100       /* Copy the key data.  */
101       memcpy (dataset->strdata, key, req->key_len);
102       *key_copy = dataset->strdata;
103
104       cacheable = true;
105     }
106   *timeoutp = timeout;
107   *totalp = total;
108   *datasetp = dataset;
109   return cacheable;
110 }
111
112 static time_t
113 addgetnetgrentX (struct database_dyn *db, int fd, request_header *req,
114                  const char *key, uid_t uid, struct hashentry *he,
115                  struct datahead *dh, struct dataset **resultp)
116 {
117   if (__glibc_unlikely (debug_level > 0))
118     {
119       if (he == NULL)
120         dbg_log (_("Haven't found \"%s\" in netgroup cache!"), key);
121       else
122         dbg_log (_("Reloading \"%s\" in netgroup cache!"), key);
123     }
124
125   static service_user *netgroup_database;
126   time_t timeout;
127   struct dataset *dataset;
128   bool cacheable = false;
129   ssize_t total;
130   bool found = false;
131
132   char *key_copy = NULL;
133   struct __netgrent data;
134   size_t buflen = MAX (1024, sizeof (*dataset) + req->key_len);
135   size_t buffilled = sizeof (*dataset);
136   char *buffer = NULL;
137   size_t nentries = 0;
138   size_t group_len = strlen (key) + 1;
139   union
140   {
141     struct name_list elem;
142     char mem[sizeof (struct name_list) + group_len];
143   } first_needed;
144
145   if (netgroup_database == NULL
146       && __nss_database_lookup ("netgroup", NULL, NULL, &netgroup_database))
147     {
148       /* No such service.  */
149       cacheable = do_notfound (db, fd, req, key, &dataset, &total, &timeout,
150                                &key_copy);
151       goto writeout;
152     }
153
154   memset (&data, '\0', sizeof (data));
155   buffer = xmalloc (buflen);
156   first_needed.elem.next = &first_needed.elem;
157   memcpy (first_needed.elem.name, key, group_len);
158   data.needed_groups = &first_needed.elem;
159
160   while (data.needed_groups != NULL)
161     {
162       /* Add the next group to the list of those which are known.  */
163       struct name_list *this_group = data.needed_groups->next;
164       if (this_group == data.needed_groups)
165         data.needed_groups = NULL;
166       else
167         data.needed_groups->next = this_group->next;
168       this_group->next = data.known_groups;
169       data.known_groups = this_group;
170
171       union
172       {
173         enum nss_status (*f) (const char *, struct __netgrent *);
174         void *ptr;
175       } setfct;
176
177       service_user *nip = netgroup_database;
178       int no_more = __nss_lookup (&nip, "setnetgrent", NULL, &setfct.ptr);
179       while (!no_more)
180         {
181           enum nss_status status
182             = DL_CALL_FCT (*setfct.f, (data.known_groups->name, &data));
183
184           if (status == NSS_STATUS_SUCCESS)
185             {
186               found = true;
187               union
188               {
189                 enum nss_status (*f) (struct __netgrent *, char *, size_t,
190                                       int *);
191                 void *ptr;
192               } getfct;
193               getfct.ptr = __nss_lookup_function (nip, "getnetgrent_r");
194               if (getfct.f != NULL)
195                 while (1)
196                   {
197                     int e;
198                     status = getfct.f (&data, buffer + buffilled,
199                                        buflen - buffilled - req->key_len, &e);
200                     if (status == NSS_STATUS_SUCCESS)
201                       {
202                         if (data.type == triple_val)
203                           {
204                             const char *nhost = data.val.triple.host;
205                             const char *nuser = data.val.triple.user;
206                             const char *ndomain = data.val.triple.domain;
207
208                             size_t hostlen = strlen (nhost ?: "") + 1;
209                             size_t userlen = strlen (nuser ?: "") + 1;
210                             size_t domainlen = strlen (ndomain ?: "") + 1;
211
212                             if (nhost == NULL || nuser == NULL || ndomain == NULL
213                                 || nhost > nuser || nuser > ndomain)
214                               {
215                                 const char *last = nhost;
216                                 if (last == NULL
217                                     || (nuser != NULL && nuser > last))
218                                   last = nuser;
219                                 if (last == NULL
220                                     || (ndomain != NULL && ndomain > last))
221                                   last = ndomain;
222
223                                 size_t bufused
224                                   = (last == NULL
225                                      ? buffilled
226                                      : last + strlen (last) + 1 - buffer);
227
228                                 /* We have to make temporary copies.  */
229                                 size_t needed = hostlen + userlen + domainlen;
230
231                                 if (buflen - req->key_len - bufused < needed)
232                                   {
233                                     buflen += MAX (buflen, 2 * needed);
234                                     /* Save offset in the old buffer.  We don't
235                                        bother with the NULL check here since
236                                        we'll do that later anyway.  */
237                                     size_t nhostdiff = nhost - buffer;
238                                     size_t nuserdiff = nuser - buffer;
239                                     size_t ndomaindiff = ndomain - buffer;
240
241                                     char *newbuf = xrealloc (buffer, buflen);
242                                     /* Fix up the triplet pointers into the new
243                                        buffer.  */
244                                     nhost = (nhost ? newbuf + nhostdiff
245                                              : NULL);
246                                     nuser = (nuser ? newbuf + nuserdiff
247                                              : NULL);
248                                     ndomain = (ndomain ? newbuf + ndomaindiff
249                                                : NULL);
250                                     buffer = newbuf;
251                                   }
252
253                                 nhost = memcpy (buffer + bufused,
254                                                 nhost ?: "", hostlen);
255                                 nuser = memcpy ((char *) nhost + hostlen,
256                                                 nuser ?: "", userlen);
257                                 ndomain = memcpy ((char *) nuser + userlen,
258                                                   ndomain ?: "", domainlen);
259                               }
260
261                             char *wp = buffer + buffilled;
262                             wp = memmove (wp, nhost ?: "", hostlen);
263                             wp += hostlen;
264                             wp = memmove (wp, nuser ?: "", userlen);
265                             wp += userlen;
266                             wp = memmove (wp, ndomain ?: "", domainlen);
267                             wp += domainlen;
268                             buffilled = wp - buffer;
269                             ++nentries;
270                           }
271                         else
272                           {
273                             /* Check that the group has not been
274                                requested before.  */
275                             struct name_list *runp = data.needed_groups;
276                             if (runp != NULL)
277                               while (1)
278                                 {
279                                   if (strcmp (runp->name, data.val.group) == 0)
280                                     break;
281
282                                   runp = runp->next;
283                                   if (runp == data.needed_groups)
284                                     {
285                                       runp = NULL;
286                                       break;
287                                     }
288                                 }
289
290                             if (runp == NULL)
291                               {
292                                 runp = data.known_groups;
293                                 while (runp != NULL)
294                                   if (strcmp (runp->name, data.val.group) == 0)
295                                     break;
296                                   else
297                                     runp = runp->next;
298                                 }
299
300                             if (runp == NULL)
301                               {
302                                 /* A new group is requested.  */
303                                 size_t namelen = strlen (data.val.group) + 1;
304                                 struct name_list *newg = alloca (sizeof (*newg)
305                                                                  + namelen);
306                                 memcpy (newg->name, data.val.group, namelen);
307                                 if (data.needed_groups == NULL)
308                                   data.needed_groups = newg->next = newg;
309                                 else
310                                   {
311                                     newg->next = data.needed_groups->next;
312                                     data.needed_groups->next = newg;
313                                     data.needed_groups = newg;
314                                   }
315                               }
316                           }
317                       }
318                     else if (status == NSS_STATUS_TRYAGAIN && e == ERANGE)
319                       {
320                         buflen *= 2;
321                         buffer = xrealloc (buffer, buflen);
322                       }
323                     else if (status == NSS_STATUS_RETURN
324                              || status == NSS_STATUS_NOTFOUND
325                              || status == NSS_STATUS_UNAVAIL)
326                       /* This was either the last one for this group or the
327                          group was empty or the NSS module had an internal
328                          failure.  Look at next group if available.  */
329                       break;
330                   }
331
332               enum nss_status (*endfct) (struct __netgrent *);
333               endfct = __nss_lookup_function (nip, "endnetgrent");
334               if (endfct != NULL)
335                 (void) DL_CALL_FCT (*endfct, (&data));
336
337               break;
338             }
339
340           no_more = __nss_next2 (&nip, "setnetgrent", NULL, &setfct.ptr,
341                                  status, 0);
342         }
343     }
344
345   /* No results.  Return a failure and write out a notfound record in the
346      cache.  */
347   if (!found)
348     {
349       cacheable = do_notfound (db, fd, req, key, &dataset, &total, &timeout,
350                                &key_copy);
351       goto writeout;
352     }
353
354   total = buffilled;
355
356   /* Fill in the dataset.  */
357   dataset = (struct dataset *) buffer;
358   timeout = datahead_init_pos (&dataset->head, total + req->key_len,
359                                total - offsetof (struct dataset, resp),
360                                he == NULL ? 0 : dh->nreloads + 1,
361                                db->postimeout);
362
363   dataset->resp.version = NSCD_VERSION;
364   dataset->resp.found = 1;
365   dataset->resp.nresults = nentries;
366   dataset->resp.result_len = buffilled - sizeof (*dataset);
367
368   assert (buflen - buffilled >= req->key_len);
369   key_copy = memcpy (buffer + buffilled, key, req->key_len);
370   buffilled += req->key_len;
371
372   /* Now we can determine whether on refill we have to create a new
373      record or not.  */
374   if (he != NULL)
375     {
376       assert (fd == -1);
377
378       if (dataset->head.allocsize == dh->allocsize
379           && dataset->head.recsize == dh->recsize
380           && memcmp (&dataset->resp, dh->data,
381                      dh->allocsize - offsetof (struct dataset, resp)) == 0)
382         {
383           /* The data has not changed.  We will just bump the timeout
384              value.  Note that the new record has been allocated on
385              the stack and need not be freed.  */
386           dh->timeout = dataset->head.timeout;
387           dh->ttl = dataset->head.ttl;
388           ++dh->nreloads;
389           dataset = (struct dataset *) dh;
390
391           goto out;
392         }
393     }
394
395   {
396     struct dataset *newp
397       = (struct dataset *) mempool_alloc (db, total + req->key_len, 1);
398     if (__glibc_likely (newp != NULL))
399       {
400         /* Adjust pointer into the memory block.  */
401         key_copy = (char *) newp + (key_copy - buffer);
402
403         dataset = memcpy (newp, dataset, total + req->key_len);
404         cacheable = true;
405
406         if (he != NULL)
407           /* Mark the old record as obsolete.  */
408           dh->usable = false;
409       }
410   }
411
412   if (he == NULL && fd != -1)
413     {
414       /* We write the dataset before inserting it to the database
415          since while inserting this thread might block and so would
416          unnecessarily let the receiver wait.  */
417     writeout:
418 #ifdef HAVE_SENDFILE
419       if (__builtin_expect (db->mmap_used, 1) && cacheable)
420         {
421           assert (db->wr_fd != -1);
422           assert ((char *) &dataset->resp > (char *) db->data);
423           assert ((char *) dataset - (char *) db->head + total
424                   <= (sizeof (struct database_pers_head)
425                       + db->head->module * sizeof (ref_t)
426                       + db->head->data_size));
427 # ifndef __ASSUME_SENDFILE
428           ssize_t written =
429 # endif
430             sendfileall (fd, db->wr_fd, (char *) &dataset->resp
431                          - (char *) db->head, dataset->head.recsize);
432 # ifndef __ASSUME_SENDFILE
433           if (written == -1 && errno == ENOSYS)
434             goto use_write;
435 # endif
436         }
437       else
438 #endif
439         {
440 #if defined HAVE_SENDFILE && !defined __ASSUME_SENDFILE
441         use_write:
442 #endif
443           writeall (fd, &dataset->resp, dataset->head.recsize);
444         }
445     }
446
447   if (cacheable)
448     {
449       /* If necessary, we also propagate the data to disk.  */
450       if (db->persistent)
451         {
452           // XXX async OK?
453           uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
454           msync ((void *) pval,
455                  ((uintptr_t) dataset & pagesize_m1) + total + req->key_len,
456                  MS_ASYNC);
457         }
458
459       (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
460                         true, db, uid, he == NULL);
461
462       pthread_rwlock_unlock (&db->lock);
463
464       /* Mark the old entry as obsolete.  */
465       if (dh != NULL)
466         dh->usable = false;
467     }
468
469  out:
470   free (buffer);
471
472   *resultp = dataset;
473
474   return timeout;
475 }
476
477
478 static time_t
479 addinnetgrX (struct database_dyn *db, int fd, request_header *req,
480              char *key, uid_t uid, struct hashentry *he,
481              struct datahead *dh)
482 {
483   const char *group = key;
484   key = (char *) rawmemchr (key, '\0') + 1;
485   size_t group_len = key - group - 1;
486   const char *host = *key++ ? key : NULL;
487   if (host != NULL)
488     key = (char *) rawmemchr (key, '\0') + 1;
489   const char *user = *key++ ? key : NULL;
490   if (user != NULL)
491     key = (char *) rawmemchr (key, '\0') + 1;
492   const char *domain = *key++ ? key : NULL;
493
494   if (__glibc_unlikely (debug_level > 0))
495     {
496       if (he == NULL)
497         dbg_log (_("Haven't found \"%s (%s,%s,%s)\" in netgroup cache!"),
498                  group, host ?: "", user ?: "", domain ?: "");
499       else
500         dbg_log (_("Reloading \"%s (%s,%s,%s)\" in netgroup cache!"),
501                  group, host ?: "", user ?: "", domain ?: "");
502     }
503
504   struct dataset *result = (struct dataset *) cache_search (GETNETGRENT,
505                                                             group, group_len,
506                                                             db, uid);
507   time_t timeout;
508   if (result != NULL)
509     timeout = result->head.timeout;
510   else
511     {
512       request_header req_get =
513         {
514           .type = GETNETGRENT,
515           .key_len = group_len
516         };
517       timeout = addgetnetgrentX (db, -1, &req_get, group, uid, NULL, NULL,
518                                  &result);
519     }
520
521   struct indataset
522   {
523     struct datahead head;
524     innetgroup_response_header resp;
525   } *dataset
526       = (struct indataset *) mempool_alloc (db,
527                                             sizeof (*dataset) + req->key_len,
528                                             1);
529   struct indataset dataset_mem;
530   bool cacheable = true;
531   if (__glibc_unlikely (dataset == NULL))
532     {
533       cacheable = false;
534       dataset = &dataset_mem;
535     }
536
537   datahead_init_pos (&dataset->head, sizeof (*dataset) + req->key_len,
538                      sizeof (innetgroup_response_header),
539                      he == NULL ? 0 : dh->nreloads + 1, result->head.ttl);
540   /* Set the notfound status and timeout based on the result from
541      getnetgrent.  */
542   dataset->head.notfound = result->head.notfound;
543   dataset->head.timeout = timeout;
544
545   dataset->resp.version = NSCD_VERSION;
546   dataset->resp.found = result->resp.found;
547   /* Until we find a matching entry the result is 0.  */
548   dataset->resp.result = 0;
549
550   char *key_copy = memcpy ((char *) (dataset + 1), group, req->key_len);
551
552   if (dataset->resp.found)
553     {
554       const char *triplets = (const char *) (&result->resp + 1);
555
556       for (nscd_ssize_t i = result->resp.nresults; i > 0; --i)
557         {
558           bool success = true;
559
560           /* For the host, user and domain in each triplet, we assume success
561              if the value is blank because that is how the wildcard entry to
562              match anything is stored in the netgroup cache.  */
563           if (host != NULL && *triplets != '\0')
564             success = strcmp (host, triplets) == 0;
565           triplets = (const char *) rawmemchr (triplets, '\0') + 1;
566
567           if (success && user != NULL && *triplets != '\0')
568             success = strcmp (user, triplets) == 0;
569           triplets = (const char *) rawmemchr (triplets, '\0') + 1;
570
571           if (success && (domain == NULL || *triplets == '\0'
572                           || strcmp (domain, triplets) == 0))
573             {
574               dataset->resp.result = 1;
575               break;
576             }
577           triplets = (const char *) rawmemchr (triplets, '\0') + 1;
578         }
579     }
580
581   if (he != NULL && dh->data[0].innetgroupdata.result == dataset->resp.result)
582     {
583       /* The data has not changed.  We will just bump the timeout
584          value.  Note that the new record has been allocated on
585          the stack and need not be freed.  */
586       dh->timeout = timeout;
587       dh->ttl = dataset->head.ttl;
588       ++dh->nreloads;
589       return timeout;
590     }
591
592   if (he == NULL)
593     {
594       /* We write the dataset before inserting it to the database
595          since while inserting this thread might block and so would
596          unnecessarily let the receiver wait.  */
597        assert (fd != -1);
598
599 #ifdef HAVE_SENDFILE
600       if (__builtin_expect (db->mmap_used, 1) && cacheable)
601         {
602           assert (db->wr_fd != -1);
603           assert ((char *) &dataset->resp > (char *) db->data);
604           assert ((char *) dataset - (char *) db->head + sizeof (*dataset)
605                   <= (sizeof (struct database_pers_head)
606                       + db->head->module * sizeof (ref_t)
607                       + db->head->data_size));
608 # ifndef __ASSUME_SENDFILE
609           ssize_t written =
610 # endif
611             sendfileall (fd, db->wr_fd,
612                          (char *) &dataset->resp - (char *) db->head,
613                          sizeof (innetgroup_response_header));
614 # ifndef __ASSUME_SENDFILE
615           if (written == -1 && errno == ENOSYS)
616             goto use_write;
617 # endif
618         }
619       else
620 #endif
621         {
622 #if defined HAVE_SENDFILE && !defined __ASSUME_SENDFILE
623         use_write:
624 #endif
625           writeall (fd, &dataset->resp, sizeof (innetgroup_response_header));
626         }
627     }
628
629   if (cacheable)
630     {
631       /* If necessary, we also propagate the data to disk.  */
632       if (db->persistent)
633         {
634           // XXX async OK?
635           uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
636           msync ((void *) pval,
637                  ((uintptr_t) dataset & pagesize_m1) + sizeof (*dataset)
638                  + req->key_len,
639                  MS_ASYNC);
640         }
641
642       (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
643                         true, db, uid, he == NULL);
644
645       pthread_rwlock_unlock (&db->lock);
646
647       /* Mark the old entry as obsolete.  */
648       if (dh != NULL)
649         dh->usable = false;
650     }
651
652   return timeout;
653 }
654
655
656 void
657 addgetnetgrent (struct database_dyn *db, int fd, request_header *req,
658                 void *key, uid_t uid)
659 {
660   struct dataset *ignore;
661
662   addgetnetgrentX (db, fd, req, key, uid, NULL, NULL, &ignore);
663 }
664
665
666 time_t
667 readdgetnetgrent (struct database_dyn *db, struct hashentry *he,
668                   struct datahead *dh)
669 {
670   request_header req =
671     {
672       .type = GETNETGRENT,
673       .key_len = he->len
674     };
675   struct dataset *ignore;
676
677   return addgetnetgrentX (db, -1, &req, db->data + he->key, he->owner, he, dh,
678                           &ignore);
679 }
680
681
682 void
683 addinnetgr (struct database_dyn *db, int fd, request_header *req,
684             void *key, uid_t uid)
685 {
686   addinnetgrX (db, fd, req, key, uid, NULL, NULL);
687 }
688
689
690 time_t
691 readdinnetgr (struct database_dyn *db, struct hashentry *he,
692               struct datahead *dh)
693 {
694   request_header req =
695     {
696       .type = INNETGR,
697       .key_len = he->len
698     };
699
700   return addinnetgrX (db, -1, &req, db->data + he->key, he->owner, he, dh);
701 }