1  /* Common code for DB-based databases in nss_db module.
       2     Copyright (C) 1996-2023 Free Software Foundation, Inc.
       3     This file is part of the GNU C Library.
       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 <dlfcn.h>
      20  #include <fcntl.h>
      21  #include <stdint.h>
      22  #include <sys/mman.h>
      23  #include <libc-lock.h>
      24  #include "nsswitch.h"
      25  #include "nss_db.h"
      26  
      27  /* The hashing function we use.  */
      28  #include "../intl/hash-string.h"
      29  
      30  /* These symbols are defined by the including source file:
      31  
      32     ENTNAME -- database name of the structure and functions (hostent, pwent).
      33     STRUCTURE -- struct name, define only if not ENTNAME (passwd, group).
      34     DATABASE -- database file name, ("hosts", "passwd")
      35  
      36     NEED_H_ERRNO - defined iff an arg `int *herrnop' is used.
      37  */
      38  
      39  #define ENTNAME_r	CONCAT(ENTNAME,_r)
      40  
      41  #include <paths.h>
      42  #define	DBFILE		_PATH_VARDB DATABASE ".db"
      43  
      44  #ifdef NEED_H_ERRNO
      45  # define H_ERRNO_PROTO	, int *herrnop
      46  # define H_ERRNO_ARG	, herrnop
      47  # define H_ERRNO_SET(val) (*herrnop = (val))
      48  #else
      49  # define H_ERRNO_PROTO
      50  # define H_ERRNO_ARG
      51  # define H_ERRNO_SET(val) ((void) 0)
      52  #endif
      53  
      54  /* State for this database.  */
      55  static struct nss_db_map state;
      56  /* Lock to protect the state and global variables.  */
      57  __libc_lock_define (static , lock);
      58  
      59  /* Maintenance of the shared handle open on the database.  */
      60  static int keep_db;
      61  static const char *entidx;
      62  
      63  
      64  /* Open the database.  */
      65  enum nss_status
      66  CONCAT(_nss_db_set,ENTNAME) (int stayopen)
      67  {
      68    enum nss_status status;
      69  
      70    __libc_lock_lock (lock);
      71  
      72    status = internal_setent (DBFILE, &state);
      73  
      74    if (status == NSS_STATUS_SUCCESS)
      75      {
      76        /* Remember STAYOPEN flag.  */
      77        keep_db |= stayopen;
      78  
      79        /* Reset the sequential index.  */
      80        entidx  = NULL;
      81      }
      82  
      83    __libc_lock_unlock (lock);
      84  
      85    return status;
      86  }
      87  
      88  
      89  /* Close it again.  */
      90  enum nss_status
      91  CONCAT(_nss_db_end,ENTNAME) (void)
      92  {
      93    __libc_lock_lock (lock);
      94  
      95    internal_endent (&state);
      96  
      97    /* Reset STAYOPEN flag.  */
      98    keep_db = 0;
      99  
     100    __libc_lock_unlock (lock);
     101  
     102    return NSS_STATUS_SUCCESS;
     103  }
     104  
     105  
     106  /* Macro for defining lookup functions for this DB-based database.
     107  
     108     NAME is the name of the lookup; e.g. `pwnam'.
     109  
     110     DB_CHAR is index indicator for the database.
     111  
     112     KEYPATTERN gives `printf' args to construct a key string;
     113     e.g. `("%d", id)'.
     114  
     115     KEYSIZE gives the allocation size of a buffer to construct it in;
     116     e.g. `1 + sizeof (id) * 4'.
     117  
     118     PROTO is the potentially empty list of other parameters.
     119  
     120     BREAK_IF_MATCH is a block of code which compares `struct STRUCTURE *result'
     121     to the lookup key arguments and does `break;' if they match.  */
     122  
     123  #define DB_LOOKUP(name, db_char, keysize, keypattern, break_if_match, proto...)\
     124  enum nss_status								      \
     125   _nss_db_get##name##_r (proto, struct STRUCTURE *result,		      \
     126  			char *buffer, size_t buflen, int *errnop H_ERRNO_PROTO)\
     127  {									      \
     128    struct parser_data *data = (void *) buffer;				      \
     129  									      \
     130    if (buflen < sizeof *data)						      \
     131      {									      \
     132        *errnop = ERANGE;							      \
     133        H_ERRNO_SET (NETDB_INTERNAL);					      \
     134        return NSS_STATUS_TRYAGAIN;					      \
     135      }									      \
     136  									      \
     137    struct nss_db_map state = { NULL, 0 };				      \
     138    enum nss_status status = internal_setent (DBFILE, &state);		      \
     139    if (status != NSS_STATUS_SUCCESS)					      \
     140      {									      \
     141        *errnop = errno;							      \
     142        H_ERRNO_SET (NETDB_INTERNAL);					      \
     143        return status;							      \
     144      }									      \
     145  									      \
     146    const struct nss_db_header *header = state.header;			      \
     147    int i;								      \
     148    for (i = 0; i < header->ndbs; ++i)					      \
     149      if (header->dbs[i].id == db_char)					      \
     150        break;								      \
     151    if (i == header->ndbs)						      \
     152      {									      \
     153        status = NSS_STATUS_UNAVAIL;					      \
     154        goto out;								      \
     155      }									      \
     156  									      \
     157    char *key;								      \
     158    if (db_char == '.')							      \
     159      key = (char *) IGNOREPATTERN keypattern;				      \
     160    else									      \
     161      {									      \
     162        const size_t size = (keysize) + 1;				      \
     163        key = alloca (size);						      \
     164  									      \
     165        KEYPRINTF keypattern;						      \
     166      }									      \
     167  									      \
     168    const stridx_t *hashtable						      \
     169      = (const stridx_t *) ((const char *) header				      \
     170  			  + header->dbs[i].hashoffset);			      \
     171    const char *valstrtab = (const char *) header + header->valstroffset;	      \
     172    uint32_t hashval = __hash_string (key);				      \
     173    size_t hidx = hashval % header->dbs[i].hashsize;			      \
     174    size_t hval2 = 1 + hashval % (header->dbs[i].hashsize - 2);		      \
     175  									      \
     176    status = NSS_STATUS_NOTFOUND;						      \
     177    while (hashtable[hidx] != ~((stridx_t) 0))				      \
     178      {									      \
     179        const char *valstr = valstrtab + hashtable[hidx];			      \
     180        size_t len = strlen (valstr) + 1;					      \
     181        if (len > buflen)							      \
     182  	{								      \
     183  	  /* No room to copy the data to.  */				      \
     184  	  *errnop = ERANGE;						      \
     185  	  H_ERRNO_SET (NETDB_INTERNAL);					      \
     186  	  status = NSS_STATUS_TRYAGAIN;					      \
     187  	  break;							      \
     188  	}								      \
     189  									      \
     190        /* Copy the string to a place where it can be modified.  */	      \
     191        char *p = memcpy (buffer, valstr, len);				      \
     192  									      \
     193        int err = parse_line (p, result, data, buflen, errnop EXTRA_ARGS);      \
     194  									      \
     195        /* Advance before break_if_match, lest it uses continue to skip
     196  	 to the next entry.  */						      \
     197        if ((hidx += hval2) >= header->dbs[i].hashsize)			      \
     198  	hidx -= header->dbs[i].hashsize;				      \
     199  									      \
     200        if (err > 0)							      \
     201  	{								      \
     202  	  status = NSS_STATUS_SUCCESS;					      \
     203  	  break_if_match;						      \
     204  	  status = NSS_STATUS_NOTFOUND;					      \
     205  	}								      \
     206        else if (err == -1)						      \
     207  	{								      \
     208  	  H_ERRNO_SET (NETDB_INTERNAL);					      \
     209  	  status = NSS_STATUS_TRYAGAIN;					      \
     210  	  break;							      \
     211  	}								      \
     212      }									      \
     213  									      \
     214    if (status == NSS_STATUS_NOTFOUND)					      \
     215      H_ERRNO_SET (HOST_NOT_FOUND);					      \
     216  									      \
     217   out:									      \
     218    internal_endent (&state);						      \
     219  									      \
     220    return status;							      \
     221  }
     222  
     223  #define KEYPRINTF(pattern, args...) snprintf (key, size, pattern ,##args)
     224  #define IGNOREPATTERN(pattern, arg1, args...) (char *) (uintptr_t) arg1
     225  
     226  
     227  
     228  
     229  /* Return the next entry from the database file, doing locking.  */
     230  enum nss_status
     231  CONCAT(_nss_db_get,ENTNAME_r) (struct STRUCTURE *result, char *buffer,
     232  			       size_t buflen, int *errnop H_ERRNO_PROTO)
     233  {
     234    /* Return next entry in host file.  */
     235    enum nss_status status;
     236    struct parser_data *data = (void *) buffer;
     237  
     238    if (buflen < sizeof *data)
     239      {
     240        *errnop = ERANGE;
     241        H_ERRNO_SET (NETDB_INTERNAL);
     242        return NSS_STATUS_TRYAGAIN;
     243      }
     244  
     245    __libc_lock_lock (lock);
     246  
     247    if (state.header == NULL)
     248      {
     249        status = internal_setent (DBFILE, &state);
     250        if (status != NSS_STATUS_SUCCESS)
     251  	{
     252  	  *errnop = errno;
     253  	  H_ERRNO_SET (NETDB_INTERNAL);
     254  	  goto out;
     255  	}
     256        entidx = NULL;
     257      }
     258  
     259    /* Start from the beginning if freshly initialized or reset
     260       requested by set*ent.  */
     261    if (entidx == NULL)
     262      entidx = (const char *) state.header + state.header->valstroffset;
     263  
     264    status = NSS_STATUS_UNAVAIL;
     265    if (state.header != MAP_FAILED)
     266      {
     267        const char *const end = ((const char *) state.header
     268  			       + state.header->valstroffset
     269  			       + state.header->valstrlen);
     270        while (entidx < end)
     271  	{
     272  	  const char *next = strchr (entidx, '\0') + 1;
     273  	  size_t len = next - entidx;
     274  
     275  	  if (len > buflen)
     276  	    {
     277  	      /* No room to copy the data to.  */
     278  	      *errnop = ERANGE;
     279  	      H_ERRNO_SET (NETDB_INTERNAL);
     280  	      status = NSS_STATUS_TRYAGAIN;
     281  	      break;
     282  	    }
     283  
     284  	  /* Copy the string to a place where it can be modified.  */
     285  	  char *p = memcpy (buffer, entidx, len);
     286  
     287  	  int err = parse_line (p, result, data, buflen, errnop EXTRA_ARGS);
     288  
     289  	  if (err > 0)
     290  	    {
     291  	      status = NSS_STATUS_SUCCESS;
     292  	      entidx = next;
     293  	      break;
     294  	    }
     295  	  if (err < 0)
     296  	    {
     297  	      H_ERRNO_SET (NETDB_INTERNAL);
     298  	      status = NSS_STATUS_TRYAGAIN;
     299  	      break;
     300  	    }
     301  
     302  	  /* Continue with the next record, this one is ill-formed.  */
     303  	  entidx = next;
     304  	}
     305      }
     306  
     307   out:
     308    __libc_lock_unlock (lock);
     309  
     310    return status;
     311  }