Blob Blame History Raw
/*
   netgroup.c - NSS lookup functions for netgroup entries

   Copyright (C) 2006 West Consulting
   Copyright (C) 2006-2015 Arthur de Jong
   Copyright (C) 2010 Symas Corporation

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
   02110-1301 USA
*/

#include "config.h"

#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include "prototypes.h"
#include "common.h"
#include "compat/attrs.h"
#include "common/set.h"

/* function for reading a single result entry */
static nss_status_t read_netgrent_line(TFILE *fp, struct __netgrent *result,
                                       char *buffer, size_t buflen, int *errnop)
{
  int32_t tmpint32;
  int type;
  size_t bufptr = 0;
  /* read netgroup type */
  READ_INT32(fp, type);
  if (type == NSLCD_NETGROUP_TYPE_NETGROUP)
  {
    /* the response is a reference to another netgroup */
    result->type = group_val;
    READ_BUF_STRING(fp, result->val.group);
    return NSS_STATUS_SUCCESS;
  }
  else if (type == NSLCD_NETGROUP_TYPE_TRIPLE)
  {
    /* the response is a host/user/domain triple */
    result->type = triple_val;
    /* read host and revert to NULL on empty string */
    READ_BUF_STRING(fp, result->val.triple.host);
#ifdef NSS_FLAVOUR_GLIBC
    if (result->val.triple.host[0] == '\0')
    {
      result->val.triple.host = NULL;
      bufptr--; /* free unused space */
    }
#endif /* NSS_FLAVOUR_GLIBC */
    /* read user and revert to NULL on empty string */
    READ_BUF_STRING(fp, result->val.triple.user);
#ifdef NSS_FLAVOUR_GLIBC
    if (result->val.triple.user[0] == '\0')
    {
      result->val.triple.user = NULL;
      bufptr--; /* free unused space */
    }
#endif /* NSS_FLAVOUR_GLIBC */
    /* read domain and revert to NULL on empty string */
    READ_BUF_STRING(fp, result->val.triple.domain);
#ifdef NSS_FLAVOUR_GLIBC
    if (result->val.triple.domain[0] == '\0')
    {
      result->val.triple.domain = NULL;
      bufptr--; /* free unused space */
    }
#endif /* NSS_FLAVOUR_GLIBC */
    return NSS_STATUS_SUCCESS;
  }
  else if (type == NSLCD_NETGROUP_TYPE_END)
    /* make NSS_NAME(getnetgrent_r)() indicate the end of the netgroup */
    return NSS_STATUS_RETURN;
  /* we got something unexpected */
  ERROR_OUT_NOSUCCESS(fp);
  return NSS_STATUS_UNAVAIL;
}

#ifdef NSS_FLAVOUR_GLIBC

/* thread-local file pointer to an ongoing request */
static TLS TFILE *netgrentfp;

/* start a request to get a netgroup by name */
nss_status_t NSS_NAME(setnetgrent)(const char *group,
                                   struct __netgrent UNUSED(*result))
{
  /* we cannot use NSS_SETENT() here because we have a parameter that is only
     available in this function */
  int32_t tmpint32;
  int errnocp;
  int *errnop = &errnocp;
  NSS_EXTRA_DEFS
  NSS_AVAILCHECK;
  /* check parameter */
  if ((group == NULL) || (group[0] == '\0'))
    return NSS_STATUS_UNAVAIL;
  /* open a new stream and write the request */
  NSLCD_REQUEST(netgrentfp, NSLCD_ACTION_NETGROUP_BYNAME,
                WRITE_STRING(netgrentfp, group));
  /* read response code */
  READ_RESPONSE_CODE(netgrentfp);
  SKIP_STRING(netgrentfp); /* netgroup name */
  return NSS_STATUS_SUCCESS;
}

/* get a single netgroup tuple from the stream */
nss_status_t NSS_NAME(getnetgrent_r)(struct __netgrent *result,
                                     char *buffer, size_t buflen, int *errnop)
{
  nss_status_t retv;
  NSS_EXTRA_DEFS;
  NSS_AVAILCHECK;
  NSS_BUFCHECK;
  /* check that we have a valid file descriptor */
  if (netgrentfp == NULL)
    return NSS_STATUS_UNAVAIL;
  /* prepare for buffer errors */
  tio_mark(netgrentfp);
  /* read a response */
  retv = read_netgrent_line(netgrentfp, result, buffer, buflen, errnop);
  /* check read result */
  if (retv == NSS_STATUS_TRYAGAIN)
  {
    /* if we have a full buffer try to reset the stream */
    if (tio_reset(netgrentfp))
    {
      /* reset failed, we close and give up with a permanent error
         because we cannot retry just the getent() call because it
         may not be only the first entry that failed */
      tio_close(netgrentfp);
      netgrentfp = NULL;
      *errnop = EINVAL;
      return NSS_STATUS_UNAVAIL;
    }
  }
  else if ((retv != NSS_STATUS_SUCCESS) && (retv != NSS_STATUS_RETURN))
    netgrentfp = NULL; /* file should be closed by now */
  return retv;
}

/* close the stream opened with setnetgrent() above */
nss_status_t NSS_NAME(endnetgrent)(struct __netgrent UNUSED(*result))
{
  NSS_ENDENT(netgrentfp);
}

#endif /* NSS_FLAVOUR_GLIBC */

#ifdef NSS_FLAVOUR_SOLARIS

/* this is the custom backend structure for the {set,get,end}ent() functions */
struct setnetgrent_backend {
  nss_backend_op_t *ops; /* function-pointer table */
  int n_ops;             /* number of function pointers */
  TFILE *fp;             /* the file pointer for {set,get,end}ent() functions */
  SET *seen_groups;      /* netgroups seen, for loop detection */
  SET *unseen_groups;    /* netgroups that need to be chased */
};

/* easy way to get sets from back-end */
#define NETGROUP_BE(be) ((struct setnetgrent_backend*)(be))

/* access arguments */
#define SETNETGRENT_ARGS(args) ((struct nss_setnetgrent_args *)(args))
#define GETNETGRENT_ARGS(args) ((struct nss_getnetgrent_args *)(args))
#define INNETGR_ARGS(ARGS)     ((struct nss_innetgr_args *)(args))

/* return a netgroup that has not been traversed (the caller should use
   free() to free it) */
static char *find_unseen_netgroup(struct setnetgrent_backend *be)
{
  char *group;
  while (1)
  {
    group = set_pop(be->unseen_groups);
    if (group == NULL)
      return NULL;
    if (!set_contains(be->seen_groups, group))
      return group;
    free(group);
  }
}

static nss_status_t start_netgroup_request(struct setnetgrent_backend *be,
                                           const char *group)
{
  /* we cannot use NSS_SETENT() here because we have a parameter that is only
     available in this function */
  int32_t tmpint32;
  int *errnop = &errno;
  /* check parameter */
  if ((group == NULL) || (group[0] == '\0'))
    return NSS_STATUS_UNAVAIL;
  set_add(be->seen_groups, group);
  /* open a new stream and write the request */
  NSLCD_REQUEST(NETGROUP_BE(be)->fp, NSLCD_ACTION_NETGROUP_BYNAME,
                WRITE_STRING(NETGROUP_BE(be)->fp, group));
  /* read response code */
  READ_RESPONSE_CODE(NETGROUP_BE(be)->fp);
  SKIP_STRING(NETGROUP_BE(be)->fp); /* netgroup name */
  return NSS_STATUS_SUCCESS;
}

static nss_status_t netgroup_setnetgrent_setnetgrent(nss_backend_t UNUSED(*be),
                                                     void UNUSED(*args))
{
  return NSS_STATUS_SUCCESS;
}

static nss_status_t netgroup_setnetgrent_getnetgrent(nss_backend_t *be,
                                                     void *args)
{
  struct __netgrent result;
  nss_status_t retv;
  /* check that we have a valid file descriptor */
  if (NETGROUP_BE(be)->fp == NULL)
    return NSS_STATUS_UNAVAIL;
  /* go over the result lines */
  while (1)
  {
    /* prepare for buffer errors */
    tio_mark(NETGROUP_BE(be)->fp);
    /* read single line from the netgroup information */
    retv = read_netgrent_line(NETGROUP_BE(be)->fp, &result, GETNETGRENT_ARGS(args)->buffer,
                              GETNETGRENT_ARGS(args)->buflen, &errno);
    /* check read result */
    if ((retv == NSS_STATUS_SUCCESS) && (result.type == group_val))
    {
      /* a netgroup nested within the current netgroup */
      set_add(NETGROUP_BE(be)->unseen_groups, result.val.group);
    }
    else if ((retv == NSS_STATUS_SUCCESS) && (result.type == triple_val))
    {
      /* a netgroup line we can return */
      GETNETGRENT_ARGS(args)->status = NSS_NETGR_FOUND;
      GETNETGRENT_ARGS(args)->retp[NSS_NETGR_MACHINE] = (char *)result.val.triple.host;
      GETNETGRENT_ARGS(args)->retp[NSS_NETGR_USER] = (char *)result.val.triple.user;
      GETNETGRENT_ARGS(args)->retp[NSS_NETGR_DOMAIN] = (char *)result.val.triple.domain;
      return NSS_STATUS_SUCCESS;
    }
    else if (retv == NSS_STATUS_TRYAGAIN)
    {
      /* we have a full buffer, try to reset the stream */
      if (tio_reset(NETGROUP_BE(be)->fp))
      {
        /* reset failed, we close and give up with a permanent error
           because we cannot retry just the getent() call because it
           may not be only the first entry that failed */
        tio_close(NETGROUP_BE(be)->fp);
        NETGROUP_BE(be)->fp = NULL;
        return NSS_STATUS_UNAVAIL;
      }
      GETNETGRENT_ARGS(args)->status = NSS_NETGR_NOMEM;
      return NSS_STATUS_TRYAGAIN;
    }
    else if (retv == NSS_STATUS_RETURN)
    {
      /* done with the current netgroup */
      tio_close(NETGROUP_BE(be)->fp);
      NETGROUP_BE(be)->fp = NULL;
      /* explore nested netgroups, if any */
      while (retv != NSS_STATUS_SUCCESS)
      {
        /* find a nested netgroup to pursue further */
        char *group = find_unseen_netgroup(NETGROUP_BE(be));
        if (group == NULL)
        {
          /* no more netgroups to explore */
          GETNETGRENT_ARGS(args)->status = NSS_NETGR_NO;
          return NSS_STATUS_SUCCESS;
        }
        /* start a new search with this netgroup */
        retv = start_netgroup_request(NETGROUP_BE(be), group);
        free(group);
      }
    }
    else
    {
      /* some error occurred when reading the line (stream should be closed by now) */
      NETGROUP_BE(be)->fp = NULL;
      GETNETGRENT_ARGS(args)->status = NSS_NETGR_NO;
      return retv;
    }
  }
}

static nss_status_t netgroup_setnetgrent_endnetgrent(nss_backend_t *be,
                                                     void UNUSED(*args))
{
  NSS_ENDENT(NETGROUP_BE(be)->fp);
}

static nss_status_t netgroup_setnetgrent_destructor(nss_backend_t *be,
                                                    void *UNUSED(args))
{
  struct setnetgrent_backend *ngbe = (struct setnetgrent_backend *)be;
  if (ngbe->fp != NULL)
    (void)tio_close(ngbe->fp);
  set_free(ngbe->seen_groups);
  set_free(ngbe->unseen_groups);
  free(ngbe);
  return NSS_STATUS_SUCCESS;
}

static nss_backend_op_t netgroup_setnetgrent_ops[] = {
  netgroup_setnetgrent_destructor,
  netgroup_setnetgrent_endnetgrent,
  netgroup_setnetgrent_setnetgrent,
  netgroup_setnetgrent_getnetgrent,
};

static nss_status_t netgroup_setnetgrent_constructor(nss_backend_t UNUSED(*be),
                                                     void *args)
{
  struct setnetgrent_backend *ngbe;
  nss_status_t retv;
  NSS_AVAILCHECK;
  SETNETGRENT_ARGS(args)->iterator = NULL; /* initialize */
  /* allocate a back-end specific to this request */
  ngbe = (struct setnetgrent_backend *)malloc(sizeof(struct setnetgrent_backend));
  if (ngbe == NULL)
    return NSS_STATUS_UNAVAIL;
  ngbe->ops = netgroup_setnetgrent_ops;
  ngbe->n_ops = sizeof(netgroup_setnetgrent_ops) / sizeof(nss_backend_op_t);
  ngbe->fp = NULL;
  ngbe->seen_groups = set_new();
  ngbe->unseen_groups = set_new();
  /* start the first search */
  retv = start_netgroup_request(ngbe, SETNETGRENT_ARGS(args)->netgroup);
  if (retv != NSS_STATUS_SUCCESS)
  {
    netgroup_setnetgrent_destructor((nss_backend_t *)ngbe, args);
    return retv;
  }
  /* return the new back-end */
  SETNETGRENT_ARGS(args)->iterator = (nss_backend_t *)ngbe;
  return NSS_STATUS_SUCCESS;
}

static nss_status_t netgroup_innetgr(nss_backend_t UNUSED(*be),
                                     void *args)
{
  unsigned int i;
  nss_status_t res = NSS_SUCCESS;
  struct nss_setnetgrent_args set_args;
  struct nss_getnetgrent_args get_args;
  const char *host = NULL, *user = NULL, *domain = NULL;
  /* get the host, user and domain arguments */
  if ((args == NULL) ||
      (INNETGR_ARGS(args)->arg[NSS_NETGR_MACHINE].argc > 1) ||
      (INNETGR_ARGS(args)->arg[NSS_NETGR_USER].argc > 1) ||
      (INNETGR_ARGS(args)->arg[NSS_NETGR_DOMAIN].argc > 1))
    return NSS_STATUS_UNAVAIL;
  if (INNETGR_ARGS(args)->arg[NSS_NETGR_MACHINE].argc == 1)
    host = INNETGR_ARGS(args)->arg[NSS_NETGR_MACHINE].argv[0];
  if (INNETGR_ARGS(args)->arg[NSS_NETGR_USER].argc == 1)
    user = INNETGR_ARGS(args)->arg[NSS_NETGR_USER].argv[0];
  if (INNETGR_ARGS(args)->arg[NSS_NETGR_DOMAIN].argc == 1)
    domain = INNETGR_ARGS(args)->arg[NSS_NETGR_DOMAIN].argv[0];
  /* go over the list of provided groups */
  INNETGR_ARGS(args)->status = NSS_NETGR_NO;
  for (i = 0; i < INNETGR_ARGS(args)->groups.argc; i++)
  {
    /* prepare calling {set,get,end}netgrent() */
    set_args.netgroup = INNETGR_ARGS(args)->groups.argv[i];
    res = netgroup_setnetgrent_constructor(NULL, &set_args);
    if (res != NSS_SUCCESS)
      break;
    /* we skip setnetgrent because it does nothing in our case */
    /* call getnetgrent until we find an error, no more or a match */
    while (1)
    {
      res = netgroup_setnetgrent_getnetgrent(set_args.iterator, &get_args);
      /* see if we have an error or are at the end of the results */
      if ((res != NSS_SUCCESS) || (get_args.status != NSS_NETGR_FOUND))
        break;
      /* see if we have a match */
      if (((host == NULL) || (strcmp(host, get_args.retp[NSS_NETGR_MACHINE]) == 0)) &&
          ((user == NULL) || (strcmp(user, get_args.retp[NSS_NETGR_USER]) == 0)) &&
          ((domain == NULL) || (strcmp(domain, get_args.retp[NSS_NETGR_DOMAIN]) == 0)))
      {
        INNETGR_ARGS(args)->status = NSS_NETGR_FOUND;
        break;
      }
    }
    (void)netgroup_setnetgrent_endnetgrent(set_args.iterator, NULL);
    (void)netgroup_setnetgrent_destructor(set_args.iterator, NULL);
    if (res != NSS_SUCCESS)
      break;
    /* check if we have a match */
    if (INNETGR_ARGS(args)->status == NSS_NETGR_FOUND)
      break;
  }
  return res;
}

static nss_backend_op_t netgroup_ops[] = {
  nss_ldap_destructor,
  NULL,
  NULL,
  NULL,
  netgroup_innetgr,
  netgroup_setnetgrent_constructor
};

nss_backend_t *NSS_NAME(netgroup_constr)(const char UNUSED(*db_name),
                                         const char UNUSED(*src_name),
                                         const char UNUSED(*cfg_args))
{
  return nss_ldap_constructor(netgroup_ops, sizeof(netgroup_ops));
}

#endif /* NSS_FLAVOUR_SOLARIS */