root/usr/src/cmd/idmap/idmapd/directory_provider_nsswitch.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 (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * Retrieve directory information for standard UNIX users/groups.
 * (NB:  not just from files, but all nsswitch sources.)
 */

#include <pwd.h>
#include <grp.h>
#include <malloc.h>
#include <string.h>
#include <stdlib.h>
#include <netdb.h>
#include <libuutil.h>
#include <note.h>
#include <errno.h>
#include "idmapd.h"
#include "directory.h"
#include "directory_private.h"
#include <rpcsvc/idmap_prot.h>
#include "directory_server_impl.h"
#include "sidutil.h"

static directory_error_t machine_sid_dav(directory_values_rpc *lvals,
    unsigned int rid);
static directory_error_t directory_provider_nsswitch_populate(
    directory_entry_rpc *pent, struct passwd *pwd, struct group *grp,
    idmap_utf8str_list *attrs);

/*
 * Retrieve information by name.
 * Called indirectly through the directory_provider_static structure.
 */
static
directory_error_t
directory_provider_nsswitch_get(
    directory_entry_rpc *del,
    idmap_utf8str_list *ids,
    idmap_utf8str types,
    idmap_utf8str_list *attrs)
{
        int i;

        RDLOCK_CONFIG();

        /* 6835280 spurious lint error if the strlen is in the declaration */
        int host_name_len = strlen(_idmapdstate.hostname);
        char my_host_name[host_name_len + 1];
        (void) strcpy(my_host_name, _idmapdstate.hostname);

        /* We use len later, so this is not merely a workaround for 6835280 */
        int machine_sid_len = strlen(_idmapdstate.cfg->pgcfg.machine_sid);
        char my_machine_sid[machine_sid_len + 1];
        (void) strcpy(my_machine_sid, _idmapdstate.cfg->pgcfg.machine_sid);

        UNLOCK_CONFIG();

        for (i = 0; i < ids->idmap_utf8str_list_len; i++) {
                struct passwd *pwd = NULL;
                struct group *grp = NULL;
                directory_error_t de;
                int type;

                /*
                 * Extract the type for this particular ID.
                 * Advance to the next type, if it's there, else keep
                 * using this type until we run out of IDs.
                 */
                type = *types;
                if (*(types+1) != '\0')
                        types++;

                /*
                 * If this entry has already been handled, one way or another,
                 * skip it.
                 */
                if (del[i].status != DIRECTORY_NOT_FOUND)
                        continue;

                char *id = ids->idmap_utf8str_list_val[i];

                if (type == DIRECTORY_ID_SID[0]) {
                        /*
                         * Is it our SID?
                         * Check whether the first part matches, then a "-",
                         * then a single RID.
                         */
                        if (strncasecmp(id, my_machine_sid, machine_sid_len) !=
                            0)
                                continue;
                        if (id[machine_sid_len] != '-')
                                continue;
                        char *p;
                        uint32_t rid =
                            strtoul(id + machine_sid_len + 1, &p, 10);
                        if (*p != '\0')
                                continue;

                        if (rid < LOCALRID_UID_MIN) {
                                /* Builtin, not handled here */
                                continue;
                        }

                        if (rid <= LOCALRID_UID_MAX) {
                                /* User */
                                errno = 0;
                                pwd = getpwuid(rid - LOCALRID_UID_MIN);
                                if (pwd == NULL) {
                                        if (errno == 0)         /* Not found */
                                                continue;
                                        char buf[40];
                                        int err = errno;
                                        (void) snprintf(buf, sizeof (buf),
                                            "%d", err);
                                        directory_entry_set_error(&del[i],
                                            directory_error("errno.getpwuid",
                                            "getpwuid: %2 (%1)",
                                            buf, strerror(err), NULL));
                                        continue;
                                }
                        } else if (rid >= LOCALRID_GID_MIN &&
                            rid <= LOCALRID_GID_MAX) {
                                /* Group */
                                errno = 0;
                                grp = getgrgid(rid - LOCALRID_GID_MIN);
                                if (grp == NULL) {
                                        if (errno == 0)         /* Not found */
                                                continue;
                                        char buf[40];
                                        int err = errno;
                                        (void) snprintf(buf, sizeof (buf),
                                            "%d", err);
                                        directory_entry_set_error(&del[i],
                                            directory_error("errno.getgrgid",
                                            "getgrgid: %2 (%1)",
                                            buf, strerror(err), NULL));
                                        continue;
                                }
                        } else
                                continue;

                } else {
                        int id_len = strlen(id);
                        char name[id_len + 1];
                        char domain[id_len + 1];

                        split_name(name, domain, id);

                        if (domain[0] != '\0') {
                                if (!domain_eq(domain, my_host_name))
                                        continue;
                        }

                        /*
                         * If the caller has requested user or group
                         * information specifically, we only set one of
                         * pwd or grp.
                         * If the caller has requested either type, we try
                         * both in the hopes of getting one.
                         * Note that directory_provider_nsswitch_populate
                         * considers it to be an error if both are set.
                         */
                        if (type != DIRECTORY_ID_GROUP[0]) {
                                /* prep for not found / error case */
                                errno = 0;

                                pwd = getpwnam(name);
                                if (pwd == NULL && errno != 0) {
                                        char buf[40];
                                        int err = errno;
                                        (void) snprintf(buf, sizeof (buf),
                                            "%d", err);
                                        directory_entry_set_error(&del[i],
                                            directory_error("errno.getpwnam",
                                            "getpwnam: %2 (%1)",
                                            buf, strerror(err), NULL));
                                        continue;
                                }
                        }

                        if (type != DIRECTORY_ID_USER[0]) {
                                /* prep for not found / error case */
                                errno = 0;

                                grp = getgrnam(name);
                                if (grp == NULL && errno != 0) {
                                        char buf[40];
                                        int err = errno;
                                        (void) snprintf(buf, sizeof (buf),
                                            "%d", err);
                                        directory_entry_set_error(&del[i],
                                            directory_error("errno.getgrnam",
                                            "getgrnam: %2 (%1)",
                                            buf, strerror(err), NULL));
                                        continue;
                                }
                        }
                }

                /*
                 * Didn't find it, don't populate the structure.
                 * Another provider might populate it.
                 */
                if (pwd == NULL && grp == NULL)
                        continue;

                de = directory_provider_nsswitch_populate(&del[i], pwd, grp,
                    attrs);
                if (de != NULL) {
                        directory_entry_set_error(&del[i], de);
                        de = NULL;
                        continue;
                }
        }

        return (NULL);
}

/*
 * Given a pwd structure or a grp structure, and a list of attributes that
 * were requested, populate the structure to return to the caller.
 */
static
directory_error_t
directory_provider_nsswitch_populate(
    directory_entry_rpc *pent,
    struct passwd *pwd,
    struct group *grp,
    idmap_utf8str_list *attrs)
{
        int j;
        directory_values_rpc *llvals;
        int nattrs;

        /*
         * If it wasn't for this case, everything would be a lot simpler.
         * UNIX allows users and groups with the same name.  Windows doesn't.
         */
        if (pwd != NULL && grp != NULL) {
                return directory_error("Ambiguous.Name",
                    "Ambiguous name, is both a user and a group",
                    NULL);
        }

        nattrs = attrs->idmap_utf8str_list_len;

        llvals = calloc(nattrs, sizeof (directory_values_rpc));
        if (llvals == NULL)
                goto nomem;

        pent->directory_entry_rpc_u.attrs.attrs_val = llvals;
        pent->directory_entry_rpc_u.attrs.attrs_len = nattrs;
        pent->status = DIRECTORY_FOUND;

        for (j = 0; j < nattrs; j++) {
                directory_values_rpc *val;
                char *a;
                directory_error_t de;

                /*
                 * We're going to refer to these a lot, so make a shorthand
                 * copy.
                 */
                a = attrs->idmap_utf8str_list_val[j];
                val = &llvals[j];

                /*
                 * Start by assuming no errors and that we don't have
                 * the information
                 */
                val->found = FALSE;
                de = NULL;

                if (pwd != NULL) {
                        /*
                         * Handle attributes for user entries.
                         */
                        if (uu_strcaseeq(a, "cn")) {
                                const char *p = pwd->pw_name;
                                de = str_list_dav(val, &p, 1);
                        } else if (uu_strcaseeq(a, "objectClass")) {
                                static const char *objectClasses[] = {
                                        "top",
                                        "posixAccount",
                                };
                                de = str_list_dav(val, objectClasses,
                                    UU_NELEM(objectClasses));
                        } else if (uu_strcaseeq(a, "gidNumber")) {
                                de = uint_list_dav(val, &pwd->pw_gid, 1);
                        } else if (uu_strcaseeq(a, "objectSid")) {
                                de = machine_sid_dav(val,
                                    pwd->pw_uid + LOCALRID_UID_MIN);
                        } else if (uu_strcaseeq(a, "displayName")) {
                                const char *p = pwd->pw_gecos;
                                de = str_list_dav(val, &p, 1);
                        } else if (uu_strcaseeq(a, "distinguishedName")) {
                                char *dn;
                                RDLOCK_CONFIG();
                                (void) asprintf(&dn,
                                    "uid=%s,ou=people,dc=%s",
                                    pwd->pw_name, _idmapdstate.hostname);
                                UNLOCK_CONFIG();
                                if (dn == NULL)
                                        goto nomem;
                                const char *cdn = dn;
                                de = str_list_dav(val, &cdn, 1);
                                free(dn);
                        } else if (uu_strcaseeq(a, "uid")) {
                                const char *p = pwd->pw_name;
                                de = str_list_dav(val, &p, 1);
                        } else if (uu_strcaseeq(a, "uidNumber")) {
                                de = uint_list_dav(val, &pwd->pw_uid, 1);
                        } else if (uu_strcaseeq(a, "gecos")) {
                                const char *p = pwd->pw_gecos;
                                de = str_list_dav(val, &p, 1);
                        } else if (uu_strcaseeq(a, "homeDirectory")) {
                                const char *p = pwd->pw_dir;
                                de = str_list_dav(val, &p, 1);
                        } else if (uu_strcaseeq(a, "loginShell")) {
                                const char *p = pwd->pw_shell;
                                de = str_list_dav(val, &p, 1);
                        } else if (uu_strcaseeq(a, "x-sun-canonicalName")) {
                                char *canon;
                                RDLOCK_CONFIG();
                                (void) asprintf(&canon, "%s@%s",
                                    pwd->pw_name, _idmapdstate.hostname);
                                UNLOCK_CONFIG();
                                if (canon == NULL)
                                        goto nomem;
                                const char *ccanon = canon;
                                de = str_list_dav(val, &ccanon, 1);
                                free(canon);
                        } else if (uu_strcaseeq(a, "x-sun-provider")) {
                                const char *provider = "UNIX-passwd";
                                de = str_list_dav(val, &provider, 1);
                        }
                } else if (grp != NULL)  {
                        /*
                         * Handle attributes for group entries.
                         */
                        if (uu_strcaseeq(a, "cn")) {
                                const char *p = grp->gr_name;
                                de = str_list_dav(val, &p, 1);
                        } else if (uu_strcaseeq(a, "objectClass")) {
                                static const char *objectClasses[] = {
                                        "top",
                                        "posixGroup",
                                };
                                de = str_list_dav(val, objectClasses,
                                    UU_NELEM(objectClasses));
                        } else if (uu_strcaseeq(a, "gidNumber")) {
                                de = uint_list_dav(val, &grp->gr_gid, 1);
                        } else if (uu_strcaseeq(a, "objectSid")) {
                                de = machine_sid_dav(val,
                                    grp->gr_gid + LOCALRID_GID_MIN);
                        } else if (uu_strcaseeq(a, "displayName")) {
                                const char *p = grp->gr_name;
                                de = str_list_dav(val, &p, 1);
                        } else if (uu_strcaseeq(a, "distinguishedName")) {
                                char *dn;
                                RDLOCK_CONFIG();
                                (void) asprintf(&dn,
                                    "cn=%s,ou=group,dc=%s",
                                    grp->gr_name, _idmapdstate.hostname);
                                UNLOCK_CONFIG();
                                if (dn == NULL)
                                        goto nomem;
                                const char *cdn = dn;
                                de = str_list_dav(val, &cdn, 1);
                                free(dn);
                        } else if (uu_strcaseeq(a, "memberUid")) {
                                /*
                                 * NEEDSWORK:  There is probably a non-cast
                                 * way to do this, but I don't immediately
                                 * see it.
                                 */
                                const char * const *members =
                                    (const char * const *)grp->gr_mem;
                                de = str_list_dav(val, members, 0);
                        } else if (uu_strcaseeq(a, "x-sun-canonicalName")) {
                                char *canon;
                                RDLOCK_CONFIG();
                                (void) asprintf(&canon, "%s@%s",
                                    grp->gr_name, _idmapdstate.hostname);
                                UNLOCK_CONFIG();
                                if (canon == NULL)
                                        goto nomem;
                                const char *ccanon = canon;
                                de = str_list_dav(val, &ccanon, 1);
                                free(canon);
                        } else if (uu_strcaseeq(a, "x-sun-provider")) {
                                const char *provider = "UNIX-group";
                                de = str_list_dav(val, &provider, 1);
                        }
                }

                if (de != NULL)
                        return (de);
        }

        return (NULL);

nomem:
        return (directory_error("ENOMEM.users",
            "No memory allocating return value for user lookup", NULL));
}

/*
 * Populate a directory attribute value with a SID based on our machine SID
 * and the specified RID.
 *
 * It's a bit perverse that we must take a text-format SID and turn it into
 * a binary-format SID, only to have the caller probably turn it back into
 * text format, but SIDs are carried across LDAP in binary format.
 */
static
directory_error_t
machine_sid_dav(directory_values_rpc *lvals, unsigned int rid)
{
        sid_t *sid;
        directory_error_t de;

        RDLOCK_CONFIG();
        int len = strlen(_idmapdstate.cfg->pgcfg.machine_sid);
        char buf[len + 100];    /* 100 is enough space for any RID */
        (void) snprintf(buf, sizeof (buf), "%s-%u",
            _idmapdstate.cfg->pgcfg.machine_sid, rid);
        UNLOCK_CONFIG();

        sid = sid_fromstr(buf);
        if (sid == NULL)
                goto nomem;

        sid_to_le(sid);

        de = bin_list_dav(lvals, sid, 1, sid_len(sid));
        sid_free(sid);
        return (de);

nomem:
        return (directory_error("ENOMEM.machine_sid_dav",
            "Out of memory allocating return value for lookup", NULL));
}

struct directory_provider_static directory_provider_nsswitch = {
        "files",
        directory_provider_nsswitch_get,
};