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