/******************************************************************************/
/* */
/* X r d S e c P r o t o c o l s s s . c c */
/* */
/* (c) 2008 by the Board of Trustees of the Leland Stanford, Jr., University */
/* All Rights Reserved */
/* Produced by Andrew Hanushevsky for Stanford University under contract */
/* DE-AC02-76-SFO0515 with the Department of Energy */
/* */
/* 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. */
/******************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include "XrdVersion.hh"
#include "XrdNet/XrdNetUtils.hh"
#include "XrdOuc/XrdOucCRC.hh"
#include "XrdOuc/XrdOucErrInfo.hh"
#include "XrdOuc/XrdOucEnv.hh"
#include "XrdOuc/XrdOucPup.hh"
#include "XrdOuc/XrdOucTokenizer.hh"
#include "XrdSecsss/XrdSecProtocolsss.hh"
#include "XrdSys/XrdSysHeaders.hh"
#include "XrdSys/XrdSysPlatform.hh"
#include "XrdSys/XrdSysPthread.hh"
/******************************************************************************/
/* D e f i n e s */
/******************************************************************************/
#define XrdSecPROTOIDENT "sss"
#define XrdSecPROTOIDLEN sizeof(XrdSecPROTOIDENT)
#define XrdSecDEBUG 0x1000
#define CLDBG(x) if (options & XrdSecDEBUG) cerr <<"sec_sss: " <buffer);
XrdSecsssRR_Data rrData;
XrdSecsssKT::ktEnt decKey;
XrdSecEntity myID("sss");
char lidBuff[16], eType, *idP, *dP, *eodP, *theIP = 0, *theHost = 0;
int idTLen, idSz, dLen;
// Decode the credentials
//
if ((dLen = Decode(einfo, decKey, cred->buffer, &rrData, cred->size)) <= 0)
return -1;
// Check if we should echo back the LID
//
if (rrData.Options == XrdSecsssRR_Data::SndLID)
{rrData.Options = 0;
getLID(lidBuff, sizeof(lidBuff));
dP = rrData.Data;
*dP++ = XrdSecsssRR_Data::theLgid;
XrdOucPup::Pack(&dP, lidBuff);
*parms = Encode(einfo, decKey, rrHdr, &rrData, dP-(char *)&rrData);
return (*parms ? 1 : -1);
}
// Set the minimum size of the id buffer. This is likely going to wind up
// a bit larger than we need but at least it will big enough.
//
idTLen = (decKey.Data.User[0] ? strlen(decKey.Data.User) : 0);
idTLen += (decKey.Data.Grup[0] ? strlen(decKey.Data.Grup) : 0);
if (idTLen < 16) idTLen = 16;
// Extract out the entity ID
//
dP = rrData.Data; eodP = dLen + (char *)&rrData;
while(dP < eodP)
{eType = *dP++;
if (!XrdOucPup::Unpack(&dP, eodP, &idP, idSz) || *idP == '\0')
{Fatal(einfo, "Authenticate", EINVAL, "Invalid id string.");
return -1;
}
idTLen += idSz;
switch(eType)
{case XrdSecsssRR_Data::theName: myID.name = idP; break;
case XrdSecsssRR_Data::theVorg: myID.vorg = idP; break;
case XrdSecsssRR_Data::theRole: myID.role = idP; break;
case XrdSecsssRR_Data::theGrps: myID.grps = idP; break;
case XrdSecsssRR_Data::theEndo: myID.endorsements = idP; break;
case XrdSecsssRR_Data::theHost: if (*idP == '[') theIP = idP;
else theHost = idP;
break;
case XrdSecsssRR_Data::theRand: idTLen -= idSz; break;
default: break;
}
}
// Verify that we have some kind of identification
//
if (!idTLen)
{Fatal(einfo, "Authenticate", ENOENT, "No id specified.");
return -1;
}
// Verify the source of the information to largely prevent packet stealing. New
// version of the protocol will send an IP address which we prefrentially use.
// Older version used a hostname. This causes problems for multi-homed machines.
//
if (!(decKey.Data.Opts & XrdSecsssKT::ktEnt::noIPCK))
{if (!theHost && !theIP)
{Fatal(einfo,"Authenticate",ENOENT,"No hostname or IP address specified.");
return -1;
}
CLDBG(urName <<' ' <0) cerr <<"; " <setErrInfo(rc, etxt);
CLDBG(epn <<": " <getKey(encKey))
{Fatal(einfo, "getCredentials", ENOENT, "Encryption key not found.");
return (XrdSecCredentials *)0;
}
// Fill out the header
//
strcpy(rrHdr.ProtID, XrdSecPROTOIDENT);
memset(rrHdr.Pad, 0, sizeof(rrHdr.Pad));
rrHdr.KeyID = htonll(encKey.Data.ID);
rrHdr.EncType = Crypto->Type();
// Now simply encode the data and return the result
//
return Encode(einfo, encKey, &rrHdr, &rrData, dLen);
}
/******************************************************************************/
/* I n i t _ C l i e n t */
/******************************************************************************/
namespace
{
XrdSysMutex initMutex;
};
int XrdSecProtocolsss::Init_Client(XrdOucErrInfo *erp, const char *pP)
{
XrdSysMutexHelper initMon(&initMutex);
XrdSecsssKT *ktP;
struct stat buf;
char *Colon;
int lifeTime;
// We must have :[]
//
if (!pP || !*pP) return Fatal(erp, "Init_Client", EINVAL,
"Client parameters missing.");
// Get encryption object
//
if (!*pP || *(pP+1) != '.') return Fatal(erp, "Init_Client", EINVAL,
"Encryption type missing.");
if (!(Crypto = Load_Crypto(erp, *pP))) return 0;
pP += 2;
// The next item is the cred lifetime
//
lifeTime = strtol(pP, &Colon, 10);
if (!lifeTime || *Colon != ':') return Fatal(erp, "Init_Client", EINVAL,
"Credential lifetime missing.");
deltaTime = lifeTime; pP = Colon+1;
// Get the correct keytab
//
if (ktFixed || (ktObject && ktObject->Same(pP))) keyTab = ktObject;
else if (*pP == '/' && !stat(pP, &buf))
{if (!(ktP=new XrdSecsssKT(erp,pP,XrdSecsssKT::isClient,3600)))
return Fatal(erp, "Init_Client", ENOMEM,
"Unable to create keytab object.");
if (erp->getErrInfo()) {delete ktP; return 0;}
if (!ktObject) ktObject = ktP;
keyTab = ktP;
CLDBG("Client keytab='" <getEnv() && ( kP = erp->getEnv()->Get( "xrd.sss" ) ) )
ktFixed = 1;
else if ( ( (kP = getenv("XrdSecSSSKT")) || (kP = getenv("XrdSecsssKT")) )
&& *kP && !stat(kP, &buf))
ktFixed = 1;
else kP = 0;
if (!kP && !stat(KTPath, &buf)) kP = KTPath;
// Build the keytable if we actual have a path (if none, then the server
// will have to supply the path)
//
if (kP)
{if (!(ktObject=new XrdSecsssKT(erp,kP,XrdSecsssKT::isClient,rfrHR)))
{Fatal(erp, "Load_Client", ENOMEM, "Unable to create keytab object.");
return (char *)0;
}
if (erp->getErrInfo())
{delete ktObject, ktObject = 0; return (char *)0;}
CLDBG("Client keytab='" <Type()) return CryptObj;
// Find correct crypto object
//
while(CryptoTab[i].cName && CryptoTab[i].cType != eT) i++;
// If we didn't find it, complain
//
if (!CryptoTab[i].cName)
{sprintf(buff, "Secsss: 0x%hhx cryptography not supported.", eT);
Fatal(erp, "Load_Crypto", EINVAL, buff);
return (XrdCryptoLite *)0;
}
// Return load result
//
if ((cP = XrdCryptoLite::Create(rc, CryptoTab[i].cName, eT))) return cP;
sprintf(buff,"Secsss: 0x%hhx cryptography load failed; %s",eT,strerror(rc));
Fatal(erp, "Load_Crypto", EINVAL, buff);
return (XrdCryptoLite *)0;
}
/******************************************************************************/
/* L o a d _ S e r v e r */
/******************************************************************************/
char *XrdSecProtocolsss::Load_Server(XrdOucErrInfo *erp, const char *parms)
{
const char *msg = 0;
const char *encName = "bf32", *ktClient = "", *ktServer = 0;
char buff[2048], parmbuff[2048], *op, *od, *eP;
int lifeTime = 13, rfrTime = 60*60;
XrdOucTokenizer inParms(parmbuff);
// Duplicate the parms
//
if (parms) strlcpy(parmbuff, parms, sizeof(parmbuff));
// Expected parameters: [-c ] [-e ]
// [-r ] [-l ] [-s ]
//
if (parms && inParms.GetLine())
while((op = inParms.GetToken()))
{if (!(od = inParms.GetToken()))
{sprintf(buff,"Secsss: Missing %s parameter argument",op);
msg = buff; break;
}
if (!strcmp("-c", op)) ktClient = od;
else if (!strcmp("-e", op)) encName = od;
else if (!strcmp("-l", op))
{lifeTime = strtol(od, &eP, 10) * 60;
if (errno || *eP || lifeTime < 1)
{msg = "Secsss: Invalid life time"; break;}
}
else if (!strcmp("-r", op))
{rfrTime = strtol(od, &eP, 10) * 60;
if (errno || *eP || rfrTime < 600)
{msg = "Secsss: Invalid refresh time"; break;}
}
else if (!strcmp("-s", op)) ktServer = od;
else {sprintf(buff,"Secsss: Invalid parameter - %s",op);
msg = buff; break;
}
}
// Check for errors
//
if (msg) {Fatal(erp, "Load_Server", EINVAL, msg); return (char *)0;}
// Load the right crypto object
//
if (!(CryptObj = Load_Crypto(erp, encName))) return (char *)0;
// Supply default keytab location if not specified
//
if (!ktServer) ktServer = XrdSecsssKT::genFN();
// Set the delta time used to expire credentials
//
deltaTime = lifeTime;
// Create a keytab object (only one for the server)
//
if (!(ktObject = new XrdSecsssKT(erp, ktServer, XrdSecsssKT::isServer,
rfrTime)))
{Fatal(erp, "Load_Server", ENOMEM, "Unable to create keytab object.");
return (char *)0;
}
if (erp->getErrInfo()) return (char *)0;
ktFixed = 1;
CLDBG("Server keytab='" <:
//
sprintf(buff, "%c.%d:%s", CryptObj->Type(), lifeTime, ktClient);
CLDBG("client parms='" <= maxLen)
return Fatal(error,"Decode",EINVAL,"Credentials missing or of invalid size.");
// Check if this is a recognized protocol
//
if (strcmp(rrHdr->ProtID, XrdSecPROTOIDENT))
{char emsg[256];
snprintf(emsg, sizeof(emsg),
"Authentication protocol id mismatch (%.4s != %.4s).",
XrdSecPROTOIDENT, rrHdr->ProtID);
return Fatal(error, "Decode", EINVAL, emsg);
}
// Verify decryption method
//
if (rrHdr->EncType != Crypto->Type())
return Fatal(error, "Decode", ENOTSUP, "Crypto type not supported.");
// Get the key
//
decKey.Data.ID = ntohll(rrHdr->KeyID);
decKey.Data.Name[0] = '\0';
if (keyTab->getKey(decKey))
return Fatal(error, "Decode", ENOENT, "Decryption key not found.");
// Decrypt
//
if ((rc = Crypto->Decrypt(decKey.Data.Val, decKey.Data.Len,
iBuff+sizeof(XrdSecsssRR_Hdr), dLen,
(char *)rrData, sizeof(XrdSecsssRR_Data))) <= 0)
return Fatal(error, "Decode", -rc, "Unable to decrypt credentials.");
// Verify that the packet has not expired (OK to do before CRC check)
//
genTime = ntohl(rrData->GenTime);
if (genTime + deltaTime <= myClock())
return Fatal(error, "Decode", ESTALE,
"Credentials expired (check for clock skew).");
// Return success (size of decrypted info)
//
return rc;
}
/******************************************************************************/
/* E n c o d e */
/******************************************************************************/
XrdSecCredentials *XrdSecProtocolsss::Encode(XrdOucErrInfo *einfo,
XrdSecsssKT::ktEnt &encKey,
XrdSecsssRR_Hdr *rrHdr,
XrdSecsssRR_Data *rrData,
int dLen)
{
static const int hdrSZ = sizeof(XrdSecsssRR_Hdr);
XrdOucEnv *errEnv = 0;
char *myIP = 0, *credP, *eodP = ((char *)rrData) + dLen;
char ipBuff[256];
int knum, cLen;
// Make sure we have enought space left in the buffer
//
if (dLen > (int)sizeof(rrData->Data) - (16+myNLen))
{Fatal(einfo,"Encode",ENOBUFS,"Insufficient buffer space for credentials.");
return (XrdSecCredentials *)0;
}
// We first insert our IP address which will be followed by our host name.
// New version of the protocol will use the IP address, older version will
// use the last hostname we actually send.
//
if (einfo && (errEnv = einfo->getEnv()) && (myIP = errEnv->Get("sockname")))
{*eodP++ = XrdSecsssRR_Data::theHost;
if (!strncmp(myIP, "[::ffff:", 8))
{strcpy(ipBuff, "[::"); strcpy(ipBuff+3, myIP+8); myIP = ipBuff;}
XrdOucPup::Pack(&eodP, myIP);
dLen = eodP - (char *)rrData;
} else {
if (epAddr.SockFD() > 0
&& XrdNetUtils::IPFormat(-(epAddr.SockFD()), ipBuff, sizeof(ipBuff),
XrdNetUtils::oldFmt))
{*eodP++ = XrdSecsssRR_Data::theHost;
XrdOucPup::Pack(&eodP, ipBuff);
dLen = eodP - (char *)rrData;
} else {
CLDBG("No IP address to encode (" <<(einfo==0) <<(errEnv==0)
<<(myIP==0) <<")!");
}
}
// Add in our host name for source verification
//
if (myName)
{*eodP++ = XrdSecsssRR_Data::theHost;
XrdOucPup::Pack(&eodP, myName, myNLen);
dLen = eodP - (char *)rrData;
}
// Make sure we have at least 128 bytes of encrypted data
//
if (dLen < 128)
{char rBuff[128];
int rLen = 128 - dLen;
*eodP++ = XrdSecsssRR_Data::theRand;
XrdSecsssKT::genKey(rBuff, rLen);
if (!(*rBuff)) *rBuff = ~(*rBuff);
XrdOucPup::Pack(&eodP, rBuff, rLen);
dLen = eodP - (char *)rrData;
}
// Complete the packet
//
XrdSecsssKT::genKey(rrData->Rand, sizeof(rrData->Rand));
rrData->GenTime = htonl(myClock());
memset(rrData->Pad, 0, sizeof(rrData->Pad));
// Allocate an output buffer
//
cLen = hdrSZ + dLen + Crypto->Overhead();
if (!(credP = (char *)malloc(cLen)))
{Fatal(einfo, "Encode", ENOMEM, "Insufficient memory for credentials.");
return (XrdSecCredentials *)0;
}
// Copy the header and encrypt the data
//
memcpy(credP, (const void *)rrHdr, hdrSZ);
if ((dLen = Crypto->Encrypt(encKey.Data.Val, encKey.Data.Len, (char *)rrData,
dLen, credP+hdrSZ, cLen-hdrSZ)) <= 0)
{Fatal(einfo, "Encode", -dLen, "Unable to encrypt credentials.");
return (XrdSecCredentials *)0;
}
// Return new credentials
//
dLen += hdrSZ; knum = encKey.Data.ID&0x7fffffff;
CLDBG("Ret " <Find(lidP, rrData.Data, sizeof(rrData.Data))) <= 0)
return Fatal(einfo, "getCred", ESRCH, "No loginid mapping.");
// All done
//
rrData.Options = XrdSecsssRR_Data::UseData;
return XrdSecsssRR_Data_HdrLen + dLen;
}
/******************************************************************************/
/* g e t L I D */
/******************************************************************************/
char *XrdSecProtocolsss::getLID(char *buff, int blen)
{
const char *dot;
// Extract out the loginid from the trace id
//
if (!Entity.tident
|| !(dot = index(Entity.tident,'.'))
|| dot == Entity.tident
|| dot >= (Entity.tident+blen)) strcpy(buff,"nobody");
else {int idsz = dot - Entity.tident;
strncpy(buff, Entity.tident, idsz);
*(buff+idsz) = '\0';
}
// All done
//
return buff;
}
/******************************************************************************/
/* m y C l o c k */
/******************************************************************************/
int XrdSecProtocolsss::myClock()
{
static const time_t baseTime = 1222183880;
return static_cast(time(0)-baseTime);
}
/******************************************************************************/
/* s e t I D */
/******************************************************************************/
char *XrdSecProtocolsss::setID(char *id, char **idP)
{
if (id)
{int n = strlen(id);
strcpy(*idP, id); id = *idP; *idP = *idP + n + 1;
}
return id;
}
/******************************************************************************/
/* s e t I P */
/******************************************************************************/
void XrdSecProtocolsss::setIP(XrdNetAddrInfo &endPoint)
{
if (!endPoint.Format(urIP, sizeof(urIP), XrdNetAddrInfo::fmtAdv6)) *urIP=0;
if (!endPoint.Format(urIQ, sizeof(urIQ), XrdNetAddrInfo::fmtAdv6,
XrdNetAddrInfo::old6Map4)) *urIQ=0;
epAddr = endPoint;
Entity.addrInfo = &epAddr;
}
/******************************************************************************/
/* X r d S e c P r o t o c o l s s s I n i t */
/******************************************************************************/
extern "C"
{
char *XrdSecProtocolsssInit(const char mode,
const char *parms,
XrdOucErrInfo *erp)
{
// Set debug option
//
if (getenv("XrdSecDEBUG")) XrdSecProtocolsss::setOpts(XrdSecDEBUG);
// Perform load-time initialization
//
return (mode == 'c' ? XrdSecProtocolsss::Load_Client(erp, parms)
: XrdSecProtocolsss::Load_Server(erp, parms));
}
}
/******************************************************************************/
/* X r d S e c P r o t o c o l s s s O b j e c t */
/******************************************************************************/
XrdVERSIONINFO(XrdSecProtocolsssObject,secsss);
extern "C"
{
XrdSecProtocol *XrdSecProtocolsssObject(const char mode,
const char *hostname,
XrdNetAddrInfo &endPoint,
const char *parms,
XrdOucErrInfo *erp)
{
XrdSecProtocolsss *prot;
int Ok;
// Get a new protocol object
//
if (!(prot = new XrdSecProtocolsss(endPoint.Name(hostname), endPoint)))
XrdSecProtocolsss::Fatal(erp, "sss_Object", ENOMEM,
"Secsss: Insufficient memory for protocol.");
else {Ok = (mode == 'c' ? prot->Init_Client(erp, parms)
: prot->Init_Server(erp, parms));
if (!Ok) {prot->Delete(); prot = 0;}
}
// All done
//
return (XrdSecProtocol *)prot;
}
}