/******************************************************************************/
/* */
/* X r d S y s P r i v . c c */
/* */
/* (c) 2006 G. Ganis (CERN) */
/* */
/* This file is part of the XRootD software suite. */
/* */
/* XRootD is free software: you can redistribute it and/or modify it under */
/* the terms of the GNU Lesser General Public License as published by the */
/* Free Software Foundation, either version 3 of the License, or (at your */
/* option) any later version. */
/* */
/* XRootD is distributed in the hope that it will be useful, but WITHOUT */
/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */
/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */
/* License for more details. */
/* */
/* You should have received a copy of the GNU Lesser General Public License */
/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */
/* COPYING (GPL license). If not, see . */
/* */
/* The copyright holder's institutional names and contributor's names may not */
/* be used to endorse or promote products derived from this software without */
/* specific prior written permission of the institution or contributor. */
/* All Rights Reserved. See XrdInfo.cc for complete License Terms */
/******************************************************************************/
//////////////////////////////////////////////////////////////////////////
// //
// XrdSysPriv //
// //
// Author: G. Ganis, CERN, 2006 //
// //
// Implementation of a privileges handling API following the paper //
// "Setuid Demystified" by H.Chen, D.Wagner, D.Dean //
// also quoted in "Secure programming Cookbook" by J.Viega & M.Messier. //
// //
//////////////////////////////////////////////////////////////////////////
#include "XrdSys/XrdSysPriv.hh"
#if !defined(WINDOWS)
#include
#include "XrdSys/XrdSysHeaders.hh"
#include "XrdSys/XrdSysPwd.hh"
#include
#include
#include
#define NOUC ((uid_t)(-1))
#define NOGC ((gid_t)(-1))
#define XSPERR(x) ((x == 0) ? -1 : -x)
// Some machine specific stuff
#if defined(__sgi) && !defined(__GNUG__) && (SGI_REL<62)
extern "C" {
int seteuid(int euid);
int setegid(int egid);
int geteuid();
int getegid();
}
#endif
#if defined(_AIX)
extern "C" {
int seteuid(uid_t euid);
int setegid(gid_t egid);
uid_t geteuid();
gid_t getegid();
}
#endif
#if !defined(HAVE_SETRESUID)
static int setresgid(gid_t r, gid_t e, gid_t)
{
if (r != NOGC && setgid(r) == -1)
return XSPERR(errno);
return ((e != NOGC) ? setegid(e) : 0);
}
static int setresuid(uid_t r, uid_t e, uid_t)
{
if (r != NOUC && setuid(r) == -1)
return XSPERR(errno);
return ((e != NOUC) ? seteuid(e) : 0);
}
static int getresgid(gid_t *r, gid_t *e, gid_t *)
{
*r = getgid();
*e = getegid();
return 0;
}
static int getresuid(uid_t *r, uid_t *e, uid_t *)
{
*r = getuid();
*e = geteuid();
return 0;
}
#else
#if (defined(__linux__) || \
(defined(__CYGWIN__) && defined(__GNUC__))) && !defined(linux)
# define linux
#endif
#if defined(linux) && !defined(HAVE_SETRESUID)
extern "C" {
int setresgid(gid_t r, gid_t e, gid_t s);
int setresuid(uid_t r, uid_t e, uid_t s);
int getresgid(gid_t *r, gid_t *e, gid_t *s);
int getresuid(uid_t *r, uid_t *e, uid_t *s);
}
#endif
#endif
#endif // not WINDOWS
bool XrdSysPriv::fDebug = 0; // debug switch
// Gloval mutex
XrdSysRecMutex XrdSysPriv::fgMutex;
//______________________________________________________________________________
int XrdSysPriv::Restore(bool saved)
{
// Restore the 'saved' (saved = TRUE) or 'real' entity as effective.
// Return 0 on success, < 0 (== -errno) if any error occurs.
#if !defined(WINDOWS)
// Get the UIDs
uid_t ruid = 0, euid = 0, suid = 0;
if (getresuid(&ruid, &euid, &suid) != 0)
return XSPERR(errno);
// Set the wanted value
uid_t uid = saved ? suid : ruid;
// Act only if a change is needed
if (euid != uid) {
// Set uid as effective
if (setresuid(NOUC, uid, NOUC) != 0)
return XSPERR(errno);
// Make sure the new effective UID is the one wanted
if (geteuid() != uid)
return XSPERR(errno);
}
// Get the GIDs
uid_t rgid = 0, egid = 0, sgid = 0;
if (getresgid(&rgid, &egid, &sgid) != 0)
return XSPERR(errno);
// Set the wanted value
gid_t gid = saved ? sgid : rgid;
// Act only if a change is needed
if (egid != gid) {
// Set newuid as effective, saving the current effective GID
if (setresgid(NOGC, gid, NOGC) != 0)
return XSPERR(errno);
// Make sure the new effective GID is the one wanted
if (getegid() != gid)
return XSPERR(errno);
}
#endif
// Done
return 0;
}
//______________________________________________________________________________
int XrdSysPriv::ChangeTo(uid_t newuid, gid_t newgid)
{
// Change effective to entity newuid. Current entity is saved.
// Real entity is not touched. Use RestoreSaved to go back to
// previous settings.
// Return 0 on success, < 0 (== -errno) if any error occurs.
#if !defined(WINDOWS)
// Current UGID
uid_t oeuid = geteuid();
gid_t oegid = getegid();
// Restore privileges, if needed
if (oeuid && XrdSysPriv::Restore(0) != 0)
return XSPERR(errno);
// Act only if a change is needed
if (newgid != oegid) {
// Set newgid as effective, saving the current effective GID
if (setresgid(NOGC, newgid, oegid) != 0)
return XSPERR(errno);
// Get the GIDs
uid_t rgid = 0, egid = 0, sgid = 0;
if (getresgid(&rgid, &egid, &sgid) != 0)
return XSPERR(errno);
// Make sure the new effective GID is the one wanted
if (egid != newgid)
return XSPERR(errno);
}
// Act only if a change is needed
if (newuid != oeuid) {
// Set newuid as effective, saving the current effective UID
if (setresuid(NOUC, newuid, oeuid) != 0)
return XSPERR(errno);
// Get the UIDs
uid_t ruid = 0, euid = 0, suid = 0;
if (getresuid(&ruid, &euid, &suid) != 0)
return XSPERR(errno);
// Make sure the new effective UID is the one wanted
if (euid != newuid)
return XSPERR(errno);
}
#endif
// Done
return 0;
}
//______________________________________________________________________________
int XrdSysPriv::ChangePerm(uid_t newuid, gid_t newgid)
{
// Change permanently to entity newuid. Requires super-userprivileges.
// Provides a way to drop permanently su privileges.
// Return 0 on success, < 0 (== -errno) if any error occurs.
// Atomic action
XrdSysPriv::fgMutex.Lock();
#if !defined(WINDOWS)
// Get UIDs
uid_t cruid = 0, ceuid = 0, csuid = 0;
if (getresuid(&cruid, &ceuid, &csuid) != 0) {
XrdSysPriv::fgMutex.UnLock();
return XSPERR(errno);
}
// Get GIDs
uid_t crgid = 0, cegid = 0, csgid = 0;
if (getresgid(&crgid, &cegid, &csgid) != 0) {
XrdSysPriv::fgMutex.UnLock();
return XSPERR(errno);
}
// Restore privileges, if needed
if (ceuid && XrdSysPriv::Restore(0) != 0) {
XrdSysPriv::fgMutex.UnLock();
return XSPERR(errno);
}
// Act only if needed
if (newgid != cegid || newgid != crgid) {
// Set newgid as GID, all levels
if (setresgid(newgid, newgid, newgid) != 0) {
XrdSysPriv::fgMutex.UnLock();
return XSPERR(errno);
}
// Get GIDs
uid_t rgid = 0, egid = 0, sgid = 0;
if (getresgid(&rgid, &egid, &sgid) != 0) {
XrdSysPriv::fgMutex.UnLock();
return XSPERR(errno);
}
// Make sure the new GIDs are all equal to the one asked
if (rgid != newgid || egid != newgid) {
XrdSysPriv::fgMutex.UnLock();
return XSPERR(errno);
}
}
// Act only if needed
if (newuid != ceuid || newuid != cruid) {
// Set newuid as UID, all levels
if (setresuid(newuid, newuid, newuid) != 0) {
XrdSysPriv::fgMutex.UnLock();
return XSPERR(errno);
}
// Get UIDs
uid_t ruid = 0, euid = 0, suid = 0;
if (getresuid(&ruid, &euid, &suid) != 0) {
XrdSysPriv::fgMutex.UnLock();
return XSPERR(errno);
}
// Make sure the new UIDs are all equal to the one asked
if (ruid != newuid || euid != newuid) {
XrdSysPriv::fgMutex.UnLock();
return XSPERR(errno);
}
}
#endif
// Release the mutex
XrdSysPriv::fgMutex.UnLock();
// Done
return 0;
}
//______________________________________________________________________________
void XrdSysPriv::DumpUGID(const char *msg)
{
// Dump current entity
#if !defined(WINDOWS)
XrdSysPriv::fgMutex.Lock();
// Get the UIDs
uid_t ruid = 0, euid = 0, suid = 0;
if (getresuid(&ruid, &euid, &suid) != 0)
return;
// Get the GIDs
uid_t rgid = 0, egid = 0, sgid = 0;
if (getresgid(&rgid, &egid, &sgid) != 0)
return;
cout << "XrdSysPriv: " << endl;
cout << "XrdSysPriv: dump values: " << (msg ? msg : "") << endl;
cout << "XrdSysPriv: " << endl;
cout << "XrdSysPriv: real = (" << ruid <<","<< rgid <<")" << endl;
cout << "XrdSysPriv: effective = (" << euid <<","<< egid <<")" << endl;
cout << "XrdSysPriv: saved = (" << suid <<","<< sgid <<")" << endl;
cout << "XrdSysPriv: " << endl;
XrdSysPriv::fgMutex.UnLock();
#endif
}
//
// Guard class
//______________________________________________________________________________
XrdSysPrivGuard::XrdSysPrivGuard(uid_t uid, gid_t gid)
{
// Constructor. Create a guard object for temporarly change to privileges
// of {'uid', 'gid'}
dum = 1;
valid = 0;
Init(uid, gid);
}
//______________________________________________________________________________
XrdSysPrivGuard::XrdSysPrivGuard(const char *usr)
{
// Constructor. Create a guard object for temporarly change to privileges
// of 'usr'
dum = 1;
valid = 0;
#if !defined(WINDOWS)
if (usr && strlen(usr) > 0) {
struct passwd *pw;
XrdSysPwd thePwd(usr, &pw);
if (pw)
Init(pw->pw_uid, pw->pw_gid);
}
#else
if (usr) { }
#endif
}
//______________________________________________________________________________
XrdSysPrivGuard::~XrdSysPrivGuard()
{
// Destructor. Restore state and unlock the global mutex.
if (!dum) {
XrdSysPriv::Restore();
XrdSysPriv::fgMutex.UnLock();
}
}
//______________________________________________________________________________
void XrdSysPrivGuard::Init(uid_t uid, gid_t gid)
{
// Init a change of privileges guard. Act only if superuser.
// The result of initialization can be tested with the Valid() method.
dum = 1;
valid = 1;
// Debug hook
if (XrdSysPriv::fDebug)
XrdSysPriv::DumpUGID("before Init()");
#if !defined(WINDOWS)
XrdSysPriv::fgMutex.Lock();
uid_t ruid = 0, euid = 0, suid = 0;
gid_t rgid = 0, egid = 0, sgid = 0;
if (getresuid(&ruid, &euid, &suid) == 0 &&
getresgid(&rgid, &egid, &sgid) == 0) {
if ((euid != uid) || (egid != gid)) {
if (!ruid) {
// Change temporarly identity
if (XrdSysPriv::ChangeTo(uid, gid) != 0)
valid = 0;
dum = 0;
} else {
// Change requested but not enough privileges
valid = 0;
}
}
} else {
// Something bad happened: memory corruption?
valid = 0;
}
// Unlock if no action
if (dum)
XrdSysPriv::fgMutex.UnLock();
#endif
// Debug hook
if (XrdSysPriv::fDebug)
XrdSysPriv::DumpUGID("after Init()");
}