root/usr/src/lib/libgss/gssd_pname_to_uid.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.
 */

#include <pwd.h>
#include <grp.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <thread.h>
#include <synch.h>
#include <syslog.h>
#include <deflt.h>
#include <mechglueP.h>
#include "../../cmd/gss/gsscred/gsscred.h"

static mutex_t uid_map_lock = DEFAULTMUTEX;
static int uid_map_opt = 0;

extern int _getgroupsbymember(const char *, gid_t[], int, int);

/* local function used to call a mechanisms pname_to_uid */
static OM_uint32 gss_pname_to_uid(OM_uint32*, const gss_name_t,
                        const gss_OID, uid_t *);

static OM_uint32 private_gsscred_expname_to_unix_cred(const gss_buffer_t,
                        uid_t *, gid_t *, gid_t **, int *);

/*
 * The gsscred functions will first attempt to call the
 * mechanism'm pname_to_uid function.  In case this function
 * returns an error or if it is not provided by a mechanism
 * then the functions will attempt to look up the principal
 * in the gsscred table.
 * It is envisioned that the pname_to_uid function will be
 * provided by only a few mechanism, which may have the principal
 * name to unix credential mapping inherently present.
 */

/*
 * Fetch gsscred options from conf file.
 */
static void
get_conf_options(int *uid_map)
{
        int  flags;
        char *ptr;
        void    *defp;
        static char *conffile = "/etc/gss/gsscred.conf";

        *uid_map = 0;
        if ((defp = defopen_r(conffile)) != NULL) {
                flags = defcntl_r(DC_GETFLAGS, 0, defp);
                /* ignore case */
                TURNOFF(flags, DC_CASE);
                (void) defcntl_r(DC_SETFLAGS, flags, defp);

                if ((ptr = defread_r("SYSLOG_UID_MAPPING=", defp)) != NULL &&
                    strcasecmp("yes", ptr) == 0) {
                        *uid_map = 1;
                }
                defclose_r(defp);
        }
}

void
gsscred_set_options()
{
        int u;

        get_conf_options(&u);
        (void) mutex_lock(&uid_map_lock);
        uid_map_opt = u;
        (void) mutex_unlock(&uid_map_lock);
}

static int
get_uid_map_opt()
{
        int u;

        (void) mutex_lock(&uid_map_lock);
        u = uid_map_opt;
        (void) mutex_unlock(&uid_map_lock);
        return (u);
}

/*
 * This routine accepts a name in export name format and retrieves
 * unix credentials associated with it.
 */

OM_uint32
gsscred_expname_to_unix_cred_ext(
        const gss_buffer_t expName,
        uid_t *uidOut,
        gid_t *gidOut,
        gid_t *gids[],
        int *gidsLen,
        int try_mech)
{
        gss_name_t intName;
        OM_uint32 minor, major;
        const char *mechStr = NULL;
        char *nameStr = NULL;
        char *whoami = "gsscred_expname_to_unix_cred";
        gss_buffer_desc namebuf;
        int debug = get_uid_map_opt();

        if (uidOut == NULL)
                return (GSS_S_CALL_INACCESSIBLE_WRITE);

        if (expName == NULL)
                return (GSS_S_CALL_INACCESSIBLE_READ);

        /* first check the mechanism for the mapping */
        if (gss_import_name(&minor, expName, (gss_OID)GSS_C_NT_EXPORT_NAME,
            &intName) == GSS_S_COMPLETE) {

                if (debug) {
                        gss_union_name_t uintName = (gss_union_name_t)intName;

                        if (uintName->mech_type)
                                mechStr = __gss_oid_to_mech(
                                    uintName->mech_type);

                        major = gss_display_name(&minor, intName,
                            &namebuf, NULL);
                        if (major == GSS_S_COMPLETE) {
                                nameStr = strdup(namebuf.value);
                                (void) gss_release_buffer(&minor, &namebuf);
                        }
                }

                if (try_mech) {
                        major = gss_pname_to_uid(&minor, intName,
                            NULL, uidOut);
                        if (major == GSS_S_COMPLETE) {

                                if (debug) {
                                        syslog(LOG_AUTH|LOG_DEBUG,
                                            "%s: mech provided local name"
                                            " mapping (%s, %s, %d)", whoami,
                                            mechStr ? mechStr : "<null>",
                                            nameStr ? nameStr : "<null>",
                                            *uidOut);
                                        free(nameStr);
                                }

                                (void) gss_release_name(&minor, &intName);
                                if (gids && gidsLen && gidOut)
                                        return (gss_get_group_info(*uidOut,
                                            gidOut, gids, gidsLen));
                                return (GSS_S_COMPLETE);
                        }
                }

                (void) gss_release_name(&minor, &intName);
        }

        /*
         * we fall back onto the gsscred table to provide the mapping
         * start by making sure that the expName is an export name buffer
         */
        major = private_gsscred_expname_to_unix_cred(expName, uidOut, gidOut,
            gids, gidsLen);

        if (debug && major == GSS_S_COMPLETE) {
                syslog(LOG_AUTH|LOG_DEBUG,
                    "%s: gsscred tbl provided"
                    " local name mapping (%s, %s, %d)",
                    whoami,
                    mechStr ? mechStr : "<unknown>",
                    nameStr ? nameStr : "<unknown>",
                    *uidOut);
                free(nameStr);
        } else if (debug) {
                syslog(LOG_AUTH|LOG_DEBUG,
                    "%s: gsscred tbl could NOT"
                    " provide local name mapping (%s, %s)",
                    whoami,
                    mechStr ? mechStr : "<unknown>",
                    nameStr ? nameStr : "<unknown>");
                free(nameStr);
        }

        return (major);

} /* gsscred_expname_to_unix_cred */

OM_uint32
gsscred_expname_to_unix_cred(
        const gss_buffer_t expName,
        uid_t *uidOut,
        gid_t *gidOut,
        gid_t *gids[],
        int *gidsLen)
{
        return (gsscred_expname_to_unix_cred_ext(expName, uidOut, gidOut, gids,
            gidsLen, 1));
}


static const char *expNameTokId = "\x04\x01";
static const int expNameTokIdLen = 2;
/*
 * private routine added to be called from gsscred_name_to_unix_cred
 * and gsscred_expName_to_unix_cred.
 */
static OM_uint32
private_gsscred_expname_to_unix_cred(expName, uidOut, gidOut, gids, gidsLen)
const gss_buffer_t expName;
uid_t *uidOut;
gid_t *gidOut;
gid_t *gids[];
int *gidsLen;
{

        if (expName->length < expNameTokIdLen ||
                (memcmp(expName->value, expNameTokId, expNameTokIdLen) != 0))
                return (GSS_S_DEFECTIVE_TOKEN);

        if (!gss_getGssCredEntry(expName, uidOut))
                return (GSS_S_FAILURE);

        /* did caller request group info also ? */
        if (gids && gidsLen && gidOut)
                return (gss_get_group_info(*uidOut, gidOut, gids, gidsLen));

        return (GSS_S_COMPLETE);
}

/*
 * Return a string of the authenticated name.
 * It's a bit of hack/workaround/longroad but the current intName
 * passed to gss_display_name insists on returning an empty string.
 *
 * Caller must free string memory.
 */
static
char *make_name_str(
        const gss_name_t intName,
        const gss_OID mechType)

{
        gss_buffer_desc expName = GSS_C_EMPTY_BUFFER;
        OM_uint32 major, minor;
        gss_name_t canonName;
        gss_name_t iName;
        gss_buffer_desc namebuf;

        if (major = gss_canonicalize_name(&minor, intName,
                                mechType, &canonName))
                return (NULL);

        major = gss_export_name(&minor, canonName, &expName);
        (void) gss_release_name(&minor, &canonName);
        if (major)
                return (NULL);

        if (gss_import_name(&minor, &expName,
                            (gss_OID)GSS_C_NT_EXPORT_NAME,
                            &iName) == GSS_S_COMPLETE) {

                major = gss_display_name(&minor, iName, &namebuf, NULL);
                if (major == GSS_S_COMPLETE) {
                        char *s;

                        if (namebuf.value)
                                s = strdup(namebuf.value);

                        (void) gss_release_buffer(&minor, &namebuf);
                        (void) gss_release_buffer(&minor, &expName);
                        (void) gss_release_buffer(&minor, (gss_buffer_t)iName);
                        return (s);
                }
                (void) gss_release_buffer(&minor, (gss_buffer_t)iName);
        }

        (void) gss_release_buffer(&minor, &expName);
        return (NULL);
}

/*
 * This routine accepts a name in gss internal name format together with
 * a mechanim OID and retrieves a unix credentials for that entity.
 */
OM_uint32
gsscred_name_to_unix_cred_ext(
        const gss_name_t intName,
        const gss_OID mechType,
        uid_t *uidOut,
        gid_t *gidOut,
        gid_t *gids[],
        int *gidsLen,
        int try_mech)
{
        gss_name_t canonName;
        gss_buffer_desc expName = GSS_C_EMPTY_BUFFER;
        OM_uint32 major, minor;
        int debug = get_uid_map_opt();

        const char *mechStr;
        char *whoami = "gsscred_name_to_unix_cred";
        gss_buffer_desc namebuf;

        if (intName == NULL || mechType == NULL)
                return (GSS_S_CALL_INACCESSIBLE_READ);

        if (uidOut == NULL)
                return (GSS_S_CALL_INACCESSIBLE_WRITE);

        mechStr = __gss_oid_to_mech(mechType);

        /* first try the mechanism provided mapping */
        if (try_mech && gss_pname_to_uid(&minor, intName, mechType, uidOut)
            == GSS_S_COMPLETE) {

                if (debug) {
                        char *s = make_name_str(intName, mechType);
                        syslog(LOG_AUTH|LOG_DEBUG,
                            "%s: mech provided local name"
                            " mapping (%s, %s, %d)", whoami,
                            mechStr ? mechStr : "<null>",
                            s ? s : "<null>",
                            *uidOut);
                        free(s);
                }

                if (gids && gidsLen && gidOut)
                        return (gss_get_group_info(*uidOut, gidOut, gids,
                            gidsLen));
                return (GSS_S_COMPLETE);
        }
        /*
         * falling back onto the gsscred table to provide the mapping
         * start by canonicalizing the passed in name and then export it
         */
        if (major = gss_canonicalize_name(&minor, intName,
            mechType, &canonName))
                return (major);

        major = gss_export_name(&minor, canonName, &expName);
        (void) gss_release_name(&minor, &canonName);
        if (major)
                return (major);

        major = private_gsscred_expname_to_unix_cred(&expName, uidOut, gidOut,
            gids, gidsLen);


        if (debug) {
                gss_name_t iName;
                OM_uint32 maj;
                char *nameStr = NULL;

                if (gss_import_name(&minor, &expName,
                    (gss_OID)GSS_C_NT_EXPORT_NAME, &iName) == GSS_S_COMPLETE) {

                        maj = gss_display_name(&minor, iName, &namebuf,
                            NULL);
                        (void) gss_release_buffer(&minor, (gss_buffer_t)iName);
                        if (maj == GSS_S_COMPLETE) {
                                nameStr = strdup(namebuf.value);
                                (void) gss_release_buffer(&minor, &namebuf);
                        }
                }

                if (major == GSS_S_COMPLETE)
                        syslog(LOG_AUTH|LOG_DEBUG,
                            "%s: gsscred tbl provided"
                            " local name mapping (%s, %s, %d)",
                            whoami,
                            mechStr ? mechStr : "<unknown>",
                            nameStr ? nameStr : "<unknown>",
                            *uidOut);
                else
                        syslog(LOG_AUTH|LOG_DEBUG,
                            "%s: gsscred tbl could NOT"
                            " provide local name mapping (%s, %s)",
                            whoami,
                            mechStr ? mechStr : "<unknown>",
                            nameStr ? nameStr : "<unknown>");

                free(nameStr);
        }

        (void) gss_release_buffer(&minor, &expName);
        return (major);
} /* gsscred_name_to_unix_cred */


OM_uint32
gsscred_name_to_unix_cred(
        const gss_name_t intName,
        const gss_OID mechType,
        uid_t *uidOut,
        gid_t *gidOut,
        gid_t *gids[],
        int *gidsLen)
{
        return (gsscred_name_to_unix_cred_ext(intName, mechType,
            uidOut, gidOut, gids, gidsLen, 1));
}



/*
 * This routine accepts a unix uid, and retrieves the group id
 * and supplamentery group ids for that uid.
 * Callers should be aware that the supplamentary group ids
 * array may be empty even when this function returns success.
 */
OM_uint32
gss_get_group_info(uid, gidOut, gids, gidsLen)
const uid_t uid;
gid_t *gidOut;
gid_t *gids[];
int *gidsLen;
{
        struct passwd *pw;
        int maxgroups;

        /* check for output parameters */
        if (gidOut == NULL || gids == NULL || gidsLen == NULL)
                return (GSS_S_CALL_INACCESSIBLE_WRITE);

        *gids = NULL;
        *gidsLen = 0;

        /* determine maximum number of groups possible */
        maxgroups = sysconf(_SC_NGROUPS_MAX);
        if (maxgroups < 1)
            maxgroups = 16;

        if ((pw = getpwuid(uid)) == NULL)
            return (GSS_S_FAILURE);

        /*
         * we allocate for the maximum number of groups
         * we do not reclaim the space when the actual number
         * is lower, just set the size approprately.
         */
        *gids = (gid_t *)calloc(maxgroups, sizeof (gid_t));
        if (*gids == NULL)
            return (GSS_S_FAILURE);

        *gidOut = pw->pw_gid;
        (*gids)[0] = pw->pw_gid;
        *gidsLen = _getgroupsbymember(pw->pw_name, *gids, maxgroups, 1);
        /*
         * we will try to remove the duplicate entry from the groups
         * array.  This can cause the group array to be empty.
         */
        if (*gidsLen < 1)
        {
                free(*gids);
                *gids = NULL;
                return (GSS_S_FAILURE);
        } else if (*gidsLen == 1) {
                free(*gids);
                *gids = NULL;
                *gidsLen = 0;
        } else {
                /* length is atleast 2 */
                *gidsLen = *gidsLen -1;
                (*gids)[0] = (*gids)[*gidsLen];
        }

        return (GSS_S_COMPLETE);
} /* gss_get_group_info */


static OM_uint32
gss_pname_to_uid(minor, name, mech_type, uidOut)
OM_uint32 *minor;
const gss_name_t name;
const gss_OID mech_type;
uid_t *uidOut;
{
        gss_mechanism mech;
        gss_union_name_t intName;
        gss_name_t mechName = NULL;
        OM_uint32 major, tmpMinor;

        if (!minor)
                return (GSS_S_CALL_INACCESSIBLE_WRITE);

        *minor = 0;

        if (uidOut == NULL)
                return (GSS_S_CALL_INACCESSIBLE_WRITE);

        if (name == NULL)
                return (GSS_S_CALL_INACCESSIBLE_READ);

        intName = (gss_union_name_t)name;

        if (mech_type != NULL)
                mech = __gss_get_mechanism(mech_type);
        else {
                /*
                 * if this is a MN, then try using the mech
                 * from the name; otherwise ask for default
                 */
                mech = __gss_get_mechanism(intName->mech_type);
        }

        if (mech == NULL || mech->pname_to_uid == NULL)
                return (GSS_S_UNAVAILABLE);

        /* may need to import the name if this is not MN */
        if (intName->mech_type == NULL) {
                major = __gss_import_internal_name(minor,
                                mech_type, intName,
                                &mechName);
                if (major != GSS_S_COMPLETE)
                        return (major);
        } else
                mechName = intName->mech_name;


        /* now call the mechanism's pname function to do the work */
        major = mech->pname_to_uid(mech->context, minor, mechName, uidOut);

        if (intName->mech_name != mechName)
                (void) __gss_release_internal_name(&tmpMinor, &mech->mech_type,
                                &mechName);

        return (major);
} /* gss_pname_to_uid */