/******************************************************************************/ /* */ /* X r d S e c s s s K T . 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 "XrdSecsss/XrdSecsssKT.hh" #include "XrdOuc/XrdOucErrInfo.hh" #include "XrdOuc/XrdOucStream.hh" #include "XrdOuc/XrdOucUtils.hh" #include "XrdSys/XrdSysHeaders.hh" /******************************************************************************/ /* S t a t i c D e f i n i t i o n s */ /******************************************************************************/ int XrdSecsssKT::randFD = -1; /******************************************************************************/ /* X r d S e c s s s K T R e f r */ /******************************************************************************/ void *XrdSecsssKTRefresh(void *Data) { XrdSecsssKT *theKT = (XrdSecsssKT *)Data; struct timespec naptime = {theKT->RefrTime(), 0}; // Loop and check if keytab has changed // while(1) {nanosleep(&naptime, 0); theKT->Refresh();} return (void *)0; } /******************************************************************************/ /* C o n s t r u c t o r */ /******************************************************************************/ XrdSecsssKT::XrdSecsssKT(XrdOucErrInfo *eInfo, const char *kPath, xMode oMode, int refrInt) { static const char *eText = "Unable to start keytab refresh thread"; const char *devRand = "/dev/urandom"; struct stat sbuf; int retc; // Do some common initialization // ktRefID= 0; ktPath = (kPath ? strdup(kPath) : 0); ktList = 0; kthiID = 0; ktMode = oMode; ktRefT = (time_t)refrInt; if (eInfo) eInfo->setErrCode(0); // Prepare /dev/random if we have it // if (stat(devRand, &sbuf)) devRand = "/dev/random"; if ((randFD = open(devRand, O_RDONLY)) < 0 && oMode != isClient && errno != ENOENT) eMsg("sssKT",errno,"Unable to generate random key"," opening ",devRand); // First get the stat information for the file // if (!kPath) {if (oMode != isAdmin) {eMsg("sssKT", -1, "Keytable path not specified."); if (eInfo) eInfo->setErrInfo(EINVAL, "Keytable path missing."); return; } sbuf.st_mtime = 0; sbuf.st_mode = S_IRWXU; } else if (stat(kPath, &sbuf)) {if (eInfo) eInfo->setErrInfo(errno, "Keytable not found"); if (errno != ENOENT || oMode != isAdmin) eMsg("sssKT",errno,"Unable process keytable ",kPath); return; } // Now read in the whole key table and start possible refresh thread // if ((ktList = getKeyTab(eInfo, sbuf.st_mtime, sbuf.st_mode)) && (oMode != isAdmin) && (!eInfo || eInfo->getErrInfo() == 0)) {if ((retc = XrdSysThread::Run(&ktRefID,XrdSecsssKTRefresh, (void *)this, XRDSYSTHREAD_HOLD))) {eMsg("sssKT", errno, eText); eInfo->setErrInfo(-1, eText);} } } /******************************************************************************/ /* D e s t r u c t o r */ /******************************************************************************/ XrdSecsssKT::~XrdSecsssKT() { ktEnt *ktP; void *Dummy; // Lock against others // myMutex.Lock(); // Kill the refresh thread first // if (ktRefID && !XrdSysThread::Kill(ktRefID)) XrdSysThread::Join(ktRefID, &Dummy); ktRefID= 0; // Now we can safely clean up // if (ktPath) {free(ktPath); ktPath = 0;} while((ktP = ktList)) {ktList = ktList->Next; delete ktP;} myMutex.UnLock(); } /******************************************************************************/ /* a d d K e y */ /******************************************************************************/ void XrdSecsssKT::addKey(ktEnt &ktNew) { ktEnt *ktPP = 0, *ktP; // Generate a key for this entry // genKey(ktNew.Data.Val, ktNew.Data.Len); ktNew.Data.Crt = time(0); ktNew.Data.ID = static_cast(ktNew.Data.Crt & 0x7fffffff) << 32L | static_cast(++kthiID); // Locate place to insert this key // ktP = ktList; while(ktP && !isKey(*ktP, &ktNew, 0)) {ktPP = ktP; ktP = ktP->Next;} // Now chain in the entry // if (ktPP) ktPP->Next = &ktNew; else ktList = &ktNew; ktNew.Next = ktP; } /******************************************************************************/ /* d e l K e y */ /******************************************************************************/ int XrdSecsssKT::delKey(ktEnt &ktDel) { ktEnt *ktN, *ktPP = 0, *ktP = ktList; int nDel = 0; // Remove all matching keys // while(ktP) {if (isKey(ktDel, ktP)) {if (ktPP) ktPP->Next = ktP->Next; else ktList = ktP->Next; ktN = ktP; ktP = ktP->Next; delete ktN; nDel++; } else {ktPP = ktP; ktP = ktP->Next;} } return nDel; } /******************************************************************************/ /* g e t K e y */ /******************************************************************************/ int XrdSecsssKT::getKey(ktEnt &theEnt) { ktEnt *ktP, *ktN; // Lock the keytab to prevent modification // myMutex.Lock(); ktP = ktList; // Find first key by key name (used normally by clients) or by keyID // if (!*theEnt.Data.Name) {if (theEnt.Data.ID >= 0) while(ktP && ktP->Data.ID != theEnt.Data.ID) ktP = ktP->Next; } else {while(ktP && strcmp(ktP->Data.Name,theEnt.Data.Name)) ktP=ktP->Next; while(ktP && ktP->Data.Exp <= time(0)) {if (!(ktN=ktP->Next) || strcmp(ktN->Data.Name,theEnt.Data.Name)) break; ktP = ktN; } } // If we found a match, export it // if (ktP) theEnt = *ktP; myMutex.UnLock(); // Indicate if key expired // if (!ktP) return ENOENT; return (theEnt.Data.Exp && theEnt.Data.Exp <= time(0) ? -1 : 0); } /******************************************************************************/ /* g e n F N */ /******************************************************************************/ char *XrdSecsssKT::genFN() { static char fnbuff[1040]; const char *pfx; // Get the path prefix // if (!(pfx = getenv("HOME")) || !*pfx) pfx = ""; // Format the name // snprintf(fnbuff, sizeof(fnbuff), "%s/.xrd/sss.keytab", pfx); return fnbuff; } /******************************************************************************/ /* g e n K e y */ /******************************************************************************/ void XrdSecsssKT::genKey(char *kBP, int kLen) { struct timeval tval; int kTemp; // See if we can directly service the key. Make sure that we get some entropy // because some /dev/random devices start out really cold. // if (randFD >= 0) {char *buffP = kBP; int i, Got, Want = kLen, zcnt = 0, maxZ = kLen*25/100; while(Want) do { {do {Got = read(randFD, buffP, Want);} while(Got < 0 && errno == EINTR); if (Got > 0) {buffP += Got; Want -= Got;} } } while(Got > 0 && Want); if (!Want) {for (i = 0; i < kLen; i++) if (!kBP[i]) zcnt++; if (zcnt <= maxZ) return; } } // Generate a seed // gettimeofday(&tval, 0); if (tval.tv_usec == 0) tval.tv_usec = tval.tv_sec; tval.tv_usec = tval.tv_usec ^ getpid(); srand48(static_cast(tval.tv_usec)); // Now generate the key (we ignore he fact that longs may be 4 or 8 bytes) // while(kLen > 0) {kTemp = mrand48(); memcpy(kBP, &kTemp, (4 > kLen ? kLen : 4)); kBP += 4; kLen -= 4; } } /******************************************************************************/ /* R e f r e s h */ /******************************************************************************/ void XrdSecsssKT::Refresh() { XrdOucErrInfo eInfo; ktEnt *ktNew, *ktOld, *ktNext; struct stat sbuf; int retc = 0; // Get change time of keytable and if changed, update it // if (stat(ktPath, &sbuf) == 0) {if (sbuf.st_mtime == ktMtime) return; if ((ktNew = getKeyTab(&eInfo, sbuf.st_mtime, sbuf.st_mode)) && eInfo.getErrInfo() == 0) {myMutex.Lock(); ktOld = ktList; ktList = ktNew; myMutex.UnLock(); } else ktOld = ktNew; while(ktOld) {ktNext = ktOld->Next; delete ktOld; ktOld = ktNext;} if ((retc == eInfo.getErrInfo()) == 0) return; } else retc = errno; // Refresh failed // eMsg("Refresh",retc,"Unable to refresh keytable",ktPath); } /******************************************************************************/ /* R e w r i t e */ /******************************************************************************/ int XrdSecsssKT::Rewrite(int Keep, int &numKeys, int &numTot, int &numExp) { char tmpFN[2048], buff[2048], kbuff[4096], *Slash; int ktFD, numID = 0, n, retc = 0; ktEnt ktCurr, *ktP, *ktN; mode_t theMode = fileMode(ktPath); // Invoke mkpath in case the path is missing // strcpy(tmpFN, ktPath); if ((Slash = rindex(tmpFN, '/'))) *Slash = '\0'; retc = XrdOucUtils::makePath(tmpFN,S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); if (retc) return (retc < 0 ? -retc : retc); if (Slash) *Slash = '/'; // Construct temporary filename // sprintf(buff, ".%d", static_cast(getpid())); strcat(tmpFN, buff); // Open the file for output // if ((ktFD = open(tmpFN, O_WRONLY|O_CREAT|O_TRUNC, theMode)) < 0) return errno; // Write all of the keytable // ktCurr.Data.Name[0] = ktCurr.Data.User[0] = ktCurr.Data.Grup[0] = 3; ktN = ktList; numKeys = numTot = numExp = 0; while((ktP = ktN)) {ktN = ktN->Next; numTot++; if (ktP->Data.Name[0] == '\0') continue; if (ktP->Data.Exp && ktP->Data.Exp <= time(0)) {numExp++; continue;} if (!isKey(ktCurr, ktP, 0)) {ktCurr.NUG(ktP); numID = 0;} else if (Keep && numID >= Keep) continue; n = sprintf(buff, "%s0 u:%s g:%s n:%s N:%lld c:%ld e:%ld f:%lld k:", (numKeys ? "\n" : ""), ktP->Data.User,ktP->Data.Grup,ktP->Data.Name,ktP->Data.ID, ktP->Data.Crt, ktP->Data.Exp, ktP->Data.Flags); numID++; numKeys++; keyB2X(ktP, kbuff); if (write(ktFD, buff, n) < 0 || write(ktFD, kbuff, ktP->Data.Len*2) < 0) break; } // Check for errors // if (ktP) retc = errno; else if (!numKeys) retc = ENODATA; // Atomically trounce the original file if we can // close(ktFD); if (!retc && rename(tmpFN, ktPath) < 0) retc = errno; // All done // unlink(tmpFN); return retc; } /******************************************************************************/ /* P r i v a t e M e t h o d s */ /******************************************************************************/ /******************************************************************************/ /* e M s g */ /******************************************************************************/ int XrdSecsssKT::eMsg(const char *epname, int rc, const char *txt1, const char *txt2, const char *txt3, const char *txt4) { cerr <<"Secsss (" << epname <<"): "; cerr <0) {cerr <<"; " <setErrInfo(EACCES, "Keytab file is not secure!"); eMsg("getKeyTab",-1,"Unable to process ",ktPath,"; file is not secure!"); return 0; } // Open the file // if (ktPath) {if ((ktFD = open(ktPath, O_RDONLY)) < 0) {if (eInfo) eInfo->setErrInfo(errno, "Unable to open keytab file."); eMsg("getKeyTab", errno, "Unable to open ", ktPath); return 0; } else ktFN = ktPath; } else {ktFD = dup(STDIN_FILENO); ktFN = "stdin";} // Attach the fd to the stream // myKT.Attach(ktFD); // Now start reading the keytable which always has the form: // // // do{while((lp = myKT.GetLine())) {recno++; What = 0; if (!*lp) continue; if (!(tp = myKT.GetToken()) || (strcmp("0", tp) && strcmp("1", tp))) {What = "keytable format missing or unsupported"; break;} if (!(ktNew = ktDecode0(myKT, eInfo))) {What = (eInfo ? eInfo->getErrText(): "invalid data"); break;} if (ktMode!=isAdmin && ktNew->Data.Exp && ktNew->Data.Exp <= time(0)) {delete ktNew; continue;} tmpID = static_cast(ktNew->Data.ID & 0x7fffffff); if (tmpID > kthiID) kthiID = tmpID; ktP = ktBase; ktPP = 0; while(ktP && !isKey(*ktP, ktNew, 0)) {ktPP=ktP; ktP=ktP->Next;} if (!ktP) {ktNew->Next = ktBase; ktBase = ktNew;} else {if (ktMode == isClient) {if ((ktNew->Data.Exp == 0 && ktP->Data.Exp != 0) || (ktP->Data.Exp!=0 && ktP->Data.Exp < ktNew->Data.Exp)) ktP->Set(*ktNew); delete ktNew; } else { while(ktNew->Data.Crt < ktP->Data.Crt) {ktPP = ktP; ktP = ktP->Next; if (!ktP || !isKey(*ktP, ktNew, 0)) break; } if (ktPP) {ktPP->Next = ktNew; ktNew->Next = ktP;} else {ktNew->Next= ktBase; ktBase = ktNew;} } } } if (What) {sprintf(rbuff, "; line %d in ", recno); NoGo = eMsg("getKeyTab", -1, What, rbuff, ktFN); } } while(lp); // Check for problems // if (NoGo) {if (eInfo) eInfo->setErrInfo(EINVAL,"Invalid keytab file.");} else if ((retc = myKT.LastError())) {if (eInfo) eInfo->setErrInfo(retc,"Unable to read keytab file."); NoGo = eMsg("getKeyTab", retc, "Unable to read keytab ",ktFN); } else if (!ktBase) {if (eInfo) eInfo->setErrInfo(ESRCH,"Keytable is empty."); NoGo = eMsg("getKeyTab",-1,"No keys found in ",ktFN); } // Check if an error should be returned // if (!NoGo) eInfo->setErrCode(0); // All done // myKT.Close(); return ktBase; } /******************************************************************************/ /* g r p F i l e */ /******************************************************************************/ mode_t XrdSecsssKT::fileMode(const char *Path) { int n; return (!Path || (n = strlen(Path)) < 5 || strcmp(".grp", &Path[n-4]) ? S_IRUSR|S_IWUSR : S_IRUSR|S_IWUSR|S_IRGRP); } /******************************************************************************/ /* i s K e y */ /******************************************************************************/ int XrdSecsssKT::isKey(ktEnt &ktRef, ktEnt *ktP, int Full) { if (*ktRef.Data.Name && strcmp(ktP->Data.Name, ktRef.Data.Name)) return 0; if (*ktRef.Data.User && strcmp(ktP->Data.User, ktRef.Data.User)) return 0; if (*ktRef.Data.Grup && strcmp(ktP->Data.Grup, ktRef.Data.Grup)) return 0; if (Full && ktRef.Data.ID > 0 && (ktP->Data.ID & 0x7fffffff) != ktRef.Data.ID) return 0; return 1; } /******************************************************************************/ /* k e y B 2 X */ /******************************************************************************/ void XrdSecsssKT::keyB2X(ktEnt *theKT, char *buff) { static const char xTab[] = "0123456789abcdef"; int kLen = theKT->Data.Len; char *kP = theKT->Data.Val, Val; // Convert // while(kLen--) {Val = *kP++; *buff++ = xTab[(Val>>4) & 0x0f]; *buff++ = xTab[ Val & 0x0f]; } *buff = '\0'; } /******************************************************************************/ /* k e y X 2 B */ /******************************************************************************/ void XrdSecsssKT::keyX2B(ktEnt *theKT, char *xKey) { // 0 1 2 3 4 5 6 7 static const char xtab[] = {10, 10, 11, 12, 13, 14, 15, 15}; int n = strlen(xKey); char *kp, kByte; // Make sure we don't overflow // n = (n%2 ? (n+1)/2 : n/2); if (n > ktEnt::maxKLen) n = ktEnt::maxKLen; kp = theKT->Data.Val; theKT->Data.Val[n-1] = 0; // Now convert (we need this to be just consistent not necessarily correct) // while(*xKey) {if (*xKey <= '9') kByte = (*xKey & 0x0f) << 4; else kByte = xtab[*xKey & 0x07] << 4; xKey++; if (*xKey <= '9') kByte |= (*xKey & 0x0f); else kByte |= xtab[*xKey & 0x07]; *kp++ = kByte; xKey++; } // Return data via the structure // theKT->Data.Len = n; } /******************************************************************************/ /* k t D e c o d e 0 */ /******************************************************************************/ XrdSecsssKT::ktEnt *XrdSecsssKT::ktDecode0(XrdOucStream &kTab, XrdOucErrInfo *eInfo) { static const short haveCRT = 0x0001; static const short haveEXP = 0x0002; static const short isTIMET = 0x0003; static const short haveGRP = 0x0004; static const short haveKEY = 0x0008; static const short haveNAM = 0x0010; static const short haveNUM = 0x0020; static const short haveUSR = 0x0040; static const short haveFLG = 0x0080; static struct {const char *Name; size_t Offset; int Ctl; short What; char Tag;} ktDesc[] = { {"crtdt", offsetof(ktEnt::ktData,Crt), 0, haveCRT, 'c'}, {"expdt", offsetof(ktEnt::ktData,Exp), 0, haveEXP, 'e'}, {"flags", offsetof(ktEnt::ktData,Flags),0, haveFLG, 'f'}, {"group", offsetof(ktEnt::ktData,Grup), ktEnt::GrupSZ, haveGRP, 'g'}, {"keyval", offsetof(ktEnt::ktData,Val), ktEnt::maxKLen*2, haveKEY, 'k'}, {"keyname", offsetof(ktEnt::ktData,Name), ktEnt::NameSZ, haveNAM, 'n'}, {"keynum", offsetof(ktEnt::ktData,ID), 0, haveNUM, 'N'}, {"user", offsetof(ktEnt::ktData,User), ktEnt::UserSZ, haveUSR, 'u'} }; static const int ktDnum = sizeof(ktDesc)/sizeof(ktDesc[0]); ktEnt *ktNew = new ktEnt; const char *Prob = 0, *What = "Whatever"; char Tag, *Dest, *ep, *tp; long long nVal; short Have = 0; int i = 0; // Decode the record using the tags described in the above table // while((tp = kTab.GetToken()) && !Prob) {Tag = *tp++; if (*tp++ == ':') for (i = 0; i < ktDnum; i++) if (ktDesc[i].Tag == Tag) {Dest = (char *)&(ktNew->Data) + ktDesc[i].Offset; Have |= ktDesc[i].What; What = ktDesc[i].Name; if (ktDesc[i].Ctl) {if ((int)strlen(tp) > ktDesc[i].Ctl) Prob=" is too long"; else if (Tag == 'k') keyX2B(ktNew, tp); else strcpy(Dest, tp); } else { nVal = strtoll(tp, &ep, 10); if (ep && *ep) Prob = " has invalid value"; else if (ktDesc[i].What & isTIMET) *(time_t *)Dest = static_cast(nVal); else *(long long *)Dest = nVal; } } } // If no problem, make sure we have the essential elements // if (!Prob) {if (!(Have & haveGRP)) strcpy(ktNew->Data.Grup, "nogroup"); if (!(Have & haveNAM)) strcpy(ktNew->Data.Name, "nowhere"); else {int n = strlen(ktNew->Data.Name); if (ktNew->Data.Name[n-1] == '+') ktNew->Data.Opts |= ktEnt::noIPCK; } if (!(Have & haveUSR)) strcpy(ktNew->Data.User, "nobody"); if (!(Have & haveKEY)) {What = "keyval"; Prob = " not found";} else if (!(Have & haveNUM)) {What = "keynum"; Prob = " not found";} } // Check if we have a problem // if (Prob) {const char *eVec[] = {What, Prob}; if (eInfo) eInfo->setErrInfo(-1, eVec, 2); delete ktNew; return 0; } // Set special value options // if (!strcmp(ktNew->Data.Grup, "anygroup")) ktNew->Data.Opts|=ktEnt::anyGRP; else if (!strcmp(ktNew->Data.Grup, "usrgroup")) ktNew->Data.Opts|=ktEnt::usrGRP; if (!strcmp(ktNew->Data.User, "anybody")) ktNew->Data.Opts|=ktEnt::anyUSR; // All done // return ktNew; }