[BACK]Return to Cred.c++ CVS log [TXT][DIR] Up to [Development] / fam / fam

File: [Development] / fam / fam / Cred.c++ (download)

Revision 1.1.1.1 (vendor branch), Thu Apr 24 19:08:26 2003 UTC (14 years, 5 months ago) by trev
Branch: sgi-fam, MAIN
CVS Tags: fam-2-6-10, HEAD
Changes since 1.1: +0 -0 lines

Initial FAM CVS repository build..

-- Trev


//  Copyright (C) 1999-2002 Silicon Graphics, Inc.  All Rights Reserved.
//
//  This program is free software; you can redistribute it and/or modify it
//  under the terms of version 2 of the GNU General Public License as
//  published by the Free Software Foundation.
//
//  This program is distributed in the hope that it would be useful, but
//  WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  Further, any
//  license provided herein, whether implied or otherwise, is limited to
//  this program in accordance with the express provisions of the GNU
//  General Public License.  Patent licenses, if any, provided herein do not
//  apply to combinations of this program with other product or programs, or
//  any other product whatsoever.  This program is distributed without any
//  warranty that the program is delivered free of the rightful claim of any
//  third person by way of infringement or the like.  See the GNU General
//  Public License for more details.
//
//  You should have received a copy of the GNU General Public License along
//  with this program; if not, write the Free Software Foundation, Inc., 59
//  Temple Place - Suite 330, Boston MA 02111-1307, USA.

#include "Cred.h"

#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <grp.h>
#include <stdlib.h>
#include <ctype.h>
#include <pwd.h>
#include <errno.h>

#include "Log.h"
#ifdef HAVE_MAC
#include <sys/mac.h>
#include <t6net.h>
#endif

static gid_t SuperUser_groups[1] = { 0 };
const Cred Cred::SuperUser(0, 1, SuperUser_groups, -1);
Cred Cred::untrusted;
const Cred::Implementation *Cred::Implementation::last = NULL;
Cred::Implementation **Cred::impllist;
unsigned Cred::nimpl;
unsigned Cred::nimpl_alloc;
bool Cred::insecure_compat = false;
#ifdef HAVE_MAC
bool Cred::use_mac = true;
#endif

//  This is used by Listener to create a new Cred for each connection whose
//  per-request UIDs will be ignored.  (The uid could either be the untrusted
//  uid, or the uid which the client has been authenticated as.)  It's also
//  used by Cred::set_untrusted_user() to create the untrusted Cred.
Cred::Cred(uid_t u, int sockfd)
{
    unsigned int nAddlGroups = sysconf(_SC_NGROUPS_MAX);
    struct passwd *pwd;
    gid_t *addlGroups = new gid_t[nAddlGroups];
    gid_t primary_group;
    if ((pwd = getpwuid(u)) != NULL)
    {
        primary_group = pwd->pw_gid;
#ifdef HAVE_GETGRMEMBER
        nAddlGroups = getgrmember(pwd->pw_name, addlGroups, nAddlGroups, 0);
        if (nAddlGroups == -1)
        {
            Log::info("getgrmember(%s, ...) failed: %s",
			pwd->pw_name, strerror(errno));
            nAddlGroups = 0;
        }
#else
        group *gbp;
        unsigned int ii = 0;
        setgrent();
        while ((ii < nAddlGroups) && ((gbp = getgrent()) != NULL))
        {
            //  See if our user's name is in the list of group members.
            for (int i = 0; gbp->gr_mem[i] != NULL; ++i)
            {
                if (!strcmp(pwd->pw_name, gbp->gr_mem[i]))
                {
                    addlGroups[ii++] = gbp->gr_gid;
                    break;
                }
            }
        }
        endgrent();
        nAddlGroups = ii;
#endif
    }
    else
    {
        nAddlGroups = 0;
#ifdef NOGROUP
        primary_group = untrusted.is_valid() ? untrusted.gid() : (gid_t) NOGROUP;
#else
        primary_group = untrusted.is_valid() ? untrusted.gid() : (gid_t) GID_NOBODY;
#endif
        Log::info("Warning: uid %i is unknown to this system, so "
                  "that user will be given the gid of the untrusted user: %i",
                  u, primary_group);
                  
    }
    mac_t mac = NULL;
#ifdef HAVE_MAC
    if ((use_mac) && (sockfd != -1))
    {
        if(tsix_get_mac(sockfd, &mac) != 0)
        {
            Log::error("tsix_get_mac failed for client fd %d", sockfd);
            exit(1);
        }
    }
#endif
    new_impl(u, primary_group, nAddlGroups, addlGroups, mac);
    delete [] addlGroups;
}

//  This is used by TCP_Client to create a new Cred for each request from
//  a trusted remote fam.  The MAC label is not handled correctly; the
//  remote fam's is being used, rather than the remote client's.
Cred::Cred(uid_t u, unsigned int ng, const gid_t *gs, int sockfd)
{
    mac_t mac = NULL;
#ifdef HAVE_MAC
    if (use_mac) tsix_get_mac(sockfd, &mac); 
#endif
    // The first gid in the group array should be considered the
    // primary group id, and the remaining groups are additional
    new_impl(u, gs[0], ng-1, gs+1, mac);
}

//  This is used by Cred::get_cred_for_untrusted_conn() to create a new
//  Cred for each unauthenticated connection.  Before the MAC stuff was
//  added, we could just use the untrusted Cred, but now each untrusted can
//  have their own MAC label.
Cred::Cred(int sockfd)
{
#ifdef HAVE_MAC
    if (use_mac)
    {
        mac_t mac = NULL;
        if(tsix_get_mac(sockfd, &mac) != 0)
        {
            //  what to do in this case?  Proceed without a good MAC label?
            Log::error("tsix_get_mac failed for client fd %d", sockfd);
        }
        //  this is broken: needs to test for null untrusted.p
        else if (mac_equal(mac, untrusted.p->mac) == 0)
	{
            new_impl(untrusted.p->myuid, untrusted.p->mygid,
                     untrusted.p->nAddlGroups, untrusted.p->AddlGroups, mac);
            return;
        }
	else mac_free(mac);  // because we'll share the Implementation.
    }
#endif
    //  If we're here, either we're not using MAC or the socket's MAC label
    //  matches untrusted's, so we want to share untrusted's Implementation.
    p = untrusted.p;
    if (p != NULL) p->refcount++;
}

//  This builds an invalid cred which shouldn't be used until it's assigned
//  the value of a good cred.  It's used for the uninitialized untrusted cred,
//  and for the Creds which are created per-connection by
//  Listener::accept_client.
Cred::Cred()
{
    p = NULL;
}

Cred::Cred(const Cred& that)
{
    p = that.p;
    if (p != NULL) p->refcount++;
}

Cred::~Cred()
{
    if (p && (!--p->refcount)) drop(p);
}

Cred&
Cred::operator = (const Cred& that)
{
    if (this != &that)
    {   if ((p != NULL) && (!--p->refcount))
	    drop(p);
	p = that.p;
	if (p != NULL) p->refcount++;
    }
    return *this;
}

void
Cred::add(Implementation *np)
{
    if (nimpl >= nimpl_alloc)
    {   nimpl_alloc = nimpl_alloc * 3 / 2 + 3;
	Implementation **nl = new Implementation *[nimpl_alloc];
	for (int i = 0; i < nimpl; i++)
	    nl[i] = impllist[i];
	delete [] impllist;
	impllist = nl;
    }
    impllist[nimpl++] = np;
}

void
Cred::drop(Implementation *dp)
{
    assert(!dp->refcount);
    for (Implementation **pp = impllist, **end = pp + nimpl; pp < end; pp++)
	if (*pp == dp)
	{   *pp = *--end;
	    assert(nimpl);
	    --nimpl;
	    break;
	}
    delete dp;

    if (!nimpl)
    {   delete[] impllist;
	impllist = NULL;
	nimpl_alloc = 0;
    }
}

void
Cred::new_impl(uid_t u, gid_t g, unsigned int ng, const gid_t *gs, mac_t mac)
{
    for (Implementation **pp = impllist, **end = pp + nimpl; pp < end; pp++)
	if ((*pp)->equal(u, g, ng, gs, mac))
	{   (*pp)->refcount++;
	    p = *pp;
#ifdef HAVE_MAC
            if (mac != NULL) mac_free(mac);
#endif
	    return;
	}

    p = new Implementation(u, g, ng, gs, mac);
    add(p);
}

void
Cred::set_untrusted_user(const char *name)
{
    //  The only time this should get called is at startup, when we're
    //  handling command-line or config-file options, and before any
    //  requests are accepted.
    assert(!untrusted.is_valid());

    //  The untrusted user is never used if we're running in
    //  insecure_compat mode.
    if (insecure_compat) return;

    //  First see if we were passed a uid.
    const char *p = name;
    while (isdigit(*p)) ++p;
    if((*p == '\0') && (p != name))  // need at least one character!
    {
        uid_t uid = atoi(name);
        struct passwd *pwd = getpwuid(uid);
        if(pwd == NULL)
        {
            Log::error("Fatal misconfiguration: attempted to use unknown uid "
                       "\"%i\" for untrusted-user", uid);
            exit(1);
        }
        Cred tmpcred(uid, -1);
        untrusted = tmpcred;
        Log::debug("Setting untrusted-user to \"%s\" (uid: %d, gid: %d)",
                   pwd->pw_name, pwd->pw_uid, pwd->pw_gid);
        return;
    }

    //  Looks like we were passed a user name.
    struct passwd *pwd = getpwnam(name);
    if(pwd == NULL)
    {
        Log::error("Fatal misconfiguration: attempted to use unknown user "
                   "name \"%s\" for untrusted-user", name);
        exit(1);
    }
    Log::debug("Setting untrusted-user to \"%s\" (uid: %d, gid: %d)",
               name, pwd->pw_uid, pwd->pw_gid);
    Cred tmpcred(pwd->pw_uid, -1);
    untrusted = tmpcred;
}

Cred
Cred::get_cred_for_untrusted_conn(int sockfd)
{
    //  An invalid Cred on a connection means we'll trust the Creds supplied
    //  by the connection.  In the case where insecure_compat is enabled, we
    //  always want to return an invalid Cred.  As it happens, untrusted is
    //  always invalid when insecure_compat is enabled, so we just use it.
    //  If insecure compat isn't enabled, we want to return a valid untrusted
    //  Cred.
    assert(untrusted.is_valid() || insecure_compat);
    return insecure_compat ? untrusted : Cred(sockfd);
}

void
Cred::disable_mac()
{
#ifdef HAVE_MAC
    use_mac = false;
    Log::audit(true, "running with MAC disabled, so all client requests will "
                     "use fam's MAC label.");
#endif
}

void
Cred::enable_insecure_compat()
{
    insecure_compat = true;
    Log::audit(true, "running in insecure compatibility mode");
    Log::info("running in insecure compatibility mode");
}

///////////////////////////////////////////////////////////////////////////////

Cred::Implementation::Implementation(uid_t u, gid_t g,
                                     unsigned int ng, const gid_t *gs,
                                     mac_t m)
    : refcount(1), myuid(u), mygid(g), nAddlGroups(ng)
{
#ifdef HAVE_MAC
    mac = NULL;
    if (use_mac) mac = m;
    else if (m != NULL) mac_free(m);
#endif
    AddlGroups = new gid_t[ng];

    for (int i = 0; i < nAddlGroups; i++)
	AddlGroups[i] = gs[i];

    if (nAddlGroups == 0) {
        addlGroupsStr = new char[1];
        *addlGroupsStr = '\0';
    }
    else {
        // The format is: <number of addl groups> <gid1> <gid2> ... <gidn>
        
        // Assume that the each num and accompanying space (or null
        // character in the case of the last one) will be <= 11 chars.
        addlGroupsStr = new char[11*(nAddlGroups + 1)];
        char * p = addlGroupsStr;
        p += snprintf(p, 10, "%d", nAddlGroups);
        for (int i = 0; i < nAddlGroups; i++)
        {
            p += snprintf(p, 11, " %d", AddlGroups[i]);
        }
    }
}

Cred::Implementation::~Implementation()
{
    if (this == last)
	SuperUser.become_user();
    delete [] AddlGroups;
    delete [] addlGroupsStr;
#ifdef HAVE_MAC
    if (mac != NULL) mac_free(mac);
#endif
}

bool
Cred::Implementation::equal(uid_t u, gid_t g, unsigned int ng,
                            const gid_t *gs, mac_t mac) const
{
    if ((u != myuid) || (g != mygid)) {
        return false;
    }
#ifdef HAVE_MAC
    if ((use_mac) && (mac_equal(this->mac, mac) == 0)) return -1;
#endif
    return addl_groups_equal(ng, gs);
}

bool
Cred::Implementation::addl_groups_equal(unsigned int ng, const gid_t *gs) const
{
    if (ng != nAddlGroups) {
        return false;
    }
    for (int i = 0; i < nAddlGroups; i++)
	if (AddlGroups[i] != gs[i])
	    return false;
    return true;
}

// This function returns a string representation of the additional
// groups, as required by ServerConnection::send_monitor().
const char *
Cred::Implementation::getAddlGroupsString() const {
    return addlGroupsStr;
}

void
Cred::Implementation::become_user() const
{
    // If we're becoming the same user we currently are, then we can
    // just skip everything.
    if (this == last)
	return;

    uid_t current_uid = last ? last->myuid : 0;
    if (current_uid != 0) {
        /* Temporarily set the effective uid to root's uid
         * so that we have permission to call setgroups, etc.
         * This assumes, of course, that we were started as root,
         * so that we have permission to do this.
         */
        Log::debug("Setting euid to 0");
        if (seteuid(0) != 0)
        {
            Log::perror("failed to set 0 uid");
            exit(1);
        }
    }

    // We need to set the primary group and additional groups before
    // setting our euid because non-root users don't have permission
    // to change the groups.
    if (!last || !addl_groups_equal(last->nAddlGroups, last->AddlGroups)) {
	if (setgroups(nAddlGroups, AddlGroups) != 0) {
            Log::perror("failed to set groups");
            exit(1);
	} else if (Log::get_level() == Log::DEBUG) {
            if (nAddlGroups == 0) {
                Log::debug("Setting groups to: (none)");
            } else {
                // The groupStr variable is almost what we want, but
                // it has the number of groups prepended.  So just
                // skip over that.
                char * p = strchr(addlGroupsStr, ' ');
                Log::debug("Setting groups to: %s", p+1);
            }
        }
    } else {
        Log::debug("Skipping setting groups, because they're already correct");
    }
    
    if (!last || (mygid != last->mygid)) {
        if (setegid(mygid)) {
            Log::perror("failed to set gid %d", mygid);
            exit(1);
        } else {
            Log::debug("Setting egid to %i", mygid);
        }
    } else {
        Log::debug("Skipping setting egid, because it's already correct");
    }
    
    

    if (myuid != 0) { // We can skip this if we're becoming root, as
                      // we either entered this function as root, or
                      // set our euid to root above
        if (seteuid(myuid)) {
            Log::perror("failed to set uid %d", myuid);
            exit(1);
        } else {
            Log::debug("Setting euid to %i", myuid);
        }
    } else {
        if (current_uid != 0) {
            // We already logged a message above
        } else {
            Log::debug("Skipping setting euid, because it's already 0");
        }
    }
    

#ifdef HAVE_MAC
    if (use_mac)
    {
        if (mac_set_proc(mac) != 0)
        {
            Log::perror("become_user() failed to set MAC label for uid %d", myuid);
        }
    }
#endif
    last = this;
}