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