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.
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.
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.
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/>. */
27 #include "../inet/netgroup.h"
31 #include <kernel-features.h>
34 /* This is the standard reply in case the service is disabled. */
35 static const netgroup_response_header disabled
=
37 .version
= NSCD_VERSION
,
43 /* This is the struct describing how to write this record. */
44 const struct iovec netgroup_iov_disabled
=
46 .iov_base
= (void *) &disabled
,
47 .iov_len
= sizeof (disabled
)
51 /* This is the standard reply in case we haven't found the dataset. */
52 static const netgroup_response_header notfound
=
54 .version
= NSCD_VERSION
,
64 netgroup_response_header resp
;
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
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
)
78 struct dataset
*dataset
;
81 bool cacheable
= false;
83 total
= sizeof (notfound
);
84 timeout
= time (NULL
) + db
->negtimeout
;
87 TEMP_FAILURE_RETRY (send (fd
, ¬found
, total
, MSG_NOSIGNAL
));
89 dataset
= mempool_alloc (db
, sizeof (struct dataset
) + req
->key_len
, 1);
90 /* If we cannot permanently store the result, so be it. */
93 dataset
->head
.allocsize
= sizeof (struct dataset
) + req
->key_len
;
94 dataset
->head
.recsize
= total
;
95 dataset
->head
.notfound
= true;
96 dataset
->head
.nreloads
= 0;
97 dataset
->head
.usable
= true;
99 /* Compute the timeout time. */
100 timeout
= dataset
->head
.timeout
= time (NULL
) + db
->negtimeout
;
101 dataset
->head
.ttl
= db
->negtimeout
;
103 /* This is the reply. */
104 memcpy (&dataset
->resp
, ¬found
, total
);
106 /* Copy the key data. */
107 memcpy (dataset
->strdata
, key
, req
->key_len
);
108 *key_copy
= dataset
->strdata
;
119 addgetnetgrentX (struct database_dyn
*db
, int fd
, request_header
*req
,
120 const char *key
, uid_t uid
, struct hashentry
*he
,
121 struct datahead
*dh
, struct dataset
**resultp
)
123 if (__builtin_expect (debug_level
> 0, 0))
126 dbg_log (_("Haven't found \"%s\" in netgroup cache!"), key
);
128 dbg_log (_("Reloading \"%s\" in netgroup cache!"), key
);
131 static service_user
*netgroup_database
;
133 struct dataset
*dataset
;
134 bool cacheable
= false;
138 char *key_copy
= NULL
;
139 struct __netgrent data
;
140 size_t buflen
= MAX (1024, sizeof (*dataset
) + req
->key_len
);
141 size_t buffilled
= sizeof (*dataset
);
144 bool use_malloc
= false;
145 size_t group_len
= strlen (key
) + 1;
148 struct name_list elem
;
149 char mem
[sizeof (struct name_list
) + group_len
];
152 if (netgroup_database
== NULL
153 && __nss_database_lookup ("netgroup", NULL
, NULL
, &netgroup_database
))
155 /* No such service. */
156 cacheable
= do_notfound (db
, fd
, req
, key
, &dataset
, &total
, &timeout
,
161 memset (&data
, '\0', sizeof (data
));
162 buffer
= alloca (buflen
);
163 first_needed
.elem
.next
= &first_needed
.elem
;
164 memcpy (first_needed
.elem
.name
, key
, group_len
);
165 data
.needed_groups
= &first_needed
.elem
;
167 while (data
.needed_groups
!= NULL
)
169 /* Add the next group to the list of those which are known. */
170 struct name_list
*this_group
= data
.needed_groups
->next
;
171 if (this_group
== data
.needed_groups
)
172 data
.needed_groups
= NULL
;
174 data
.needed_groups
->next
= this_group
->next
;
175 this_group
->next
= data
.known_groups
;
176 data
.known_groups
= this_group
;
180 enum nss_status (*f
) (const char *, struct __netgrent
*);
184 service_user
*nip
= netgroup_database
;
185 int no_more
= __nss_lookup (&nip
, "setnetgrent", NULL
, &setfct
.ptr
);
188 enum nss_status status
189 = DL_CALL_FCT (*setfct
.f
, (data
.known_groups
->name
, &data
));
191 if (status
== NSS_STATUS_SUCCESS
)
196 enum nss_status (*f
) (struct __netgrent
*, char *, size_t,
200 getfct
.ptr
= __nss_lookup_function (nip
, "getnetgrent_r");
201 if (getfct
.f
!= NULL
)
205 status
= getfct
.f (&data
, buffer
+ buffilled
,
206 buflen
- buffilled
, &e
);
207 if (status
== NSS_STATUS_RETURN
208 || status
== NSS_STATUS_NOTFOUND
)
209 /* This was either the last one for this group or the
210 group was empty. Look at next group if available. */
212 if (status
== NSS_STATUS_SUCCESS
)
214 if (data
.type
== triple_val
)
216 const char *nhost
= data
.val
.triple
.host
;
217 const char *nuser
= data
.val
.triple
.user
;
218 const char *ndomain
= data
.val
.triple
.domain
;
220 if (nhost
== NULL
|| nuser
== NULL
|| ndomain
== NULL
221 || nhost
> nuser
|| nuser
> ndomain
)
223 const char *last
= nhost
;
225 || (nuser
!= NULL
&& nuser
> last
))
228 || (ndomain
!= NULL
&& ndomain
> last
))
234 : last
+ strlen (last
) + 1 - buffer
);
236 /* We have to make temporary copies. */
237 size_t hostlen
= strlen (nhost
?: "") + 1;
238 size_t userlen
= strlen (nuser
?: "") + 1;
239 size_t domainlen
= strlen (ndomain
?: "") + 1;
240 size_t needed
= hostlen
+ userlen
+ domainlen
;
242 if (buflen
- req
->key_len
- bufused
< needed
)
244 size_t newsize
= MAX (2 * buflen
,
245 buflen
+ 2 * needed
);
246 if (use_malloc
|| newsize
> 1024 * 1024)
249 char *newbuf
= xrealloc (use_malloc
258 extend_alloca (buffer
, buflen
, newsize
);
261 nhost
= memcpy (buffer
+ bufused
,
262 nhost
?: "", hostlen
);
263 nuser
= memcpy ((char *) nhost
+ hostlen
,
264 nuser
?: "", userlen
);
265 ndomain
= memcpy ((char *) nuser
+ userlen
,
266 ndomain
?: "", domainlen
);
269 char *wp
= buffer
+ buffilled
;
270 wp
= stpcpy (wp
, nhost
) + 1;
271 wp
= stpcpy (wp
, nuser
) + 1;
272 wp
= stpcpy (wp
, ndomain
) + 1;
273 buffilled
= wp
- buffer
;
278 /* Check that the group has not been
280 struct name_list
*runp
= data
.needed_groups
;
284 if (strcmp (runp
->name
, data
.val
.group
) == 0)
288 if (runp
== data
.needed_groups
)
297 runp
= data
.known_groups
;
299 if (strcmp (runp
->name
, data
.val
.group
) == 0)
307 /* A new group is requested. */
308 size_t namelen
= strlen (data
.val
.group
) + 1;
309 struct name_list
*newg
= alloca (sizeof (*newg
)
311 memcpy (newg
->name
, data
.val
.group
, namelen
);
312 if (data
.needed_groups
== NULL
)
313 data
.needed_groups
= newg
->next
= newg
;
316 newg
->next
= data
.needed_groups
->next
;
317 data
.needed_groups
->next
= newg
;
318 data
.needed_groups
= newg
;
323 else if (status
== NSS_STATUS_UNAVAIL
&& e
== ERANGE
)
325 size_t newsize
= 2 * buflen
;
326 if (use_malloc
|| newsize
> 1024 * 1024)
329 char *newbuf
= xrealloc (use_malloc
330 ? buffer
: NULL
, buflen
);
336 extend_alloca (buffer
, buflen
, newsize
);
340 enum nss_status (*endfct
) (struct __netgrent
*);
341 endfct
= __nss_lookup_function (nip
, "endnetgrent");
343 (void) DL_CALL_FCT (*endfct
, (&data
));
348 no_more
= __nss_next2 (&nip
, "setnetgrent", NULL
, &setfct
.ptr
,
353 /* No results. Return a failure and write out a notfound record in the
357 cacheable
= do_notfound (db
, fd
, req
, key
, &dataset
, &total
, &timeout
,
364 /* Fill in the dataset. */
365 dataset
= (struct dataset
*) buffer
;
366 dataset
->head
.allocsize
= total
+ req
->key_len
;
367 dataset
->head
.recsize
= total
- offsetof (struct dataset
, resp
);
368 dataset
->head
.notfound
= false;
369 dataset
->head
.nreloads
= he
== NULL
? 0 : (dh
->nreloads
+ 1);
370 dataset
->head
.usable
= true;
371 dataset
->head
.ttl
= db
->postimeout
;
372 timeout
= dataset
->head
.timeout
= time (NULL
) + dataset
->head
.ttl
;
374 dataset
->resp
.version
= NSCD_VERSION
;
375 dataset
->resp
.found
= 1;
376 dataset
->resp
.nresults
= nentries
;
377 dataset
->resp
.result_len
= buffilled
- sizeof (*dataset
);
379 assert (buflen
- buffilled
>= req
->key_len
);
380 key_copy
= memcpy (buffer
+ buffilled
, key
, req
->key_len
);
381 buffilled
+= req
->key_len
;
383 /* Now we can determine whether on refill we have to create a new
389 if (dataset
->head
.allocsize
== dh
->allocsize
390 && dataset
->head
.recsize
== dh
->recsize
391 && memcmp (&dataset
->resp
, dh
->data
,
392 dh
->allocsize
- offsetof (struct dataset
, resp
)) == 0)
394 /* The data has not changed. We will just bump the timeout
395 value. Note that the new record has been allocated on
396 the stack and need not be freed. */
397 dh
->timeout
= dataset
->head
.timeout
;
398 dh
->ttl
= dataset
->head
.ttl
;
400 dataset
= (struct dataset
*) dh
;
408 = (struct dataset
*) mempool_alloc (db
, total
+ req
->key_len
, 1);
409 if (__builtin_expect (newp
!= NULL
, 1))
411 /* Adjust pointer into the memory block. */
412 key_copy
= (char *) newp
+ (key_copy
- buffer
);
414 dataset
= memcpy (newp
, dataset
, total
+ req
->key_len
);
418 /* Mark the old record as obsolete. */
423 if (he
== NULL
&& fd
!= -1)
425 /* We write the dataset before inserting it to the database
426 since while inserting this thread might block and so would
427 unnecessarily let the receiver wait. */
430 if (__builtin_expect (db
->mmap_used
, 1) && cacheable
)
432 assert (db
->wr_fd
!= -1);
433 assert ((char *) &dataset
->resp
> (char *) db
->data
);
434 assert ((char *) dataset
- (char *) db
->head
+ total
435 <= (sizeof (struct database_pers_head
)
436 + db
->head
->module
* sizeof (ref_t
)
437 + db
->head
->data_size
));
438 # ifndef __ASSUME_SENDFILE
441 sendfileall (fd
, db
->wr_fd
, (char *) &dataset
->resp
442 - (char *) db
->head
, dataset
->head
.recsize
);
443 # ifndef __ASSUME_SENDFILE
444 if (written
== -1 && errno
== ENOSYS
)
451 #if defined HAVE_SENDFILE && !defined __ASSUME_SENDFILE
454 writeall (fd
, &dataset
->resp
, dataset
->head
.recsize
);
460 /* If necessary, we also propagate the data to disk. */
464 uintptr_t pval
= (uintptr_t) dataset
& ~pagesize_m1
;
465 msync ((void *) pval
,
466 ((uintptr_t) dataset
& pagesize_m1
) + total
+ req
->key_len
,
470 (void) cache_add (req
->type
, key_copy
, req
->key_len
, &dataset
->head
,
471 true, db
, uid
, he
== NULL
);
473 pthread_rwlock_unlock (&db
->lock
);
475 /* Mark the old entry as obsolete. */
491 addinnetgrX (struct database_dyn
*db
, int fd
, request_header
*req
,
492 char *key
, uid_t uid
, struct hashentry
*he
,
495 const char *group
= key
;
496 key
= (char *) rawmemchr (key
, '\0') + 1;
497 size_t group_len
= key
- group
- 1;
498 const char *host
= *key
++ ? key
: NULL
;
500 key
= (char *) rawmemchr (key
, '\0') + 1;
501 const char *user
= *key
++ ? key
: NULL
;
503 key
= (char *) rawmemchr (key
, '\0') + 1;
504 const char *domain
= *key
++ ? key
: NULL
;
506 if (__builtin_expect (debug_level
> 0, 0))
509 dbg_log (_("Haven't found \"%s (%s,%s,%s)\" in netgroup cache!"),
510 group
, host
?: "", user
?: "", domain
?: "");
512 dbg_log (_("Reloading \"%s (%s,%s,%s)\" in netgroup cache!"),
513 group
, host
?: "", user
?: "", domain
?: "");
516 struct dataset
*result
= (struct dataset
*) cache_search (GETNETGRENT
,
521 timeout
= result
->head
.timeout
;
524 request_header req_get
=
529 timeout
= addgetnetgrentX (db
, -1, &req_get
, group
, uid
, NULL
, NULL
,
535 struct datahead head
;
536 innetgroup_response_header resp
;
538 = (struct indataset
*) mempool_alloc (db
,
539 sizeof (*dataset
) + req
->key_len
,
541 struct indataset dataset_mem
;
542 bool cacheable
= true;
543 if (__builtin_expect (dataset
== NULL
, 0))
546 dataset
= &dataset_mem
;
549 dataset
->head
.allocsize
= sizeof (*dataset
) + req
->key_len
;
550 dataset
->head
.recsize
= sizeof (innetgroup_response_header
);
551 dataset
->head
.notfound
= result
->head
.notfound
;
552 dataset
->head
.nreloads
= he
== NULL
? 0 : (dh
->nreloads
+ 1);
553 dataset
->head
.usable
= true;
554 dataset
->head
.ttl
= result
->head
.ttl
;
555 dataset
->head
.timeout
= timeout
;
557 dataset
->resp
.version
= NSCD_VERSION
;
558 dataset
->resp
.found
= result
->resp
.found
;
559 /* Until we find a matching entry the result is 0. */
560 dataset
->resp
.result
= 0;
562 char *key_copy
= memcpy ((char *) (dataset
+ 1), group
, req
->key_len
);
564 if (dataset
->resp
.found
)
566 const char *triplets
= (const char *) (&result
->resp
+ 1);
568 for (nscd_ssize_t i
= result
->resp
.nresults
; i
> 0; --i
)
573 success
= strcmp (host
, triplets
) == 0;
574 triplets
= (const char *) rawmemchr (triplets
, '\0') + 1;
576 if (success
&& user
!= NULL
)
577 success
= strcmp (user
, triplets
) == 0;
578 triplets
= (const char *) rawmemchr (triplets
, '\0') + 1;
580 if (success
&& (domain
== NULL
|| strcmp (domain
, triplets
) == 0))
582 dataset
->resp
.result
= 1;
585 triplets
= (const char *) rawmemchr (triplets
, '\0') + 1;
589 if (he
!= NULL
&& dh
->data
[0].innetgroupdata
.result
== dataset
->resp
.result
)
591 /* The data has not changed. We will just bump the timeout
592 value. Note that the new record has been allocated on
593 the stack and need not be freed. */
594 dh
->timeout
= timeout
;
595 dh
->ttl
= dataset
->head
.ttl
;
602 /* We write the dataset before inserting it to the database
603 since while inserting this thread might block and so would
604 unnecessarily let the receiver wait. */
608 if (__builtin_expect (db
->mmap_used
, 1) && cacheable
)
610 assert (db
->wr_fd
!= -1);
611 assert ((char *) &dataset
->resp
> (char *) db
->data
);
612 assert ((char *) dataset
- (char *) db
->head
+ sizeof (*dataset
)
613 <= (sizeof (struct database_pers_head
)
614 + db
->head
->module
* sizeof (ref_t
)
615 + db
->head
->data_size
));
616 # ifndef __ASSUME_SENDFILE
619 sendfileall (fd
, db
->wr_fd
,
620 (char *) &dataset
->resp
- (char *) db
->head
,
621 sizeof (innetgroup_response_header
));
622 # ifndef __ASSUME_SENDFILE
623 if (written
== -1 && errno
== ENOSYS
)
630 #if defined HAVE_SENDFILE && !defined __ASSUME_SENDFILE
633 writeall (fd
, &dataset
->resp
, sizeof (innetgroup_response_header
));
639 /* If necessary, we also propagate the data to disk. */
643 uintptr_t pval
= (uintptr_t) dataset
& ~pagesize_m1
;
644 msync ((void *) pval
,
645 ((uintptr_t) dataset
& pagesize_m1
) + sizeof (*dataset
)
650 (void) cache_add (req
->type
, key_copy
, req
->key_len
, &dataset
->head
,
651 true, db
, uid
, he
== NULL
);
653 pthread_rwlock_unlock (&db
->lock
);
655 /* Mark the old entry as obsolete. */
665 addgetnetgrent (struct database_dyn
*db
, int fd
, request_header
*req
,
666 void *key
, uid_t uid
)
668 struct dataset
*ignore
;
670 addgetnetgrentX (db
, fd
, req
, key
, uid
, NULL
, NULL
, &ignore
);
675 readdgetnetgrent (struct database_dyn
*db
, struct hashentry
*he
,
683 struct dataset
*ignore
;
685 return addgetnetgrentX (db
, -1, &req
, db
->data
+ he
->key
, he
->owner
, he
, dh
,
691 addinnetgr (struct database_dyn
*db
, int fd
, request_header
*req
,
692 void *key
, uid_t uid
)
694 addinnetgrX (db
, fd
, req
, key
, uid
, NULL
, NULL
);
699 readdinnetgr (struct database_dyn
*db
, struct hashentry
*he
,
708 return addinnetgrX (db
, -1, &req
, db
->data
+ he
->key
, he
->owner
, he
, dh
);