(root)/
glibc-2.38/
nss/
nss_compat/
compat-spwd.c
       1  /* Copyright (C) 1996-2023 Free Software Foundation, Inc.
       2     This file is part of the GNU C Library.
       3  
       4     The GNU C Library is free software; you can redistribute it and/or
       5     modify it under the terms of the GNU Lesser General Public
       6     License as published by the Free Software Foundation; either
       7     version 2.1 of the License, or (at your option) any later version.
       8  
       9     The GNU C Library is distributed in the hope that it will be useful,
      10     but WITHOUT ANY WARRANTY; without even the implied warranty of
      11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      12     Lesser General Public License for more details.
      13  
      14     You should have received a copy of the GNU Lesser General Public
      15     License along with the GNU C Library; if not, see
      16     <https://www.gnu.org/licenses/>.  */
      17  
      18  #include <ctype.h>
      19  #include <errno.h>
      20  #include <fcntl.h>
      21  #include <netdb.h>
      22  #include <nss.h>
      23  #include <nsswitch.h>
      24  #include <shadow.h>
      25  #include <stdio_ext.h>
      26  #include <string.h>
      27  #include <libc-lock.h>
      28  #include <kernel-features.h>
      29  #include <nss_files.h>
      30  
      31  #include "netgroup.h"
      32  #include "nisdomain.h"
      33  
      34  NSS_DECLARE_MODULE_FUNCTIONS (compat)
      35  
      36  static nss_action_list ni;
      37  static enum nss_status (*setspent_impl) (int stayopen);
      38  static enum nss_status (*getspnam_r_impl) (const char *name, struct spwd * sp,
      39  					   char *buffer, size_t buflen,
      40  					   int *errnop);
      41  static enum nss_status (*getspent_r_impl) (struct spwd * sp, char *buffer,
      42  					   size_t buflen, int *errnop);
      43  static enum nss_status (*endspent_impl) (void);
      44  
      45  /* Get the declaration of the parser function.  */
      46  #define ENTNAME spent
      47  #define STRUCTURE spwd
      48  #define EXTERN_PARSER
      49  #include <nss/nss_files/files-parse.c>
      50  
      51  /* Structure for remembering -@netgroup and -user members ... */
      52  #define BLACKLIST_INITIAL_SIZE 512
      53  #define BLACKLIST_INCREMENT 256
      54  struct blacklist_t
      55  {
      56    char *data;
      57    int current;
      58    int size;
      59  };
      60  
      61  struct ent_t
      62  {
      63    bool netgroup;
      64    bool files;
      65    bool first;
      66    enum nss_status setent_status;
      67    FILE *stream;
      68    struct blacklist_t blacklist;
      69    struct spwd pwd;
      70    struct __netgrent netgrdata;
      71  };
      72  typedef struct ent_t ent_t;
      73  
      74  static ent_t ext_ent = { false, true, false, NSS_STATUS_SUCCESS, NULL,
      75  			 { NULL, 0, 0},
      76  			 { NULL, NULL, 0, 0, 0, 0, 0, 0, 0}};
      77  
      78  /* Protect global state against multiple changers.  */
      79  __libc_lock_define_initialized (static, lock)
      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    if (__nss_database_get (nss_database_shadow_compat, &ni))
      91      {
      92        setspent_impl = __nss_lookup_function (ni, "setspent");
      93        getspnam_r_impl = __nss_lookup_function (ni, "getspnam_r");
      94        getspent_r_impl = __nss_lookup_function (ni, "getspent_r");
      95        endspent_impl = __nss_lookup_function (ni, "endspent");
      96      }
      97  }
      98  
      99  static void
     100  give_spwd_free (struct spwd *pwd)
     101  {
     102    free (pwd->sp_namp);
     103    free (pwd->sp_pwdp);
     104  
     105    memset (pwd, '\0', sizeof (struct spwd));
     106    pwd->sp_warn = -1;
     107    pwd->sp_inact = -1;
     108    pwd->sp_expire = -1;
     109    pwd->sp_flag = ~0ul;
     110  }
     111  
     112  static int
     113  spwd_need_buflen (struct spwd *pwd)
     114  {
     115    int len = 0;
     116  
     117    if (pwd->sp_pwdp != NULL)
     118      len += strlen (pwd->sp_pwdp) + 1;
     119  
     120    return len;
     121  }
     122  
     123  static void
     124  copy_spwd_changes (struct spwd *dest, struct spwd *src,
     125  		   char *buffer, size_t buflen)
     126  {
     127    if (src->sp_pwdp != NULL && strlen (src->sp_pwdp))
     128      {
     129        if (buffer == NULL)
     130  	dest->sp_pwdp = strdup (src->sp_pwdp);
     131        else if (dest->sp_pwdp
     132  	       && strlen (dest->sp_pwdp) >= strlen (src->sp_pwdp))
     133  	strcpy (dest->sp_pwdp, src->sp_pwdp);
     134        else
     135  	{
     136  	  dest->sp_pwdp = buffer;
     137  	  strcpy (dest->sp_pwdp, src->sp_pwdp);
     138  	  buffer += strlen (dest->sp_pwdp) + 1;
     139  	  buflen = buflen - (strlen (dest->sp_pwdp) + 1);
     140  	}
     141      }
     142    if (src->sp_lstchg != 0)
     143      dest->sp_lstchg = src->sp_lstchg;
     144    if (src->sp_min != 0)
     145      dest->sp_min = src->sp_min;
     146    if (src->sp_max != 0)
     147      dest->sp_max = src->sp_max;
     148    if (src->sp_warn != -1)
     149      dest->sp_warn = src->sp_warn;
     150    if (src->sp_inact != -1)
     151      dest->sp_inact = src->sp_inact;
     152    if (src->sp_expire != -1)
     153      dest->sp_expire = src->sp_expire;
     154    if (src->sp_flag != ~0ul)
     155      dest->sp_flag = src->sp_flag;
     156  }
     157  
     158  static enum nss_status
     159  internal_setspent (ent_t *ent, int stayopen, int needent)
     160  {
     161    enum nss_status status = NSS_STATUS_SUCCESS;
     162  
     163    ent->first = ent->netgroup = 0;
     164    ent->files = true;
     165  
     166    /* If something was left over free it.  */
     167    if (ent->netgroup)
     168      __internal_endnetgrent (&ent->netgrdata);
     169  
     170    if (ent->blacklist.data != NULL)
     171      {
     172        ent->blacklist.current = 1;
     173        ent->blacklist.data[0] = '|';
     174        ent->blacklist.data[1] = '\0';
     175      }
     176    else
     177      ent->blacklist.current = 0;
     178  
     179    if (ent->stream == NULL)
     180      {
     181        ent->stream = __nss_files_fopen ("/etc/shadow");
     182  
     183        if (ent->stream == NULL)
     184  	status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
     185      }
     186    else
     187      rewind (ent->stream);
     188  
     189    give_spwd_free (&ent->pwd);
     190  
     191    if (needent && status == NSS_STATUS_SUCCESS && setspent_impl)
     192      ent->setent_status = setspent_impl (stayopen);
     193  
     194    return status;
     195  }
     196  
     197  
     198  enum nss_status
     199  _nss_compat_setspent (int stayopen)
     200  {
     201    enum nss_status result;
     202  
     203    __libc_lock_lock (lock);
     204  
     205    if (ni == NULL)
     206      init_nss_interface ();
     207  
     208    result = internal_setspent (&ext_ent, stayopen, 1);
     209  
     210    __libc_lock_unlock (lock);
     211  
     212    return result;
     213  }
     214  
     215  
     216  static enum nss_status __attribute_warn_unused_result__
     217  internal_endspent (ent_t *ent)
     218  {
     219    if (ent->stream != NULL)
     220      {
     221        fclose (ent->stream);
     222        ent->stream = NULL;
     223      }
     224  
     225    if (ent->netgroup)
     226      __internal_endnetgrent (&ent->netgrdata);
     227  
     228    ent->first = ent->netgroup = false;
     229    ent->files = true;
     230  
     231    if (ent->blacklist.data != NULL)
     232      {
     233        ent->blacklist.current = 1;
     234        ent->blacklist.data[0] = '|';
     235        ent->blacklist.data[1] = '\0';
     236      }
     237    else
     238      ent->blacklist.current = 0;
     239  
     240    give_spwd_free (&ent->pwd);
     241  
     242    return NSS_STATUS_SUCCESS;
     243  }
     244  
     245  /* Like internal_endspent, but preserve errno in all cases.  */
     246  static void
     247  internal_endspent_noerror (ent_t *ent)
     248  {
     249    int saved_errno = errno;
     250    enum nss_status unused __attribute__ ((unused)) = internal_endspent (ent);
     251    __set_errno (saved_errno);
     252  }
     253  
     254  enum nss_status
     255  _nss_compat_endspent (void)
     256  {
     257    enum nss_status result;
     258  
     259    __libc_lock_lock (lock);
     260  
     261    if (endspent_impl)
     262      endspent_impl ();
     263  
     264    result = internal_endspent (&ext_ent);
     265  
     266    __libc_lock_unlock (lock);
     267  
     268    return result;
     269  }
     270  
     271  static enum nss_status
     272  getspent_next_nss_netgr (const char *name, struct spwd *result, ent_t *ent,
     273  			 char *group, char *buffer, size_t buflen,
     274  			 int *errnop)
     275  {
     276    char *curdomain = NULL, *host, *user, *domain, *p2;
     277    size_t p2len;
     278  
     279    if (!getspnam_r_impl)
     280      return NSS_STATUS_UNAVAIL;
     281  
     282    /* If the setpwent call failed, say so.  */
     283    if (ent->setent_status != NSS_STATUS_SUCCESS)
     284      return ent->setent_status;
     285  
     286    if (ent->first)
     287      {
     288        memset (&ent->netgrdata, 0, sizeof (struct __netgrent));
     289        __internal_setnetgrent (group, &ent->netgrdata);
     290        ent->first = false;
     291      }
     292  
     293    while (1)
     294      {
     295        enum nss_status status;
     296  
     297        status = __internal_getnetgrent_r (&host, &user, &domain,
     298  					 &ent->netgrdata, buffer, buflen,
     299  					 errnop);
     300        if (status != 1)
     301  	{
     302  	  __internal_endnetgrent (&ent->netgrdata);
     303  	  ent->netgroup = false;
     304  	  give_spwd_free (&ent->pwd);
     305  	  return NSS_STATUS_RETURN;
     306  	}
     307  
     308        if (user == NULL || user[0] == '-')
     309  	continue;
     310  
     311        if (domain != NULL)
     312  	{
     313  	  if (curdomain == NULL
     314  	      && __nss_get_default_domain (&curdomain) != 0)
     315  	    {
     316  	      __internal_endnetgrent (&ent->netgrdata);
     317  	      ent->netgroup = false;
     318  	      give_spwd_free (&ent->pwd);
     319  	      return NSS_STATUS_UNAVAIL;
     320  	    }
     321  	  if (strcmp (curdomain, domain) != 0)
     322  	    continue;
     323  	}
     324  
     325        /* If name != NULL, we are called from getpwnam */
     326        if (name != NULL)
     327  	if (strcmp (user, name) != 0)
     328  	  continue;
     329  
     330        p2len = spwd_need_buflen (&ent->pwd);
     331        if (p2len > buflen)
     332  	{
     333  	  *errnop = ERANGE;
     334  	  return NSS_STATUS_TRYAGAIN;
     335  	}
     336        p2 = buffer + (buflen - p2len);
     337        buflen -= p2len;
     338  
     339        if (getspnam_r_impl (user, result, buffer, buflen, errnop)
     340  	  != NSS_STATUS_SUCCESS)
     341  	continue;
     342  
     343        if (!in_blacklist (result->sp_namp, strlen (result->sp_namp), ent))
     344  	{
     345  	  /* Store the User in the blacklist for possible the "+" at the
     346  	     end of /etc/passwd */
     347  	  blacklist_store_name (result->sp_namp, ent);
     348  	  copy_spwd_changes (result, &ent->pwd, p2, p2len);
     349  	  break;
     350  	}
     351      }
     352  
     353    return NSS_STATUS_SUCCESS;
     354  }
     355  
     356  
     357  static enum nss_status
     358  getspent_next_nss (struct spwd *result, ent_t *ent,
     359  		   char *buffer, size_t buflen, int *errnop)
     360  {
     361    enum nss_status status;
     362    char *p2;
     363    size_t p2len;
     364  
     365    if (!getspent_r_impl)
     366      return NSS_STATUS_UNAVAIL;
     367  
     368    p2len = spwd_need_buflen (&ent->pwd);
     369    if (p2len > buflen)
     370      {
     371        *errnop = ERANGE;
     372        return NSS_STATUS_TRYAGAIN;
     373      }
     374    p2 = buffer + (buflen - p2len);
     375    buflen -= p2len;
     376    do
     377      {
     378        if ((status = getspent_r_impl (result, buffer, buflen, errnop))
     379  	  != NSS_STATUS_SUCCESS)
     380  	return status;
     381      }
     382    while (in_blacklist (result->sp_namp, strlen (result->sp_namp), ent));
     383  
     384    copy_spwd_changes (result, &ent->pwd, p2, p2len);
     385  
     386    return NSS_STATUS_SUCCESS;
     387  }
     388  
     389  
     390  /* This function handle the +user entries in /etc/shadow */
     391  static enum nss_status
     392  getspnam_plususer (const char *name, struct spwd *result, ent_t *ent,
     393  		   char *buffer, size_t buflen, int *errnop)
     394  {
     395    if (!getspnam_r_impl)
     396      return NSS_STATUS_UNAVAIL;
     397  
     398    struct spwd pwd;
     399    memset (&pwd, '\0', sizeof (struct spwd));
     400    pwd.sp_warn = -1;
     401    pwd.sp_inact = -1;
     402    pwd.sp_expire = -1;
     403    pwd.sp_flag = ~0ul;
     404  
     405    copy_spwd_changes (&pwd, result, NULL, 0);
     406  
     407    size_t plen = spwd_need_buflen (&pwd);
     408    if (plen > buflen)
     409      {
     410        *errnop = ERANGE;
     411        return NSS_STATUS_TRYAGAIN;
     412      }
     413    char *p = buffer + (buflen - plen);
     414    buflen -= plen;
     415  
     416    enum nss_status status = getspnam_r_impl (name, result, buffer, buflen,
     417  					    errnop);
     418    if (status != NSS_STATUS_SUCCESS)
     419      return status;
     420  
     421    if (in_blacklist (result->sp_namp, strlen (result->sp_namp), ent))
     422      return NSS_STATUS_NOTFOUND;
     423  
     424    copy_spwd_changes (result, &pwd, p, plen);
     425    give_spwd_free (&pwd);
     426    /* We found the entry.  */
     427    return NSS_STATUS_SUCCESS;
     428  }
     429  
     430  
     431  static enum nss_status
     432  getspent_next_file (struct spwd *result, ent_t *ent,
     433  		    char *buffer, size_t buflen, int *errnop)
     434  {
     435    struct parser_data *data = (void *) buffer;
     436    while (1)
     437      {
     438        fpos_t pos;
     439        int parse_res = 0;
     440        char *p;
     441  
     442        do
     443  	{
     444  	  /* We need at least 3 characters for one line.  */
     445  	  if (__glibc_unlikely (buflen < 3))
     446  	    {
     447  	    erange:
     448  	      *errnop = ERANGE;
     449  	      return NSS_STATUS_TRYAGAIN;
     450  	    }
     451  
     452  	  fgetpos (ent->stream, &pos);
     453  	  buffer[buflen - 1] = '\xff';
     454  	  p = fgets_unlocked (buffer, buflen, ent->stream);
     455  	  if (p == NULL && feof_unlocked (ent->stream))
     456  	    return NSS_STATUS_NOTFOUND;
     457  
     458  	  if (p == NULL || __builtin_expect (buffer[buflen - 1] != '\xff', 0))
     459  	    {
     460  	    erange_reset:
     461  	      fsetpos (ent->stream, &pos);
     462  	      goto erange;
     463  	    }
     464  
     465  	  /* Skip leading blanks.  */
     466  	  while (isspace (*p))
     467  	    ++p;
     468  	}
     469        while (*p == '\0' || *p == '#'	/* Ignore empty and comment lines.  */
     470  	     /* Parse the line.  If it is invalid, loop to
     471  	        get the next line of the file to parse.  */
     472  	     || !(parse_res = _nss_files_parse_spent (p, result, data,
     473  						      buflen, errnop)));
     474  
     475        if (__glibc_unlikely (parse_res == -1))
     476  	/* The parser ran out of space.  */
     477  	goto erange_reset;
     478  
     479        if (result->sp_namp[0] != '+' && result->sp_namp[0] != '-')
     480  	/* This is a real entry.  */
     481  	break;
     482  
     483        /* -@netgroup */
     484        if (result->sp_namp[0] == '-' && result->sp_namp[1] == '@'
     485  	  && result->sp_namp[2] != '\0')
     486  	{
     487  	  /* XXX Do not use fixed length buffers.  */
     488  	  char buf2[1024];
     489  	  char *user, *host, *domain;
     490  	  struct __netgrent netgrdata;
     491  
     492  	  memset (&netgrdata, 0, sizeof (struct __netgrent));
     493  	  __internal_setnetgrent (&result->sp_namp[2], &netgrdata);
     494  	  while (__internal_getnetgrent_r (&host, &user, &domain,
     495  					   &netgrdata, buf2, sizeof (buf2),
     496  					   errnop))
     497  	    {
     498  	      if (user != NULL && user[0] != '-')
     499  		blacklist_store_name (user, ent);
     500  	    }
     501  	  __internal_endnetgrent (&netgrdata);
     502  	  continue;
     503  	}
     504  
     505        /* +@netgroup */
     506        if (result->sp_namp[0] == '+' && result->sp_namp[1] == '@'
     507  	  && result->sp_namp[2] != '\0')
     508  	{
     509  	  int status;
     510  
     511  	  ent->netgroup = true;
     512  	  ent->first = true;
     513  	  copy_spwd_changes (&ent->pwd, result, NULL, 0);
     514  
     515  	  status = getspent_next_nss_netgr (NULL, result, ent,
     516  					    &result->sp_namp[2],
     517  					    buffer, buflen, errnop);
     518  	  if (status == NSS_STATUS_RETURN)
     519  	    continue;
     520  	  else
     521  	    return status;
     522  	}
     523  
     524        /* -user */
     525        if (result->sp_namp[0] == '-' && result->sp_namp[1] != '\0'
     526  	  && result->sp_namp[1] != '@')
     527  	{
     528  	  blacklist_store_name (&result->sp_namp[1], ent);
     529  	  continue;
     530  	}
     531  
     532        /* +user */
     533        if (result->sp_namp[0] == '+' && result->sp_namp[1] != '\0'
     534  	  && result->sp_namp[1] != '@')
     535  	{
     536  	  size_t len = strlen (result->sp_namp);
     537  	  char buf[len];
     538  	  enum nss_status status;
     539  
     540  	  /* Store the User in the blacklist for the "+" at the end of
     541  	     /etc/passwd */
     542  	  memcpy (buf, &result->sp_namp[1], len);
     543  	  status = getspnam_plususer (&result->sp_namp[1], result, ent,
     544  				      buffer, buflen, errnop);
     545  	  blacklist_store_name (buf, ent);
     546  
     547  	  if (status == NSS_STATUS_SUCCESS)	/* We found the entry. */
     548  	    break;
     549  	  /* We couldn't parse the entry */
     550  	  else if (status == NSS_STATUS_RETURN
     551  		   /* entry doesn't exist */
     552  		   || status == NSS_STATUS_NOTFOUND)
     553  	    continue;
     554  	  else
     555  	    {
     556  	      if (status == NSS_STATUS_TRYAGAIN)
     557  		{
     558  		  fsetpos (ent->stream, &pos);
     559  		  *errnop = ERANGE;
     560  		}
     561  	      return status;
     562  	    }
     563  	}
     564  
     565        /* +:... */
     566        if (result->sp_namp[0] == '+' && result->sp_namp[1] == '\0')
     567  	{
     568  	  ent->files = false;
     569  	  ent->first = true;
     570  	  copy_spwd_changes (&ent->pwd, result, NULL, 0);
     571  
     572  	  return getspent_next_nss (result, ent, buffer, buflen, errnop);
     573  	}
     574      }
     575  
     576    return NSS_STATUS_SUCCESS;
     577  }
     578  
     579  
     580  static enum nss_status
     581  internal_getspent_r (struct spwd *pw, ent_t *ent,
     582  		     char *buffer, size_t buflen, int *errnop)
     583  {
     584    if (ent->netgroup)
     585      {
     586        enum nss_status status;
     587  
     588        /* We are searching members in a netgroup */
     589        /* Since this is not the first call, we don't need the group name */
     590        status = getspent_next_nss_netgr (NULL, pw, ent, NULL, buffer,
     591  					buflen, errnop);
     592  
     593        if (status == NSS_STATUS_RETURN)
     594  	return getspent_next_file (pw, ent, buffer, buflen, errnop);
     595        else
     596  	return status;
     597      }
     598    else if (ent->files)
     599      return getspent_next_file (pw, ent, buffer, buflen, errnop);
     600    else
     601      return getspent_next_nss (pw, ent, buffer, buflen, errnop);
     602  }
     603  
     604  
     605  enum nss_status
     606  _nss_compat_getspent_r (struct spwd *pwd, char *buffer, size_t buflen,
     607  			int *errnop)
     608  {
     609    enum nss_status result = NSS_STATUS_SUCCESS;
     610  
     611    __libc_lock_lock (lock);
     612  
     613    /* Be prepared that the setpwent function was not called before.  */
     614    if (ni == NULL)
     615      init_nss_interface ();
     616  
     617    if (ext_ent.stream == NULL)
     618      result = internal_setspent (&ext_ent, 1, 1);
     619  
     620    if (result == NSS_STATUS_SUCCESS)
     621      result = internal_getspent_r (pwd, &ext_ent, buffer, buflen, errnop);
     622  
     623    __libc_lock_unlock (lock);
     624  
     625    return result;
     626  }
     627  
     628  
     629  /* Searches in /etc/passwd and the NIS/NIS+ map for a special user */
     630  static enum nss_status
     631  internal_getspnam_r (const char *name, struct spwd *result, ent_t *ent,
     632  		     char *buffer, size_t buflen, int *errnop)
     633  {
     634    struct parser_data *data = (void *) buffer;
     635  
     636    while (1)
     637      {
     638        fpos_t pos;
     639        char *p;
     640        int parse_res;
     641  
     642        do
     643  	{
     644  	  /* We need at least 3 characters for one line.  */
     645  	  if (__glibc_unlikely (buflen < 3))
     646  	    {
     647  	    erange:
     648  	      *errnop = ERANGE;
     649  	      return NSS_STATUS_TRYAGAIN;
     650  	    }
     651  
     652  	  fgetpos (ent->stream, &pos);
     653  	  buffer[buflen - 1] = '\xff';
     654  	  p = fgets_unlocked (buffer, buflen, ent->stream);
     655  	  if (p == NULL && feof_unlocked (ent->stream))
     656  	    return NSS_STATUS_NOTFOUND;
     657  
     658  	  if (p == NULL || buffer[buflen - 1] != '\xff')
     659  	    {
     660  	    erange_reset:
     661  	      fsetpos (ent->stream, &pos);
     662  	      goto erange;
     663  	    }
     664  
     665            /* Terminate the line for any case.  */
     666  	  buffer[buflen - 1] = '\0';
     667  
     668  	  /* Skip leading blanks.  */
     669  	  while (isspace (*p))
     670  	    ++p;
     671  	}
     672        while (*p == '\0' || *p == '#' /* Ignore empty and comment lines.  */
     673  	     /* Parse the line.  If it is invalid, loop to
     674  	        get the next line of the file to parse.  */
     675  	     || !(parse_res = _nss_files_parse_spent (p, result, data, buflen,
     676  						      errnop)));
     677  
     678        if (__glibc_unlikely (parse_res == -1))
     679  	/* The parser ran out of space.  */
     680  	goto erange_reset;
     681  
     682        /* This is a real entry.  */
     683        if (result->sp_namp[0] != '+' && result->sp_namp[0] != '-')
     684  	{
     685  	  if (strcmp (result->sp_namp, name) == 0)
     686  	    return NSS_STATUS_SUCCESS;
     687  	  else
     688  	    continue;
     689  	}
     690  
     691        /* -@netgroup */
     692        /* If the loaded NSS module does not support this service, add
     693           all users from a +@netgroup entry to the blacklist, too.  */
     694        if (result->sp_namp[0] == '-' && result->sp_namp[1] == '@'
     695  	  && result->sp_namp[2] != '\0')
     696  	{
     697  	  if (innetgr (&result->sp_namp[2], NULL, name, NULL))
     698  	    return NSS_STATUS_NOTFOUND;
     699  	  continue;
     700  	}
     701  
     702        /* +@netgroup */
     703        if (result->sp_namp[0] == '+' && result->sp_namp[1] == '@'
     704  	  && result->sp_namp[2] != '\0')
     705  	{
     706  	  enum nss_status status;
     707  
     708  	  if (innetgr (&result->sp_namp[2], NULL, name, NULL))
     709  	    {
     710  	      status = getspnam_plususer (name, result, ent, buffer,
     711  					  buflen, errnop);
     712  
     713  	      if (status == NSS_STATUS_RETURN)
     714  		continue;
     715  
     716  	      return status;
     717  	    }
     718  	  continue;
     719  	}
     720  
     721        /* -user */
     722        if (result->sp_namp[0] == '-' && result->sp_namp[1] != '\0'
     723  	  && result->sp_namp[1] != '@')
     724  	{
     725  	  if (strcmp (&result->sp_namp[1], name) == 0)
     726  	    return NSS_STATUS_NOTFOUND;
     727  	  else
     728  	    continue;
     729  	}
     730  
     731        /* +user */
     732        if (result->sp_namp[0] == '+' && result->sp_namp[1] != '\0'
     733  	  && result->sp_namp[1] != '@')
     734  	{
     735  	  if (strcmp (name, &result->sp_namp[1]) == 0)
     736  	    {
     737  	      enum nss_status status;
     738  
     739  	      status = getspnam_plususer (name, result, ent,
     740  					  buffer, buflen, errnop);
     741  
     742  	      if (status == NSS_STATUS_RETURN)
     743  		/* We couldn't parse the entry */
     744  		return NSS_STATUS_NOTFOUND;
     745  	      else
     746  		return status;
     747  	    }
     748  	}
     749  
     750        /* +:... */
     751        if (result->sp_namp[0] == '+' && result->sp_namp[1] == '\0')
     752  	{
     753  	  enum nss_status status;
     754  
     755  	  status = getspnam_plususer (name, result, ent,
     756  				      buffer, buflen, errnop);
     757  
     758  	  if (status == NSS_STATUS_SUCCESS)
     759  	    /* We found the entry. */
     760              break;
     761            else if (status == NSS_STATUS_RETURN)
     762  	    /* We couldn't parse the entry */
     763              return NSS_STATUS_NOTFOUND;
     764  	  else
     765  	    return status;
     766  	}
     767      }
     768    return NSS_STATUS_SUCCESS;
     769  }
     770  
     771  
     772  enum nss_status
     773  _nss_compat_getspnam_r (const char *name, struct spwd *pwd,
     774  			char *buffer, size_t buflen, int *errnop)
     775  {
     776    enum nss_status result;
     777    ent_t ent = { false, true, false, NSS_STATUS_SUCCESS, NULL, { NULL, 0, 0},
     778  		{ NULL, NULL, 0, 0, 0, 0, 0, 0, 0}};
     779  
     780    if (name[0] == '-' || name[0] == '+')
     781      return NSS_STATUS_NOTFOUND;
     782  
     783    __libc_lock_lock (lock);
     784  
     785    if (ni == NULL)
     786      init_nss_interface ();
     787  
     788    __libc_lock_unlock (lock);
     789  
     790    result = internal_setspent (&ent, 0, 0);
     791  
     792    if (result == NSS_STATUS_SUCCESS)
     793      result = internal_getspnam_r (name, pwd, &ent, buffer, buflen, errnop);
     794  
     795    internal_endspent_noerror (&ent);
     796  
     797    return result;
     798  }
     799  
     800  
     801  /* Support routines for remembering -@netgroup and -user entries.
     802     The names are stored in a single string with `|' as separator. */
     803  static void
     804  blacklist_store_name (const char *name, ent_t *ent)
     805  {
     806    int namelen = strlen (name);
     807    char *tmp;
     808  
     809    /* first call, setup cache */
     810    if (ent->blacklist.size == 0)
     811      {
     812        ent->blacklist.size = MAX (BLACKLIST_INITIAL_SIZE, 2 * namelen);
     813        ent->blacklist.data = malloc (ent->blacklist.size);
     814        if (ent->blacklist.data == NULL)
     815  	return;
     816        ent->blacklist.data[0] = '|';
     817        ent->blacklist.data[1] = '\0';
     818        ent->blacklist.current = 1;
     819      }
     820    else
     821      {
     822        if (in_blacklist (name, namelen, ent))
     823  	return;			/* no duplicates */
     824  
     825        if (ent->blacklist.current + namelen + 1 >= ent->blacklist.size)
     826  	{
     827  	  ent->blacklist.size += MAX (BLACKLIST_INCREMENT, 2 * namelen);
     828  	  tmp = realloc (ent->blacklist.data, ent->blacklist.size);
     829  	  if (tmp == NULL)
     830  	    {
     831  	      free (ent->blacklist.data);
     832  	      ent->blacklist.size = 0;
     833  	      return;
     834  	    }
     835  	  ent->blacklist.data = tmp;
     836  	}
     837      }
     838  
     839    tmp = stpcpy (ent->blacklist.data + ent->blacklist.current, name);
     840    *tmp++ = '|';
     841    *tmp = '\0';
     842    ent->blacklist.current += namelen + 1;
     843  
     844    return;
     845  }
     846  
     847  
     848  /* Returns whether ent->blacklist contains name.  */
     849  static bool
     850  in_blacklist (const char *name, int namelen, ent_t *ent)
     851  {
     852    char buf[namelen + 3];
     853    char *cp;
     854  
     855    if (ent->blacklist.data == NULL)
     856      return false;
     857  
     858    buf[0] = '|';
     859    cp = stpcpy (&buf[1], name);
     860    *cp++ = '|';
     861    *cp = '\0';
     862    return strstr (ent->blacklist.data, buf) != NULL;
     863  }