| 1 | /* Copyright (C) 1998-2020 Free Software Foundation, Inc. | 
| 2 |    This file is part of the GNU C Library. | 
| 3 |    Contributed by Thorsten Kukuk <kukuk@suse.de>, 1998. | 
| 4 |  | 
| 5 |    The GNU C Library is free software; you can redistribute it and/or | 
| 6 |    modify it under the terms of the GNU Lesser General Public | 
| 7 |    License as published by the Free Software Foundation; either | 
| 8 |    version 2.1 of the License, or (at your option) any later version. | 
| 9 |  | 
| 10 |    The GNU C Library is distributed in the hope that it will be useful, | 
| 11 |    but WITHOUT ANY WARRANTY; without even the implied warranty of | 
| 12 |    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
| 13 |    Lesser General Public License for more details. | 
| 14 |  | 
| 15 |    You should have received a copy of the GNU Lesser General Public | 
| 16 |    License along with the GNU C Library; if not, see | 
| 17 |    <https://www.gnu.org/licenses/>.  */ | 
| 18 |  | 
| 19 | #include <ctype.h> | 
| 20 | #include <errno.h> | 
| 21 | #include <fcntl.h> | 
| 22 | #include <grp.h> | 
| 23 | #include <nss.h> | 
| 24 | #include <stdio_ext.h> | 
| 25 | #include <string.h> | 
| 26 | #include <unistd.h> | 
| 27 | #include <sys/param.h> | 
| 28 | #include <nsswitch.h> | 
| 29 | #include <libc-lock.h> | 
| 30 | #include <kernel-features.h> | 
| 31 | #include <scratch_buffer.h> | 
| 32 | #include <nss_files.h> | 
| 33 |  | 
| 34 | NSS_DECLARE_MODULE_FUNCTIONS (compat) | 
| 35 |  | 
| 36 | static service_user *ni; | 
| 37 | static enum nss_status (*initgroups_dyn_impl) (const char *, gid_t, | 
| 38 | 					       long int *, long int *, | 
| 39 | 					       gid_t **, long int, int *); | 
| 40 | static enum nss_status (*getgrnam_r_impl) (const char *name, | 
| 41 | 					   struct group * grp, char *buffer, | 
| 42 | 					   size_t buflen, int *errnop); | 
| 43 | static enum nss_status (*getgrgid_r_impl) (gid_t gid, struct group * grp, | 
| 44 | 					   char *buffer, size_t buflen, | 
| 45 | 					   int *errnop); | 
| 46 | static enum nss_status (*setgrent_impl) (int stayopen); | 
| 47 | static enum nss_status (*getgrent_r_impl) (struct group * grp, char *buffer, | 
| 48 | 					   size_t buflen, int *errnop); | 
| 49 | static enum nss_status (*endgrent_impl) (void); | 
| 50 |  | 
| 51 | /* Protect global state against multiple changers.  */ | 
| 52 | __libc_lock_define_initialized (static, lock) | 
| 53 |  | 
| 54 |  | 
| 55 | /* Get the declaration of the parser function.  */ | 
| 56 | #define ENTNAME grent | 
| 57 | #define STRUCTURE group | 
| 58 | #define EXTERN_PARSER | 
| 59 | #include <nss/nss_files/files-parse.c> | 
| 60 |  | 
| 61 | /* Structure for remembering -group members ... */ | 
| 62 | #define BLACKLIST_INITIAL_SIZE 512 | 
| 63 | #define BLACKLIST_INCREMENT 256 | 
| 64 | struct blacklist_t | 
| 65 | { | 
| 66 |   char *data; | 
| 67 |   int current; | 
| 68 |   int size; | 
| 69 | }; | 
| 70 |  | 
| 71 | struct ent_t | 
| 72 | { | 
| 73 |   bool files; | 
| 74 |   bool need_endgrent; | 
| 75 |   bool skip_initgroups_dyn; | 
| 76 |   FILE *stream; | 
| 77 |   struct blacklist_t blacklist; | 
| 78 | }; | 
| 79 | typedef struct ent_t ent_t; | 
| 80 |  | 
| 81 | /* Prototypes for local functions.  */ | 
| 82 | static void blacklist_store_name (const char *, ent_t *); | 
| 83 | static bool in_blacklist (const char *, int, ent_t *); | 
| 84 |  | 
| 85 | /* Initialize the NSS interface/functions. The calling function must | 
| 86 |    hold the lock.  */ | 
| 87 | static void | 
| 88 | init_nss_interface (void) | 
| 89 | { | 
| 90 |   __libc_lock_lock (lock); | 
| 91 |  | 
| 92 |   /* Retest.  */ | 
| 93 |   if (ni == NULL | 
| 94 |       && __nss_database_lookup2 ("group_compat" , NULL, "nis" , &ni) >= 0) | 
| 95 |     { | 
| 96 |       initgroups_dyn_impl = __nss_lookup_function (ni, "initgroups_dyn" ); | 
| 97 |       getgrnam_r_impl = __nss_lookup_function (ni, "getgrnam_r" ); | 
| 98 |       getgrgid_r_impl = __nss_lookup_function (ni, "getgrgid_r" ); | 
| 99 |       setgrent_impl = __nss_lookup_function (ni, "setgrent" ); | 
| 100 |       getgrent_r_impl = __nss_lookup_function (ni, "getgrent_r" ); | 
| 101 |       endgrent_impl = __nss_lookup_function (ni, "endgrent" ); | 
| 102 |     } | 
| 103 |  | 
| 104 |   __libc_lock_unlock (lock); | 
| 105 | } | 
| 106 |  | 
| 107 | static enum nss_status | 
| 108 | internal_setgrent (ent_t *ent) | 
| 109 | { | 
| 110 |   enum nss_status status = NSS_STATUS_SUCCESS; | 
| 111 |  | 
| 112 |   ent->files = true; | 
| 113 |  | 
| 114 |   if (ni == NULL) | 
| 115 |     init_nss_interface (); | 
| 116 |  | 
| 117 |   if (ent->blacklist.data != NULL) | 
| 118 |     { | 
| 119 |       ent->blacklist.current = 1; | 
| 120 |       ent->blacklist.data[0] = '|'; | 
| 121 |       ent->blacklist.data[1] = '\0'; | 
| 122 |     } | 
| 123 |   else | 
| 124 |     ent->blacklist.current = 0; | 
| 125 |  | 
| 126 |   ent->stream = __nss_files_fopen ("/etc/group" ); | 
| 127 |  | 
| 128 |   if (ent->stream == NULL) | 
| 129 |     status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL; | 
| 130 |  | 
| 131 |   return status; | 
| 132 | } | 
| 133 |  | 
| 134 |  | 
| 135 | static enum nss_status __attribute_warn_unused_result__ | 
| 136 | internal_endgrent (ent_t *ent) | 
| 137 | { | 
| 138 |   if (ent->stream != NULL) | 
| 139 |     { | 
| 140 |       fclose (ent->stream); | 
| 141 |       ent->stream = NULL; | 
| 142 |     } | 
| 143 |  | 
| 144 |   if (ent->blacklist.data != NULL) | 
| 145 |     { | 
| 146 |       ent->blacklist.current = 1; | 
| 147 |       ent->blacklist.data[0] = '|'; | 
| 148 |       ent->blacklist.data[1] = '\0'; | 
| 149 |     } | 
| 150 |   else | 
| 151 |     ent->blacklist.current = 0; | 
| 152 |  | 
| 153 |   if (ent->need_endgrent && endgrent_impl != NULL) | 
| 154 |     endgrent_impl (); | 
| 155 |  | 
| 156 |   return NSS_STATUS_SUCCESS; | 
| 157 | } | 
| 158 |  | 
| 159 | /* Like internal_endgrent, but preserve errno in all cases.  */ | 
| 160 | static void | 
| 161 | internal_endgrent_noerror (ent_t *ent) | 
| 162 | { | 
| 163 |   int saved_errno = errno; | 
| 164 |   enum nss_status unused __attribute__ ((unused)) = internal_endgrent (ent); | 
| 165 |   __set_errno (saved_errno); | 
| 166 | } | 
| 167 |  | 
| 168 | /* Add new group record.  */ | 
| 169 | static void | 
| 170 | add_group (long int *start, long int *size, gid_t **groupsp, long int limit, | 
| 171 | 	   gid_t gid) | 
| 172 | { | 
| 173 |   gid_t *groups = *groupsp; | 
| 174 |  | 
| 175 |   /* Matches user.  Insert this group.  */ | 
| 176 |   if (__glibc_unlikely (*start == *size)) | 
| 177 |     { | 
| 178 |       /* Need a bigger buffer.  */ | 
| 179 |       gid_t *newgroups; | 
| 180 |       long int newsize; | 
| 181 |  | 
| 182 |       if (limit > 0 && *size == limit) | 
| 183 | 	/* We reached the maximum.  */ | 
| 184 | 	return; | 
| 185 |  | 
| 186 |       if (limit <= 0) | 
| 187 | 	newsize = 2 * *size; | 
| 188 |       else | 
| 189 | 	newsize = MIN (limit, 2 * *size); | 
| 190 |  | 
| 191 |       newgroups = realloc (groups, newsize * sizeof (*groups)); | 
| 192 |       if (newgroups == NULL) | 
| 193 | 	return; | 
| 194 |       *groupsp = groups = newgroups; | 
| 195 |       *size = newsize; | 
| 196 |     } | 
| 197 |  | 
| 198 |   groups[*start] = gid; | 
| 199 |   *start += 1; | 
| 200 | } | 
| 201 |  | 
| 202 | /* This function checks, if the user is a member of this group and if | 
| 203 |    yes, add the group id to the list.  Return nonzero is we couldn't | 
| 204 |    handle the group because the user is not in the member list.  */ | 
| 205 | static int | 
| 206 | check_and_add_group (const char *user, gid_t group, long int *start, | 
| 207 | 		     long int *size, gid_t **groupsp, long int limit, | 
| 208 | 		     struct group *grp) | 
| 209 | { | 
| 210 |   char **member; | 
| 211 |  | 
| 212 |   /* Don't add main group to list of groups.  */ | 
| 213 |   if (grp->gr_gid == group) | 
| 214 |     return 0; | 
| 215 |  | 
| 216 |   for (member = grp->gr_mem; *member != NULL; ++member) | 
| 217 |     if (strcmp (*member, user) == 0) | 
| 218 |       { | 
| 219 | 	add_group (start, size, groupsp, limit, grp->gr_gid); | 
| 220 | 	return 0; | 
| 221 |       } | 
| 222 |  | 
| 223 |   return 1; | 
| 224 | } | 
| 225 |  | 
| 226 | /* Get the next group from NSS  (+ entry). If the NSS module supports | 
| 227 |    initgroups_dyn, get all entries at once.  */ | 
| 228 | static enum nss_status | 
| 229 | getgrent_next_nss (ent_t *ent, char *buffer, size_t buflen, const char *user, | 
| 230 | 		   gid_t group, long int *start, long int *size, | 
| 231 | 		   gid_t **groupsp, long int limit, int *errnop) | 
| 232 | { | 
| 233 |   enum nss_status status; | 
| 234 |   struct group grpbuf; | 
| 235 |  | 
| 236 |   /* Try nss_initgroups_dyn if supported. We also need getgrgid_r. | 
| 237 |      If this function is not supported, step through the whole group | 
| 238 |      database with getgrent_r.  */ | 
| 239 |   if (! ent->skip_initgroups_dyn) | 
| 240 |     { | 
| 241 |       long int mystart = 0; | 
| 242 |       long int mysize = limit <= 0 ? *size : limit; | 
| 243 |       gid_t *mygroups = malloc (mysize * sizeof (gid_t)); | 
| 244 |  | 
| 245 |       if (mygroups == NULL) | 
| 246 | 	return NSS_STATUS_TRYAGAIN; | 
| 247 |  | 
| 248 |       /* For every gid in the list we get from the NSS module, | 
| 249 | 	 get the whole group entry. We need to do this, since we | 
| 250 | 	 need the group name to check if it is in the blacklist. | 
| 251 | 	 In worst case, this is as twice as slow as stepping with | 
| 252 | 	 getgrent_r through the whole group database. But for large | 
| 253 | 	 group databases this is faster, since the user can only be | 
| 254 | 	 in a limited number of groups.  */ | 
| 255 |       if (initgroups_dyn_impl (user, group, &mystart, &mysize, &mygroups, | 
| 256 | 			       limit, errnop) == NSS_STATUS_SUCCESS) | 
| 257 | 	{ | 
| 258 | 	  status = NSS_STATUS_NOTFOUND; | 
| 259 |  | 
| 260 | 	  /* If there is no blacklist we can trust the underlying | 
| 261 | 	     initgroups implementation.  */ | 
| 262 | 	  if (ent->blacklist.current <= 1) | 
| 263 | 	    for (int i = 0; i < mystart; i++) | 
| 264 | 	      add_group (start, size, groupsp, limit, mygroups[i]); | 
| 265 | 	  else | 
| 266 | 	    { | 
| 267 | 	      /* A temporary buffer. We use the normal buffer, until we find | 
| 268 | 		 an entry, for which this buffer is to small.  In this case, we | 
| 269 | 		 overwrite the pointer with one to a bigger buffer.  */ | 
| 270 | 	      char *tmpbuf = buffer; | 
| 271 | 	      size_t tmplen = buflen; | 
| 272 |  | 
| 273 | 	      for (int i = 0; i < mystart; i++) | 
| 274 | 		{ | 
| 275 | 		  while ((status = getgrgid_r_impl (mygroups[i], &grpbuf, | 
| 276 | 						    tmpbuf, tmplen, errnop)) | 
| 277 | 			 == NSS_STATUS_TRYAGAIN | 
| 278 | 			 && *errnop == ERANGE) | 
| 279 |                     { | 
| 280 | 		      /* Check for overflow. */ | 
| 281 | 		      if (__glibc_unlikely (tmplen * 2 < tmplen)) | 
| 282 | 			{ | 
| 283 | 			  __set_errno (ENOMEM); | 
| 284 | 			  status = NSS_STATUS_TRYAGAIN; | 
| 285 | 			  goto done; | 
| 286 | 			} | 
| 287 | 		      /* Increase the size.  Make sure that we retry | 
| 288 | 			 with a reasonable size.  */ | 
| 289 | 		      tmplen *= 2; | 
| 290 | 		      if (tmplen < 1024) | 
| 291 | 			tmplen = 1024; | 
| 292 | 		      if (tmpbuf != buffer) | 
| 293 | 			free (tmpbuf); | 
| 294 | 		      tmpbuf = malloc (tmplen); | 
| 295 | 		      if (__glibc_unlikely (tmpbuf == NULL)) | 
| 296 | 			{ | 
| 297 | 			  status = NSS_STATUS_TRYAGAIN; | 
| 298 | 			  goto done; | 
| 299 | 			} | 
| 300 |                     } | 
| 301 |  | 
| 302 | 		  if (__builtin_expect  (status != NSS_STATUS_NOTFOUND, 1)) | 
| 303 | 		    { | 
| 304 | 		      if (__builtin_expect  (status != NSS_STATUS_SUCCESS, 0)) | 
| 305 | 		        goto done; | 
| 306 |  | 
| 307 | 		      if (!in_blacklist (grpbuf.gr_name, | 
| 308 | 					 strlen (grpbuf.gr_name), ent) | 
| 309 | 			  && check_and_add_group (user, group, start, size, | 
| 310 | 						  groupsp, limit, &grpbuf)) | 
| 311 | 			{ | 
| 312 | 			  if (setgrent_impl != NULL) | 
| 313 | 			    { | 
| 314 | 			      setgrent_impl (1); | 
| 315 | 			      ent->need_endgrent = true; | 
| 316 | 			    } | 
| 317 | 			  ent->skip_initgroups_dyn = true; | 
| 318 |  | 
| 319 | 			  goto iter; | 
| 320 | 			} | 
| 321 | 		    } | 
| 322 | 		} | 
| 323 |  | 
| 324 | 	      status = NSS_STATUS_NOTFOUND; | 
| 325 |  | 
| 326 |  done: | 
| 327 | 	      if (tmpbuf != buffer) | 
| 328 | 	        free (tmpbuf); | 
| 329 | 	    } | 
| 330 |  | 
| 331 | 	  free (mygroups); | 
| 332 |  | 
| 333 | 	  return status; | 
| 334 | 	} | 
| 335 |  | 
| 336 |       free (mygroups); | 
| 337 |     } | 
| 338 |  | 
| 339 |   /* If we come here, the NSS module does not support initgroups_dyn | 
| 340 |      or we were confronted with a split group.  In these cases we have | 
| 341 |      to step through the whole list ourself.  */ | 
| 342 |  iter: | 
| 343 |   do | 
| 344 |     { | 
| 345 |       if ((status = getgrent_r_impl (&grpbuf, buffer, buflen, errnop)) | 
| 346 | 	  != NSS_STATUS_SUCCESS) | 
| 347 | 	break; | 
| 348 |     } | 
| 349 |   while (in_blacklist (grpbuf.gr_name, strlen (grpbuf.gr_name), ent)); | 
| 350 |  | 
| 351 |   if (status == NSS_STATUS_SUCCESS) | 
| 352 |     check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf); | 
| 353 |  | 
| 354 |   return status; | 
| 355 | } | 
| 356 |  | 
| 357 | static enum nss_status | 
| 358 | internal_getgrent_r (ent_t *ent, char *buffer, size_t buflen, const char *user, | 
| 359 | 		     gid_t group, long int *start, long int *size, | 
| 360 | 		     gid_t **groupsp, long int limit, int *errnop) | 
| 361 | { | 
| 362 |   struct parser_data *data = (void *) buffer; | 
| 363 |   struct group grpbuf; | 
| 364 |  | 
| 365 |   if (!ent->files) | 
| 366 |     return getgrent_next_nss (ent, buffer, buflen, user, group, | 
| 367 | 			      start, size, groupsp, limit, errnop); | 
| 368 |  | 
| 369 |   while (1) | 
| 370 |     { | 
| 371 |       fpos_t pos; | 
| 372 |       int parse_res = 0; | 
| 373 |       char *p; | 
| 374 |  | 
| 375 |       do | 
| 376 | 	{ | 
| 377 | 	  /* We need at least 3 characters for one line.  */ | 
| 378 | 	  if (__glibc_unlikely (buflen < 3)) | 
| 379 | 	    { | 
| 380 | 	    erange: | 
| 381 | 	      *errnop = ERANGE; | 
| 382 | 	      return NSS_STATUS_TRYAGAIN; | 
| 383 | 	    } | 
| 384 |  | 
| 385 | 	  fgetpos (ent->stream, &pos); | 
| 386 | 	  buffer[buflen - 1] = '\xff'; | 
| 387 | 	  p = fgets_unlocked (buffer, buflen, ent->stream); | 
| 388 | 	  if (p == NULL && feof_unlocked (ent->stream)) | 
| 389 | 	    return NSS_STATUS_NOTFOUND; | 
| 390 |  | 
| 391 | 	  if (p == NULL || __builtin_expect (buffer[buflen - 1] != '\xff', 0)) | 
| 392 | 	    { | 
| 393 | 	    erange_reset: | 
| 394 | 	      fsetpos (ent->stream, &pos); | 
| 395 | 	      goto erange; | 
| 396 | 	    } | 
| 397 |  | 
| 398 | 	  /* Terminate the line for any case.  */ | 
| 399 | 	  buffer[buflen - 1] = '\0'; | 
| 400 |  | 
| 401 | 	  /* Skip leading blanks.  */ | 
| 402 | 	  while (isspace (*p)) | 
| 403 | 	    ++p; | 
| 404 | 	} | 
| 405 |       while (*p == '\0' || *p == '#' /* Ignore empty and comment lines. */ | 
| 406 | 	     /* Parse the line.  If it is invalid, loop to | 
| 407 | 		get the next line of the file to parse.  */ | 
| 408 | 	     || !(parse_res = _nss_files_parse_grent (p, &grpbuf, data, buflen, | 
| 409 | 						      errnop))); | 
| 410 |  | 
| 411 |       if (__glibc_unlikely (parse_res == -1)) | 
| 412 | 	/* The parser ran out of space.  */ | 
| 413 | 	goto erange_reset; | 
| 414 |  | 
| 415 |       if (grpbuf.gr_name[0] != '+' && grpbuf.gr_name[0] != '-') | 
| 416 | 	/* This is a real entry.  */ | 
| 417 | 	break; | 
| 418 |  | 
| 419 |       /* -group */ | 
| 420 |       if (grpbuf.gr_name[0] == '-' && grpbuf.gr_name[1] != '\0' | 
| 421 | 	  && grpbuf.gr_name[1] != '@') | 
| 422 | 	{ | 
| 423 | 	  blacklist_store_name (&grpbuf.gr_name[1], ent); | 
| 424 | 	  continue; | 
| 425 | 	} | 
| 426 |  | 
| 427 |       /* +group */ | 
| 428 |       if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] != '\0' | 
| 429 | 	  && grpbuf.gr_name[1] != '@') | 
| 430 | 	{ | 
| 431 | 	  if (in_blacklist (&grpbuf.gr_name[1], | 
| 432 | 			    strlen (&grpbuf.gr_name[1]), ent)) | 
| 433 | 	    continue; | 
| 434 | 	  /* Store the group in the blacklist for the "+" at the end of | 
| 435 | 	     /etc/group */ | 
| 436 | 	  blacklist_store_name (&grpbuf.gr_name[1], ent); | 
| 437 | 	  if (getgrnam_r_impl == NULL) | 
| 438 | 	    return NSS_STATUS_UNAVAIL; | 
| 439 | 	  else if (getgrnam_r_impl (&grpbuf.gr_name[1], &grpbuf, buffer, | 
| 440 | 				    buflen, errnop) != NSS_STATUS_SUCCESS) | 
| 441 | 	    continue; | 
| 442 |  | 
| 443 | 	  check_and_add_group (user, group, start, size, groupsp, | 
| 444 | 			       limit, &grpbuf); | 
| 445 |  | 
| 446 | 	  return NSS_STATUS_SUCCESS; | 
| 447 | 	} | 
| 448 |  | 
| 449 |       /* +:... */ | 
| 450 |       if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] == '\0') | 
| 451 | 	{ | 
| 452 | 	  /* If the selected module does not support getgrent_r or | 
| 453 | 	     initgroups_dyn, abort. We cannot find the needed group | 
| 454 | 	     entries.  */ | 
| 455 | 	  if (initgroups_dyn_impl == NULL || getgrgid_r_impl == NULL) | 
| 456 | 	    { | 
| 457 | 	      if (setgrent_impl != NULL) | 
| 458 | 		{ | 
| 459 | 		  setgrent_impl (1); | 
| 460 | 		  ent->need_endgrent = true; | 
| 461 | 		} | 
| 462 | 	      ent->skip_initgroups_dyn = true; | 
| 463 |  | 
| 464 | 	      if (getgrent_r_impl == NULL) | 
| 465 | 		return NSS_STATUS_UNAVAIL; | 
| 466 | 	    } | 
| 467 |  | 
| 468 | 	  ent->files = false; | 
| 469 |  | 
| 470 | 	  return getgrent_next_nss (ent, buffer, buflen, user, group, | 
| 471 | 				    start, size, groupsp, limit, errnop); | 
| 472 | 	} | 
| 473 |     } | 
| 474 |  | 
| 475 |   check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf); | 
| 476 |  | 
| 477 |   return NSS_STATUS_SUCCESS; | 
| 478 | } | 
| 479 |  | 
| 480 |  | 
| 481 | enum nss_status | 
| 482 | _nss_compat_initgroups_dyn (const char *user, gid_t group, long int *start, | 
| 483 | 			    long int *size, gid_t **groupsp, long int limit, | 
| 484 | 			    int *errnop) | 
| 485 | { | 
| 486 |   enum nss_status status; | 
| 487 |   ent_t intern = { true, false, false, NULL, {NULL, 0, 0} }; | 
| 488 |  | 
| 489 |   status = internal_setgrent (&intern); | 
| 490 |   if (status != NSS_STATUS_SUCCESS) | 
| 491 |     return status; | 
| 492 |  | 
| 493 |   struct scratch_buffer tmpbuf; | 
| 494 |   scratch_buffer_init (&tmpbuf); | 
| 495 |  | 
| 496 |   do | 
| 497 |     { | 
| 498 |       while ((status = internal_getgrent_r (&intern, tmpbuf.data, tmpbuf.length, | 
| 499 | 					    user, group, start, size, | 
| 500 | 					    groupsp, limit, errnop)) | 
| 501 | 	     == NSS_STATUS_TRYAGAIN && *errnop == ERANGE) | 
| 502 |         if (!scratch_buffer_grow (&tmpbuf)) | 
| 503 | 	    goto done; | 
| 504 |     } | 
| 505 |   while (status == NSS_STATUS_SUCCESS); | 
| 506 |  | 
| 507 |   status = NSS_STATUS_SUCCESS; | 
| 508 |  | 
| 509 |  done: | 
| 510 |   scratch_buffer_free (&tmpbuf); | 
| 511 |  | 
| 512 |   internal_endgrent_noerror (&intern); | 
| 513 |  | 
| 514 |   return status; | 
| 515 | } | 
| 516 |  | 
| 517 |  | 
| 518 | /* Support routines for remembering -@netgroup and -user entries. | 
| 519 |    The names are stored in a single string with `|' as separator. */ | 
| 520 | static void | 
| 521 | blacklist_store_name (const char *name, ent_t *ent) | 
| 522 | { | 
| 523 |   int namelen = strlen (name); | 
| 524 |   char *tmp; | 
| 525 |  | 
| 526 |   /* First call, setup cache.  */ | 
| 527 |   if (ent->blacklist.size == 0) | 
| 528 |     { | 
| 529 |       ent->blacklist.size = MAX (BLACKLIST_INITIAL_SIZE, 2 * namelen); | 
| 530 |       ent->blacklist.data = malloc (ent->blacklist.size); | 
| 531 |       if (ent->blacklist.data == NULL) | 
| 532 | 	return; | 
| 533 |       ent->blacklist.data[0] = '|'; | 
| 534 |       ent->blacklist.data[1] = '\0'; | 
| 535 |       ent->blacklist.current = 1; | 
| 536 |     } | 
| 537 |   else | 
| 538 |     { | 
| 539 |       if (in_blacklist (name, namelen, ent)) | 
| 540 | 	return;			/* no duplicates */ | 
| 541 |  | 
| 542 |       if (ent->blacklist.current + namelen + 1 >= ent->blacklist.size) | 
| 543 | 	{ | 
| 544 | 	  ent->blacklist.size += MAX (BLACKLIST_INCREMENT, 2 * namelen); | 
| 545 | 	  tmp = realloc (ent->blacklist.data, ent->blacklist.size); | 
| 546 | 	  if (tmp == NULL) | 
| 547 | 	    { | 
| 548 | 	      free (ent->blacklist.data); | 
| 549 | 	      ent->blacklist.size = 0; | 
| 550 | 	      return; | 
| 551 | 	    } | 
| 552 | 	  ent->blacklist.data = tmp; | 
| 553 | 	} | 
| 554 |     } | 
| 555 |  | 
| 556 |   tmp = stpcpy (ent->blacklist.data + ent->blacklist.current, name); | 
| 557 |   *tmp++ = '|'; | 
| 558 |   *tmp = '\0'; | 
| 559 |   ent->blacklist.current += namelen + 1; | 
| 560 |  | 
| 561 |   return; | 
| 562 | } | 
| 563 |  | 
| 564 | /* Return whether ent->blacklist contains name.  */ | 
| 565 | static bool | 
| 566 | in_blacklist (const char *name, int namelen, ent_t *ent) | 
| 567 | { | 
| 568 |   char buf[namelen + 3]; | 
| 569 |   char *cp; | 
| 570 |  | 
| 571 |   if (ent->blacklist.data == NULL) | 
| 572 |     return false; | 
| 573 |  | 
| 574 |   buf[0] = '|'; | 
| 575 |   cp = stpcpy (&buf[1], name); | 
| 576 |   *cp++ = '|'; | 
| 577 |   *cp = '\0'; | 
| 578 |   return strstr (ent->blacklist.data, buf) != NULL; | 
| 579 | } | 
| 580 |  |