root/usr/src/lib/libpkg/common/ncgrpw.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */


/*
 * This module fetches group and passwd structs for the caller. It
 * uses a hash table to speed up retrieval of repeated entries. If
 * the attempts to initialize the hash tables fail, this just
 * continues the slow way.
 */

#include <pwd.h>
#include <grp.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "pkglib.h"
#include "pkglocale.h"
#include "nhash.h"

#define HASHSIZE        151
#define BSZ             4

#define ERR_DUPFAIL     "%s: strdup(%s) failed.\n"
#define ERR_ADDFAIL     "%s: add_cache() failed.\n"
#define ERR_BADMEMB     "%s: %s in \"%s\" %s structure is invalid.\n"
#define ERR_NOGRP       "dup_gr_ent(): no group entry provided.\n"
#define ERR_NOPWD       "dup_pw_ent(): no passwd entry provided.\n"
#define ERR_NOINIT      "%s: init_cache() failed.\n"
#define ERR_MALLOC      "%s: malloc(%d) failed for %s.\n"

static Cache *pwnam_cache = (Cache *) NULL;
static Cache *grnam_cache = (Cache *) NULL;
static Cache *pwuid_cache = (Cache *) NULL;
static Cache *grgid_cache = (Cache *) NULL;

static int dup_gr_ent(struct group *grp);
static int dup_pw_ent(struct passwd *pwp);

/*
 * These indicate whether the hash table has been initialized for the four
 * categories.
 */
static int is_a_pwnam_cache;
static int is_a_grnam_cache;
static int is_a_pwuid_cache;
static int is_a_grgid_cache;

extern char *get_install_root(void);

/*
 * If there's a grnam cache, then update it with this new
 * group, otherwise, skip it.
 */
static Item *
cache_alloc(char *fname, int len, size_t struct_size)
{
        Item *itemp;

        /*
         * Allocate space for the Item pointer, key and data.
         */
        if ((itemp = (Item *) malloc(sizeof (*itemp))) ==
            Null_Item) {
                (void) fprintf(stderr,
                    pkg_gt(ERR_MALLOC), fname,
                    sizeof (*itemp), "itemp");
        } else if ((itemp->key = (char *)malloc(len)) == NULL) {
                (void) fprintf(stderr, pkg_gt(ERR_MALLOC), fname, len,
                    "itemp->key");
                free(itemp);
        } else if ((itemp->data = malloc(struct_size)) == NULL) {
                (void) fprintf(stderr, pkg_gt(ERR_MALLOC), fname,
                    struct_size, "itemp->data");
                free(itemp->key);
                free(itemp);
        } else {
                /* Set length parameters. */
                itemp->keyl = len;
                itemp->datal = struct_size;

                return (itemp);
        }

        return ((Item *) NULL);
}

/* Get the required group structure based upon the group name. */
struct group *
cgrnam(char *nam)
{
        struct group *grp;
        Item *itemp;
        int len;
        static int cache_failed;

        /* Attempt to initialize the grname cache. */
        if (!is_a_grnam_cache && !cache_failed) {
                if (init_cache(&grnam_cache, HASHSIZE, BSZ,
                    (int (*)())NULL, (int (*)())NULL) == -1) {
                        (void) fprintf(stderr, pkg_gt(ERR_NOINIT), "cgrnam()");
                        grnam_cache = (Cache *) NULL;
                        cache_failed = 1;
                } else
                        is_a_grnam_cache = 1;
        }

        len = strlen(nam) + 1;

        /* First look in the cache. Failing that, do it the hard way. */
        if ((itemp = lookup_cache(grnam_cache, nam, len)) == Null_Item) {

                /* Get the group by name. */
                if ((grp = clgrnam(nam)) != NULL ||
                                (grp = getgrnam(nam)) != NULL) {
                        /* A group by that name exists on this machine. */
                        if (dup_gr_ent(grp))
                                /*
                                 * Effectively no such group since struct
                                 * couldn't be duplicated.
                                 */
                                grp = (struct group *)NULL;
                        /*
                         * If there's a grnam cache, then update it with this
                         * new group, otherwise, skip it.
                         */
                        else if (is_a_grnam_cache) {
                                if ((itemp = cache_alloc("cgrnam()", len,
                                    sizeof (struct group))) != Null_Item) {
                                        /*
                                         * With that allocated, insert the
                                         * group name as key and set the key
                                         * length.
                                         */
                                        (void) memmove(itemp->key, nam, len);

                                        /*
                                         * Insert the data associated with
                                         * the key and the data length.
                                         */
                                        (void) memmove(itemp->data, grp,
                                            sizeof (struct group));

                                        /* Insert the Item into the cache. */
                                        if (add_cache(grnam_cache, itemp) == -1)
                                                (void) fprintf(stderr,
                                                    pkg_gt(ERR_ADDFAIL),
                                                    "cgrnam()");
                                }
                        }
                }
                return (grp);
        } else  /* Found it in the cache. */
                return ((struct group *)itemp->data);
}

struct passwd *
cpwnam(char *nam)
{
        struct passwd *pwd;
        Item *itemp;
        int len;
        static int cache_failed;

        if (!is_a_pwnam_cache && !cache_failed) {
                if (init_cache(&pwnam_cache, HASHSIZE, BSZ,
                    (int (*)())NULL, (int (*)())NULL) == -1) {
                        (void) fprintf(stderr, pkg_gt(ERR_NOINIT), "cpwnam()");
                        pwnam_cache = (Cache *) NULL;
                        cache_failed = 1;
                } else
                        is_a_pwnam_cache = 1;
        }

        len = strlen(nam) + 1;

        /* First look in the cache. Failing that, do it the hard way. */
        if ((itemp = lookup_cache(pwnam_cache, nam, len)) == Null_Item) {

                /* Get the passwd by name. */
                if ((pwd = clpwnam(nam)) != NULL ||
                                (pwd = getpwnam(nam)) != NULL) {
                        /* A passwd by that name exists on this machine. */
                        if (dup_pw_ent(pwd))
                                /*
                                 * Effectively no such passwd since struct
                                 * couldn't be duplicated.
                                 */
                                pwd = (struct passwd *)NULL;
                        /*
                         * If there's a pwnam cache, then update it with this
                         * new passwd, otherwise, skip it.
                         */
                        else if (is_a_pwnam_cache) {
                                /*
                                 * Allocate space for the Item pointer, key
                                 * and data.
                                 */
                                if ((itemp = cache_alloc("cpwnam()", len,
                                    sizeof (struct passwd))) != Null_Item) {
                                        /*
                                         * With that allocated, insert the
                                         * group name as key and set the key
                                         * length.
                                         */
                                        (void) memmove(itemp->key, nam, len);

                                        /*
                                         * Insert the data associated with
                                         * the key and the data length.
                                         */
                                        (void) memmove(itemp->data, pwd,
                                            sizeof (struct passwd));

                                        if (add_cache(pwnam_cache, itemp) == -1)
                                                (void) fprintf(stderr,
                                                    pkg_gt(ERR_ADDFAIL),
                                                    "cpwnam()");
                                }
                        }
                }
                return (pwd);
        } else  /* Found it in the cache. */
                return ((struct passwd *)itemp->data);
}

static int
uid_hash(void *datap, int datalen, int hsz)
{
#ifdef lint
        int i = datalen;
        datalen = i;
#endif  /* lint */

        return (*((uid_t *)datap) % hsz);
}

static int
uid_comp(void *datap1, void *datap2, int datalen)
{
#ifdef lint
        int i = datalen;
        datalen = i;
#endif  /* lint */

        return (*((uid_t *)datap1) - *((uid_t *)datap2));
}

struct group *
cgrgid(gid_t gid)
{
        struct group *grp;
        Item *itemp;
        int len;
        static int cache_failed;

        if (!is_a_grgid_cache && !cache_failed) {
                if (init_cache(&grgid_cache, HASHSIZE, BSZ,
                    uid_hash, uid_comp) == -1) {
                        (void) fprintf(stderr, pkg_gt(ERR_NOINIT), "cgrgid()");
                        grgid_cache = (Cache *) NULL;
                        cache_failed = 1;
                } else
                        is_a_grgid_cache = 1;
        }

        len = sizeof (uid_t);

        /* First look in the cache. Failing that, do it the hard way. */
        if ((itemp = lookup_cache(grgid_cache, &gid, len)) == Null_Item) {
                if ((grp = clgrgid(gid)) != NULL ||
                                (grp = getgrgid(gid)) != NULL) {
                        /* A group by that number exists on this machine. */
                        if (dup_gr_ent(grp))
                                /*
                                 * Effectively no such group since struct
                                 * couldn't be duplicated.
                                 */
                                grp = (struct group *)NULL;
                        /*
                         * If there's a grnam cache, then update it with this
                         * new group, otherwise, skip it.
                         */
                        else if (is_a_grgid_cache) {
                                if ((itemp = cache_alloc("cgrgid()", len,
                                    sizeof (struct group))) != Null_Item) {
                                        /*
                                         * With that allocated, insert the
                                         * group name as key and set the key
                                         * length.
                                         */
                                        (void) memmove(itemp->key, &gid, len);

                                        /*
                                         * Insert the data associated with
                                         * the key and the data length.
                                         */
                                        (void) memmove(itemp->data, grp,
                                            sizeof (struct group));

                                        if (add_cache(grgid_cache, itemp) == -1)
                                                (void) fprintf(stderr,
                                                    pkg_gt(ERR_ADDFAIL),
                                                    "cgrgid()");
                                }
                        }
                }
                return (grp);
        } else  /* Found it in the cache. */
                return ((struct group *)itemp->data);
}

struct passwd *
cpwuid(uid_t uid)
{
        struct passwd *pwd;
        Item *itemp;
        int len;
        static int cache_failed;

        if (!is_a_pwuid_cache && !cache_failed) {
                if (init_cache(&pwuid_cache, HASHSIZE, BSZ,
                    uid_hash, uid_comp) == -1) {
                        (void) fprintf(stderr,
                            pkg_gt(ERR_NOINIT), "cpwuid()");
                        pwuid_cache = (Cache *) NULL;
                        cache_failed = 1;
                } else
                        is_a_pwuid_cache = 1;
        }

        len = sizeof (uid_t);

        /* First look in the cache. Failing that, do it the hard way. */
        if ((itemp = lookup_cache(pwuid_cache, &uid, len)) == Null_Item) {

                /* Get the passwd by number. */
                if ((pwd = clpwuid(uid)) != NULL ||
                                (pwd = getpwuid(uid)) != NULL) {
                        /* A passwd by that user ID exists on this machine. */
                        if (dup_pw_ent(pwd))
                                /*
                                 * Effectively no such passwd since struct
                                 * couldn't be duplicated.
                                 */
                                pwd = (struct passwd *)NULL;
                        /*
                         * If there's a pwuid cache, then update it with this
                         * new passwd, otherwise, skip it.
                         */
                        else if (is_a_pwuid_cache) {
                                if ((itemp = cache_alloc("cpwuid()", len,
                                    sizeof (struct passwd))) != Null_Item) {
                                        /*
                                         * With that allocated, insert the
                                         * group name as key and set the key
                                         * length.
                                         */
                                        (void) memmove(itemp->key, &uid, len);

                                        /*
                                         * Insert the data associated with
                                         * the key and the data length.
                                         */
                                        (void) memmove(itemp->data, pwd,
                                            sizeof (struct passwd));

                                        if (add_cache(pwuid_cache, itemp) == -1)
                                                (void) fprintf(stderr,
                                                    pkg_gt(ERR_ADDFAIL),
                                                    "cpwuid()");
                                }
                        }
                }
                return (pwd);
        } else  /* Found it in the cache. */
                return ((struct passwd *)itemp->data);
}

/*
 * This function duplicates the group structure provided from kernel static
 * memory. There is a lot of defensive coding here because there have been
 * problems with the getgr*() functions. They will sometimes provide NULL
 * values instead of pointers to NULL values. There has been no explanation
 * for the reason behind this; but, this function takes a NULL to be an
 * invalid (char *) and returns an error.
 */
static int
dup_gr_ent(struct group *grp)
{
        char **tp = NULL;
        char **memp = NULL;
        int     nent = 0;       /* Number of entries in the member list. */

        if (grp) {
                if (grp->gr_name == NULL) {
                        (void) fprintf(stderr,
                            pkg_gt(ERR_BADMEMB), "dup_gr_ent()", "gr_name",
                            "unknown", "group");
                        return (-1);
                } else if ((grp->gr_name = strdup(grp->gr_name)) == NULL) {
                        (void) fprintf(stderr,
                            pkg_gt(ERR_DUPFAIL), "dup_gr_ent()", "gr_name");
                        return (-1);
                }
                if (grp->gr_passwd == NULL) {
                        (void) fprintf(stderr,
                            pkg_gt(ERR_BADMEMB), "dup_gr_ent()", "gr_passwd",
                            grp->gr_name, "group");
                        return (-1);
                } else if ((grp->gr_passwd = strdup(grp->gr_passwd)) == NULL) {
                        (void) fprintf(stderr,
                            pkg_gt(ERR_DUPFAIL), "dup_gr_ent()", "gr_passwd");
                        return (-1);
                }
                /*
                 * Allocate space for the member list and move the members
                 * into it.
                 */
                if (grp->gr_mem) {
                        /*
                         * First count the members. The nent variable will be
                         * the number of members + 1 for the terminator.
                         */
                        for (tp = grp->gr_mem; *tp; nent++, tp++);

                        /* Now allocate room for the pointers. */
                        memp = malloc(sizeof (char **)* (nent+1));

                        if (memp == NULL) {
                                (void) fprintf(stderr,
                                    pkg_gt(ERR_MALLOC), "dup_gr_ent()",
                                    (sizeof (char **)* (nent+1)),
                                    "memp");
                                return (-1);
                        }

                        /*
                         * Now copy over the pointers and entries. It should
                         * be noted that if the structure is messed up here,
                         * the resulting member list will be truncated at the
                         * NULL entry.
                         */
                        for (nent = 0, tp = grp->gr_mem; *tp; tp++) {
                                if ((memp[nent++] = strdup(*tp)) == NULL) {
                                        (void) fprintf(stderr,
                                            pkg_gt(ERR_DUPFAIL), "dup_gr_ent()",
                                            "gr_mem");
                                        return (-1);
                                }
                        }
                } else {
                        (void) fprintf(stderr,
                            pkg_gt(ERR_BADMEMB), "dup_gr_ent()", "gr_mem",
                            grp->gr_name, "group");
                        return (-1);
                }
        } else {
                (void) fprintf(stderr, pkg_gt(ERR_NOGRP));
                return (-1);
        }
        memp[nent++] = '\0';
        return (0);
}

/*
 * This function duplicates the passwd structure provided from kernel static
 * memory. As in the above function, since there have been problems with the
 * getpw*() functions, the structure provided is rigorously scrubbed. This
 * function takes a NULL to be an invalid (char *) and returns an error if
 * one is detected.
 */
static int
dup_pw_ent(struct passwd *pwd)
{
        if (pwd) {
                if (pwd->pw_name == NULL) {
                        (void) fprintf(stderr,
                            pkg_gt(ERR_BADMEMB), "dup_pw_ent()", "pw_name",
                            "unknown", "passwd");
                        return (-1);
                } else if ((pwd->pw_name = strdup(pwd->pw_name)) == NULL) {
                        (void) fprintf(stderr,
                            pkg_gt(ERR_DUPFAIL), "dup_pw_ent()", "pw_name");
                        return (-1);
                }

                if (pwd->pw_passwd == NULL) {
                        (void) fprintf(stderr,
                            pkg_gt(ERR_BADMEMB), "dup_pw_ent()", "pw_passwd",
                            pwd->pw_name, "passwd");
                        return (-1);
                } else if ((pwd->pw_passwd = strdup(pwd->pw_passwd)) == NULL) {
                        (void) fprintf(stderr,
                            pkg_gt(ERR_DUPFAIL), "dup_pw_ent()", "pw_passwd");
                        return (-1);
                }

                if (pwd->pw_age == NULL) {
                        (void) fprintf(stderr,
                            pkg_gt(ERR_BADMEMB), "dup_pw_ent()", "pw_age",
                            pwd->pw_name, "passwd");
                        return (-1);
                } else if ((pwd->pw_age = strdup(pwd->pw_age)) == NULL) {
                        (void) fprintf(stderr,
                            pkg_gt(ERR_DUPFAIL), "dup_pw_ent()", "pw_age");
                        return (-1);
                }

                if (pwd->pw_comment == NULL) {
                        (void) fprintf(stderr,
                            pkg_gt(ERR_BADMEMB), "dup_pw_ent()", "pw_comment",
                            pwd->pw_name, "passwd");
                        return (-1);
                } else if ((pwd->pw_comment = strdup(pwd->pw_comment)) ==
                    NULL) {
                        (void) fprintf(stderr,
                            pkg_gt(ERR_DUPFAIL), "dup_pw_ent()", "pw_comment");
                        return (-1);
                }

                if (pwd->pw_gecos == NULL) {
                        (void) fprintf(stderr,
                            pkg_gt(ERR_BADMEMB), "dup_pw_ent()", "pw_gecos",
                            pwd->pw_name, "passwd");
                        return (-1);
                } else if ((pwd->pw_gecos = strdup(pwd->pw_gecos)) == NULL) {
                        (void) fprintf(stderr,
                            pkg_gt(ERR_DUPFAIL), "dup_pw_ent()", "pw_gecos");
                        return (-1);
                }

                if (pwd->pw_dir == NULL) {
                        (void) fprintf(stderr,
                            pkg_gt(ERR_BADMEMB), "dup_pw_ent()", "pw_dir",
                            pwd->pw_name, "passwd");
                        return (-1);
                } else if ((pwd->pw_dir = strdup(pwd->pw_dir)) == NULL) {
                        (void) fprintf(stderr,
                            pkg_gt(ERR_DUPFAIL), "dup_pw_ent()", "pw_dir");
                        return (-1);
                }

                if (pwd->pw_shell == NULL) {
                        (void) fprintf(stderr,
                            pkg_gt(ERR_BADMEMB), "dup_pw_ent()", "pw_shell",
                            pwd->pw_name, "passwd");
                        return (-1);
                } else if ((pwd->pw_shell = strdup(pwd->pw_shell)) == NULL) {
                        (void) fprintf(stderr,
                            pkg_gt(ERR_DUPFAIL), "dup_pw_ent()", "pw_shell");
                        return (-1);
                }
        } else {
                (void) fprintf(stderr, pkg_gt(ERR_NOPWD));
                return (-1);
        }

        return (0);
}

/*
 * Check the client's etc/group file for the group name
 *
 * returns a pointer to the group structure if the group is found
 * returns NULL if not found
 */
struct group *
clgrnam(char *nam)
{
        struct group *gr;
        char *instroot, *buf;
        FILE *gr_ptr;
        size_t bufsz;

        if ((instroot = get_install_root()) != NULL) {
                bufsz = strlen(instroot) + strlen(GROUP) + 1;
                if ((buf = (char *)malloc(bufsz)) == NULL) {
                        (void) fprintf(stderr,
                            pkg_gt(ERR_MALLOC), "clgrnam()",
                            strlen(instroot) + strlen(GROUP), "buf");
                }
                (void) snprintf(buf, bufsz, "%s%s", instroot, GROUP);
                if ((gr_ptr = fopen(buf, "r")) == NULL) {
                        free(buf);
                        return (NULL);
                } else {
                        while ((gr = fgetgrent(gr_ptr)) != NULL) {
                                if (strcmp(gr->gr_name, nam) == 0) {
                                        break;
                                }
                        }
                }
                free(buf);
                (void) fclose(gr_ptr);
                return (gr);
        } else {
                return (NULL);
        }
}

/*
 * Check the client's etc/passwd file for the user name
 *
 * returns a pointer to the passwd structure if the passwd is found
 * returns NULL if not found
 */
struct passwd *
clpwnam(char *nam)
{
        struct passwd *pw;
        char *instroot, *buf;
        FILE *pw_ptr;

        if ((instroot = get_install_root()) != NULL) {
                if (asprintf(&buf, "%s%s", instroot, PASSWD) < 0) {
                        (void) fprintf(stderr,
                            pkg_gt(ERR_MALLOC), "clpwnam()",
                            strlen(instroot) + strlen(PASSWD), "buf");
                        return (NULL);
                }
                if ((pw_ptr = fopen(buf, "r")) == NULL) {
                        free(buf);
                        return (NULL);
                } else {
                        while ((pw = fgetpwent(pw_ptr)) != NULL) {
                                if (strcmp(pw->pw_name, nam) == 0) {
                                        break;
                                }
                        }
                }
                free(buf);
                (void) fclose(pw_ptr);
                return (pw);
        } else {
                return (NULL);
        }
}

/*
 * Check the client's etc/group file for the group id
 *
 * returns a pointer to the group structure if the group id is found
 * returns NULL if not found
 */
struct group *
clgrgid(gid_t gid)
{
        struct group *gr;
        char *instroot, *buf;
        FILE *gr_ptr;

        if ((instroot = get_install_root()) != NULL) {
                if (asprintf(&buf, "%s%s", instroot, GROUP) < 0) {
                        (void) fprintf(stderr,
                            pkg_gt(ERR_MALLOC), "clgrgid()",
                            strlen(instroot) + strlen(GROUP), "buf");
                        return (NULL);
                }

                if ((gr_ptr = fopen(buf, "r")) == NULL) {
                        free(buf);
                        return (NULL);
                } else {
                        while ((gr = fgetgrent(gr_ptr)) != NULL) {
                                if (gr->gr_gid == gid) {
                                        break;
                                }
                        }
                }
                free(buf);
                (void) fclose(gr_ptr);
                return (gr);
        } else {
                return (NULL);
        }
}

/*
 * Check the client's etc/passwd file for the user id
 *
 * returns a pointer to the passwd structure if the user id is found
 * returns NULL if not found
 */
struct passwd *
clpwuid(uid_t uid)
{
        struct passwd *pw;
        char *instroot, *buf;
        FILE *pw_ptr;

        if ((instroot = get_install_root()) != NULL) {
                if (asprintf(&buf, "%s%s", instroot, PASSWD) < 0) {
                        (void) fprintf(stderr, pkg_gt(ERR_MALLOC), "clpwuid()",
                            strlen(instroot) + strlen(PASSWD), "buf");
                        return (NULL);
                }
                if ((pw_ptr = fopen(buf, "r")) == NULL) {
                        free(buf);
                        return (NULL);
                } else {
                        while ((pw = fgetpwent(pw_ptr)) != NULL) {
                                if (pw->pw_uid == uid) {
                                        break;
                                }
                        }
                }
                free(buf);
                (void) fclose(pw_ptr);
                return (pw);
        } else {
                return (NULL);
        }
}