Remove --enable-obsolete-nsl configure flag
[platform/upstream/glibc.git] / nscd / initgrcache.c
1 /* Cache handling for host lookup.
2    Copyright (C) 2004-2020 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 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 <https://www.gnu.org/licenses/>.  */
18
19 #include <assert.h>
20 #include <errno.h>
21 #include <grp.h>
22 #include <libintl.h>
23 #include <string.h>
24 #include <time.h>
25 #include <unistd.h>
26 #include <sys/mman.h>
27 #include <scratch_buffer.h>
28 #include <config.h>
29
30 #include "dbg_log.h"
31 #include "nscd.h"
32
33 #include "../nss/nsswitch.h"
34
35 /* Type of the lookup function.  */
36 typedef enum nss_status (*initgroups_dyn_function) (const char *, gid_t,
37                                                     long int *, long int *,
38                                                     gid_t **, long int, int *);
39
40
41 static const initgr_response_header notfound =
42 {
43   .version = NSCD_VERSION,
44   .found = 0,
45   .ngrps = 0
46 };
47
48
49 #include "../grp/compat-initgroups.c"
50
51
52 static time_t
53 addinitgroupsX (struct database_dyn *db, int fd, request_header *req,
54                 void *key, uid_t uid, struct hashentry *const he,
55                 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
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     initgr_response_header resp;
69     char strdata[0];
70   } *dataset = NULL;
71
72   if (__glibc_unlikely (debug_level > 0))
73     {
74       if (he == NULL)
75         dbg_log (_("Haven't found \"%s\" in group cache!"), (char *) key);
76       else
77         dbg_log (_("Reloading \"%s\" in group cache!"), (char *) key);
78     }
79
80   static service_user *group_database;
81   service_user *nip;
82   int no_more;
83
84   if (group_database == NULL)
85     no_more = __nss_database_lookup2 ("group", NULL, "files",
86                                       &group_database);
87   else
88     no_more = 0;
89   nip = group_database;
90
91  /* We always use sysconf even if NGROUPS_MAX is defined.  That way, the
92      limit can be raised in the kernel configuration without having to
93      recompile libc.  */
94   long int limit = __sysconf (_SC_NGROUPS_MAX);
95
96   long int size;
97   if (limit > 0)
98     /* We limit the size of the intially allocated array.  */
99     size = MIN (limit, 64);
100   else
101     /* No fixed limit on groups.  Pick a starting buffer size.  */
102     size = 16;
103
104   long int start = 0;
105   bool all_tryagain = true;
106   bool any_success = false;
107
108   /* This is temporary memory, we need not (and must not) call
109      mempool_alloc.  */
110   // XXX This really should use alloca.  need to change the backends.
111   gid_t *groups = (gid_t *) malloc (size * sizeof (gid_t));
112   if (__glibc_unlikely (groups == NULL))
113     /* No more memory.  */
114     goto out;
115
116   /* Nothing added yet.  */
117   while (! no_more)
118     {
119       long int prev_start = start;
120       enum nss_status status;
121       initgroups_dyn_function fct;
122       fct = __nss_lookup_function (nip, "initgroups_dyn");
123
124       if (fct == NULL)
125         {
126           status = compat_call (nip, key, -1, &start, &size, &groups,
127                                 limit, &errno);
128
129           if (nss_next_action (nip, NSS_STATUS_UNAVAIL) != NSS_ACTION_CONTINUE)
130             break;
131         }
132       else
133         status = DL_CALL_FCT (fct, (key, -1, &start, &size, &groups,
134                                     limit, &errno));
135
136       /* Remove duplicates.  */
137       long int cnt = prev_start;
138       while (cnt < start)
139         {
140           long int inner;
141           for (inner = 0; inner < prev_start; ++inner)
142             if (groups[inner] == groups[cnt])
143               break;
144
145           if (inner < prev_start)
146             groups[cnt] = groups[--start];
147           else
148             ++cnt;
149         }
150
151       if (status != NSS_STATUS_TRYAGAIN)
152         all_tryagain = false;
153
154       /* This is really only for debugging.  */
155       if (NSS_STATUS_TRYAGAIN > status || status > NSS_STATUS_RETURN)
156         __libc_fatal ("Illegal status in internal_getgrouplist.\n");
157
158       any_success |= status == NSS_STATUS_SUCCESS;
159
160       if (status != NSS_STATUS_SUCCESS
161           && nss_next_action (nip, status) == NSS_ACTION_RETURN)
162          break;
163
164       if (nip->next == NULL)
165         no_more = -1;
166       else
167         nip = nip->next;
168     }
169
170   bool all_written;
171   ssize_t total;
172   time_t timeout;
173  out:
174   all_written = true;
175   timeout = MAX_TIMEOUT_VALUE;
176   if (!any_success)
177     {
178       /* Nothing found.  Create a negative result record.  */
179       total = sizeof (notfound);
180
181       if (he != NULL && all_tryagain)
182         {
183           /* If we have an old record available but cannot find one now
184              because the service is not available we keep the old record
185              and make sure it does not get removed.  */
186           if (reload_count != UINT_MAX && dh->nreloads == reload_count)
187             /* Do not reset the value if we never not reload the record.  */
188             dh->nreloads = reload_count - 1;
189
190           /* Reload with the same time-to-live value.  */
191           timeout = dh->timeout = time (NULL) + db->postimeout;
192         }
193       else
194         {
195           /* We have no data.  This means we send the standard reply for this
196              case.  */
197           if (fd != -1
198               && TEMP_FAILURE_RETRY (send (fd, &notfound, total,
199                                            MSG_NOSIGNAL)) != total)
200             all_written = false;
201
202           /* If we have a transient error or cannot permanently store
203              the result, so be it.  */
204           if (all_tryagain || __builtin_expect (db->negtimeout == 0, 0))
205             {
206               /* Mark the old entry as obsolete.  */
207               if (dh != NULL)
208                 dh->usable = false;
209             }
210           else if ((dataset = mempool_alloc (db, (sizeof (struct dataset)
211                                                   + req->key_len), 1)) != NULL)
212             {
213               timeout = datahead_init_neg (&dataset->head,
214                                            (sizeof (struct dataset)
215                                             + req->key_len), total,
216                                            db->negtimeout);
217
218               /* This is the reply.  */
219               memcpy (&dataset->resp, &notfound, total);
220
221               /* Copy the key data.  */
222               char *key_copy = memcpy (dataset->strdata, key, req->key_len);
223
224               /* If necessary, we also propagate the data to disk.  */
225               if (db->persistent)
226                 {
227                   // XXX async OK?
228                   uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
229                   msync ((void *) pval,
230                          ((uintptr_t) dataset & pagesize_m1)
231                          + sizeof (struct dataset) + req->key_len, MS_ASYNC);
232                 }
233
234               (void) cache_add (req->type, key_copy, req->key_len,
235                                 &dataset->head, true, db, uid, he == NULL);
236
237               pthread_rwlock_unlock (&db->lock);
238
239               /* Mark the old entry as obsolete.  */
240               if (dh != NULL)
241                 dh->usable = false;
242             }
243         }
244     }
245   else
246     {
247
248       total = offsetof (struct dataset, strdata) + start * sizeof (int32_t);
249
250       /* If we refill the cache, first assume the reconrd did not
251          change.  Allocate memory on the cache since it is likely
252          discarded anyway.  If it turns out to be necessary to have a
253          new record we can still allocate real memory.  */
254       bool alloca_used = false;
255       dataset = NULL;
256
257       if (he == NULL)
258         dataset = (struct dataset *) mempool_alloc (db, total + req->key_len,
259                                                     1);
260
261       if (dataset == NULL)
262         {
263           /* We cannot permanently add the result in the moment.  But
264              we can provide the result as is.  Store the data in some
265              temporary memory.  */
266           dataset = (struct dataset *) alloca (total + req->key_len);
267
268           /* We cannot add this record to the permanent database.  */
269           alloca_used = true;
270         }
271
272       timeout = datahead_init_pos (&dataset->head, total + req->key_len,
273                                    total - offsetof (struct dataset, resp),
274                                    he == NULL ? 0 : dh->nreloads + 1,
275                                    db->postimeout);
276
277       dataset->resp.version = NSCD_VERSION;
278       dataset->resp.found = 1;
279       dataset->resp.ngrps = start;
280
281       char *cp = dataset->strdata;
282
283       /* Copy the GID values.  If the size of the types match this is
284          very simple.  */
285       if (sizeof (gid_t) == sizeof (int32_t))
286         cp = mempcpy (cp, groups, start * sizeof (gid_t));
287       else
288         {
289           gid_t *gcp = (gid_t *) cp;
290
291           for (int i = 0; i < start; ++i)
292             *gcp++ = groups[i];
293
294           cp = (char *) gcp;
295         }
296
297       /* Finally the user name.  */
298       memcpy (cp, key, req->key_len);
299
300       assert (cp == dataset->strdata + total - offsetof (struct dataset,
301                                                          strdata));
302
303       /* Now we can determine whether on refill we have to create a new
304          record or not.  */
305       if (he != NULL)
306         {
307           assert (fd == -1);
308
309           if (total + req->key_len == dh->allocsize
310               && total - offsetof (struct dataset, resp) == dh->recsize
311               && memcmp (&dataset->resp, dh->data,
312                          dh->allocsize - offsetof (struct dataset, resp)) == 0)
313             {
314               /* The data has not changed.  We will just bump the
315                  timeout value.  Note that the new record has been
316                  allocated on the stack and need not be freed.  */
317               dh->timeout = dataset->head.timeout;
318               ++dh->nreloads;
319             }
320           else
321             {
322               /* We have to create a new record.  Just allocate
323                  appropriate memory and copy it.  */
324               struct dataset *newp
325                 = (struct dataset *) mempool_alloc (db, total + req->key_len,
326                                                     1);
327               if (newp != NULL)
328                 {
329                   /* Adjust pointer into the memory block.  */
330                   cp = (char *) newp + (cp - (char *) dataset);
331
332                   dataset = memcpy (newp, dataset, total + req->key_len);
333                   alloca_used = false;
334                 }
335
336               /* Mark the old record as obsolete.  */
337               dh->usable = false;
338             }
339         }
340       else
341         {
342           /* We write the dataset before inserting it to the database
343              since while inserting this thread might block and so would
344              unnecessarily let the receiver wait.  */
345           assert (fd != -1);
346
347           if (writeall (fd, &dataset->resp, dataset->head.recsize)
348               != dataset->head.recsize)
349             all_written = false;
350         }
351
352
353       /* Add the record to the database.  But only if it has not been
354          stored on the stack.  */
355       if (! alloca_used)
356         {
357           /* If necessary, we also propagate the data to disk.  */
358           if (db->persistent)
359             {
360               // XXX async OK?
361               uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
362               msync ((void *) pval,
363                      ((uintptr_t) dataset & pagesize_m1) + total
364                      + req->key_len, MS_ASYNC);
365             }
366
367           (void) cache_add (INITGROUPS, cp, req->key_len, &dataset->head, true,
368                             db, uid, he == NULL);
369
370           pthread_rwlock_unlock (&db->lock);
371         }
372     }
373
374   free (groups);
375
376   if (__builtin_expect (!all_written, 0) && debug_level > 0)
377     {
378       char buf[256];
379       dbg_log (_("short write in %s: %s"), __FUNCTION__,
380                strerror_r (errno, buf, sizeof (buf)));
381     }
382
383   return timeout;
384 }
385
386
387 void
388 addinitgroups (struct database_dyn *db, int fd, request_header *req, void *key,
389                uid_t uid)
390 {
391   addinitgroupsX (db, fd, req, key, uid, NULL, NULL);
392 }
393
394
395 time_t
396 readdinitgroups (struct database_dyn *db, struct hashentry *he,
397                  struct datahead *dh)
398 {
399   request_header req =
400     {
401       .type = INITGROUPS,
402       .key_len = he->len
403     };
404
405   return addinitgroupsX (db, -1, &req, db->data + he->key, he->owner, he, dh);
406 }