/******************************************************************************/ /* */ /* 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; } }