Imported Upstream version 1.8.7
[debian/sudo] / compat / getgrouplist.c
index 2d22714f4c1278cafc3b837532eaf332c065dfdf..c37ddb6e781ed4deef0b60c9a241b1c0fc5a0e33 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010 Todd C. Miller <Todd.Miller@courtesan.com>
+ * Copyright (c) 2010, 2011, 2013 Todd C. Miller <Todd.Miller@courtesan.com>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -16,6 +16,8 @@
 
 #include <config.h>
 
+#ifndef HAVE_GETGROUPLIST
+
 #include <sys/types.h>
 #include <stdio.h>
 #ifdef STDC_HEADERS
 # include <strings.h>
 #endif /* HAVE_STRINGS_H */
 #include <grp.h>
+#ifdef HAVE_NSS_SEARCH
+# include <limits.h>
+# include <nsswitch.h>
+# ifdef HAVE_NSS_DBDEFS_H
+#  include <nss_dbdefs.h>
+# else
+#  include "compat/nss_dbdefs.h"
+# endif
+#endif
 
 #include "missing.h"
 
-#ifdef HAVE_GETGRSET
+#if defined(HAVE_GETGRSET)
 /*
  * BSD-compatible getgrouplist(3) using getgrset(3)
  */
@@ -70,7 +81,7 @@ getgrouplist(const char *name, gid_t basegid, gid_t *groups, int *ngroupsp)
     rval = 0;
 
 done:
-    efree(grset);
+    free(grset);
 #ifdef HAVE_SETAUTHDB
     aix_restoreauthdb();
 #endif
@@ -79,7 +90,179 @@ done:
     return rval;
 }
 
-#else /* HAVE_GETGRSET */
+#elif defined(HAVE_NSS_SEARCH)
+
+#ifndef GID_MAX
+# define GID_MAX       UID_MAX
+#endif
+
+#ifndef ALIGNBYTES
+# define ALIGNBYTES    (sizeof(long) - 1L)
+#endif
+#ifndef ALIGN
+# define ALIGN(p)      (((unsigned long)(p) + ALIGNBYTES) & ~ALIGNBYTES)
+#endif
+
+extern void _nss_initf_group(nss_db_params_t *);
+
+/*
+ * Convert a groups file string (instr) to a struct group (ent) using
+ * buf for storage.  
+ */
+static int
+str2grp(const char *instr, int inlen, void *ent, char *buf, int buflen)
+{
+    struct group *grp = ent;
+    char *cp, *ep, *fieldsep = buf;
+    char **gr_mem, **gr_end;
+    int yp = 0;
+    unsigned long gid;
+
+    /* Must at least have space to copy instr -> buf. */
+    if (inlen >= buflen)
+       return NSS_STR_PARSE_ERANGE;
+
+    /* Paranoia: buf and instr should be distinct. */
+    if (buf != instr) {
+       memmove(buf, instr, inlen);
+       buf[inlen] = '\0';
+    }
+
+    if ((fieldsep = strchr(cp = fieldsep, ':')) == NULL)
+       return NSS_STR_PARSE_PARSE;
+    *fieldsep++ = '\0';
+    grp->gr_name = cp;
+
+    /* Check for YP inclusion/exclusion entries. */
+    if (*cp == '+' || *cp == '-') {
+       /* Only the name is required for YP inclusion/exclusion entries. */
+       grp->gr_passwd = "";
+       grp->gr_gid = 0;
+       grp->gr_mem = NULL;
+       yp = 1;
+    }
+
+    if ((fieldsep = strchr(cp = fieldsep, ':')) == NULL)
+       return yp ? NSS_STR_PARSE_SUCCESS : NSS_STR_PARSE_PARSE;
+    *fieldsep++ = '\0';
+    grp->gr_passwd = cp;
+
+    if ((fieldsep = strchr(cp = fieldsep, ':')) == NULL)
+       return yp ? NSS_STR_PARSE_SUCCESS : NSS_STR_PARSE_PARSE;
+    *fieldsep++ = '\0';
+    gid = strtoul(cp, &ep, 10);
+    if (*cp == '\0' || *ep != '\0')
+       return yp ? NSS_STR_PARSE_SUCCESS : NSS_STR_PARSE_PARSE;
+#ifdef GID_NOBODY
+    if (*cp == '-' && gid != 0) {
+       /* Negative gids get mapped to nobody on Solaris. */
+       grp->gr_gid = GID_NOBODY;
+    } else
+#endif
+    if ((errno == ERANGE && gid == ULONG_MAX) ||
+       gid > GID_MAX || gid != (gid_t)gid) {
+       return NSS_STR_PARSE_ERANGE;
+    } else {
+       grp->gr_gid = (gid_t)gid;
+    }
+
+    /* Store group members, taking care to use proper alignment. */
+    grp->gr_mem = NULL;
+    if (*fieldsep != '\0') {
+       grp->gr_mem = gr_mem = (char **)ALIGN(buf + inlen + 1);
+       gr_end = (char **)((unsigned long)(buf + buflen) & ~ALIGNBYTES);
+       for (;;) {
+           if (gr_mem == gr_end)
+               return NSS_STR_PARSE_ERANGE;    /* out of space! */
+           *gr_mem++ = cp;
+           if (fieldsep == NULL)
+               break;
+           if ((fieldsep = strchr(cp = fieldsep, ',')) != NULL)
+               *fieldsep++ = '\0';
+       }
+       *gr_mem = NULL;
+    }
+    return NSS_STR_PARSE_SUCCESS;
+}
+
+static nss_status_t
+process_cstr(const char *instr, int inlen, struct nss_groupsbymem *gbm)
+{
+    const char *user = gbm->username;
+    nss_status_t rval = NSS_NOTFOUND;
+    nss_XbyY_buf_t *buf;
+    struct group *grp;
+    char **gr_mem;
+    int        error, i;
+
+    buf = _nss_XbyY_buf_alloc(sizeof(struct group), NSS_BUFLEN_GROUP);
+    if (buf == NULL)
+       return NSS_UNAVAIL;
+
+    /* Parse groups file string -> struct group. */
+    grp = buf->result;
+    error = (*gbm->str2ent)(instr, inlen, grp, buf->buffer, buf->buflen);
+    if (error || grp->gr_mem == NULL)
+       goto done;
+
+    for (gr_mem = grp->gr_mem; *gr_mem != NULL; gr_mem++) {
+       if (strcmp(*gr_mem, user) == 0) {
+           /* Append to gid_array unless gr_gid is a dupe. */
+           for (i = 0; i < gbm->numgids; i++) {
+               if (gbm->gid_array[i] == grp->gr_gid)
+                   goto done;                  /* already present */
+           }
+           /* Store gid if there is space. */
+           if (i < gbm->maxgids)
+               gbm->gid_array[i] = grp->gr_gid;
+           /* Always increment numgids so we can detect when out of space. */
+           gbm->numgids++;
+           goto done;
+       }
+    }
+done:
+    _nss_XbyY_buf_free(buf);
+    return rval;
+}
+
+/*
+ * BSD-compatible getgrouplist(3) using nss_search(3)
+ */
+int
+getgrouplist(const char *name, gid_t basegid, gid_t *groups, int *ngroupsp)
+{
+    struct nss_groupsbymem gbm;
+    static DEFINE_NSS_DB_ROOT(db_root);
+
+    /* We support BSD semantics where the first element is the base gid */
+    if (*ngroupsp <= 0)
+       return -1;
+    groups[0] = basegid;
+
+    memset(&gbm, 0, sizeof(gbm));
+    gbm.username = name;
+    gbm.gid_array = groups;
+    gbm.maxgids = *ngroupsp;
+    gbm.numgids = 1; /* for basegid */
+    gbm.force_slow_way = 1;
+    gbm.str2ent = str2grp;
+    gbm.process_cstr = process_cstr;
+
+    /*
+     * Can't use nss_search return value since it may return NSS_UNAVAIL
+     * when no nsswitch.conf entry (e.g. compat mode).
+     */
+    (void)nss_search(&db_root, _nss_initf_group, NSS_DBOP_GROUP_BYMEMBER, &gbm);
+
+    if (gbm.numgids <= gbm.maxgids) {
+        *ngroupsp = gbm.numgids;
+        return 0;
+    }
+    *ngroupsp = gbm.maxgids;
+    return -1;
+}
+
+#else /* !HAVE_GETGRSET && !HAVE__GETGROUPSBYMEMBER */
 
 /*
  * BSD-compatible getgrouplist(3) using getgrent(3)
@@ -128,4 +311,5 @@ done:
 
     return rval;
 }
-#endif /* HAVE_GETGRSET */
+#endif /* !HAVE_GETGRSET && !HAVE__GETGROUPSBYMEMBER */
+#endif /* HAVE_GETGROUPLIST */