/******************************************************************************/ /* */ /* X r d S e c P r o t o c o l p w d . c c */ /* */ /* (c) 2005 by the Board of Trustees of the Leland Stanford, Jr., University */ /* Produced by Gerri Ganis for 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. */ /******************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include "XrdVersion.hh" #include "XrdSys/XrdSysHeaders.hh" #include "XrdSys/XrdSysLogger.hh" #include "XrdSys/XrdSysError.hh" #include "XrdSys/XrdSysPwd.hh" #include "XrdOuc/XrdOucStream.hh" #include "XrdSys/XrdSysPriv.hh" #include "XrdSut/XrdSutPFCache.hh" #include "XrdSecpwd/XrdSecProtocolpwd.hh" #include "XrdSecpwd/XrdSecpwdPlatform.hh" /******************************************************************************/ /* T r a c i n g I n i t O p t i o n s */ /******************************************************************************/ #ifndef NODEBUG #define POPTS(t,y) {if (t) {t->Beg(epname); cerr <End();}} #else #define POPTS(t,y) #endif /******************************************************************************/ /* S t a t i c D a t a */ /******************************************************************************/ static String Prefix = "xrd"; static String ProtoID = XrdSecPROTOIDENT; static const kXR_int32 Version = XrdSecpwdVERSION; static String AdminRef = ProtoID + "admin"; static String SrvPukRef= ProtoID + "srvpuk"; static String UserRef = ProtoID + "user"; static String NetRcRef = ProtoID + "netrc"; static const char *pwdClientSteps[] = { "kXPC_none", "kXPC_normal", "kXPC_verifysrv", "kXPC_signedrtag", "kXPC_creds", "kXPC_autoreg", "kXPC_failureack", "kXPC_reserved" }; static const char *pwdServerSteps[] = { "kXPS_none", "kXPS_init", "kXPS_credsreq", "kXPS_rtag", "kXPS_signedrtag", "kXPS_newpuk", "kXPS_puk", "kXPS_failure", "kXPS_reserved" }; static const char *gPWErrStr[] = { "parsing buffer", // 10000 "decoding buffer", // 10001 "loading crypto factory", // 10002 "protocol mismatch", // 10003 "resolving user / host", // 10004 "user missing", // 10005 "host missing", // 10006 "unknown user", // 10007 "creating bucket", // 10008 "duplicating bucket", // 10009 "creating buffer", // 10010 "serializing buffer", // 10011 "generating cipher", // 10012 "exporting public key", // 10013 "encrypting random tag", // 10014 "random tag mismatch", // 10015 "random tag missing", // 10016 "cipher missing", // 10017 "getting credentials", // 10018 "credentials missing", // 10019 "wrong password for user", // 10020 "checking cache", // 10021 "cache entry for link missing", // 10022 "session handshaking ID missing", // 10023 "session handshaking ID mismatch", // 10024 "unknown step option", // 10025 "marshaling integer", // 10026 "unmarshaling integer", // 10027 "saving new credentials", // 10028 "salt missing", // 10029 "buffer empty", // 10030 "obtaining reference cipher", // 10031 "obtaining cipher public info", // 10032 "adding bucket to list", // 10033 "finalizing cipher from public info", // 10034 "error during initialization", // 10035 "wrong credentials", // 10035 "error" // 10036 }; // Masks for options static const short kOptsServer = 0x0001; static const short kOptsUserPwd = 0x0002; static const short kOptsAutoReg = 0x0004; static const short kOptsAregAll = 0x0008; static const short kOptsVeriSrv = 0x0020; static const short kOptsVeriClt = 0x0040; static const short kOptsClntTty = 0x0080; static const short kOptsExpCred = 0x0100; static const short kOptsCrypPwd = 0x0200; static const short kOptsChngPwd = 0x0400; static const short kOptsAFSPwd = 0x0800; // One day in secs static const int kOneDay = 86400; /******************************************************************************/ /* S t a t i c C l a s s D a t a */ /******************************************************************************/ XrdSysMutex XrdSecProtocolpwd::pwdContext; String XrdSecProtocolpwd::FileAdmin= ""; String XrdSecProtocolpwd::FileExpCreds= ""; String XrdSecProtocolpwd::FileUser = ""; String XrdSecProtocolpwd::FileCrypt= "/.xrdpass"; String XrdSecProtocolpwd::FileSrvPuk= ""; String XrdSecProtocolpwd::SrvID = ""; String XrdSecProtocolpwd::SrvEmail = ""; String XrdSecProtocolpwd::DefCrypto= "ssl"; String XrdSecProtocolpwd::DefError = "insufficient credentials - contact "; XrdSutPFile XrdSecProtocolpwd::PFAdmin(0); // Admin file (server) XrdSutPFile XrdSecProtocolpwd::PFAlog(0); // Autologin file (client) XrdSutPFile XrdSecProtocolpwd::PFSrvPuk(0); // File with server public keys (client) // // Crypto related info int XrdSecProtocolpwd::ncrypt = 0; // Number of factories int XrdSecProtocolpwd::cryptID[XrdCryptoMax] = {0}; // their IDs String XrdSecProtocolpwd::cryptName[XrdCryptoMax] = {0}; // their names XrdCryptoCipher *XrdSecProtocolpwd::refcip[XrdCryptoMax] = {0}; // ref for session ciphers // // Caches for info files XrdSutPFCache XrdSecProtocolpwd::cacheAdmin; // Admin file XrdSutPFCache XrdSecProtocolpwd::cacheSrvPuk; // SrvPuk file XrdSutPFCache XrdSecProtocolpwd::cacheUser; // User files XrdSutPFCache XrdSecProtocolpwd::cacheAlog; // Autologin file // // Running options / settings int XrdSecProtocolpwd::Debug = 0; // [CS] Debug level bool XrdSecProtocolpwd::Server = 1; // [CS] If server mode int XrdSecProtocolpwd::UserPwd = 0; // [S] Check passwd file in user's bool XrdSecProtocolpwd::SysPwd = 0; // [S] Check passwd file in user's int XrdSecProtocolpwd::VeriClnt = 2; // [S] Client authenticity verification level: // 0 none, 1 timestamp, 2 random tag int XrdSecProtocolpwd::VeriSrv = 1; // [C] Server authenticity verification level: // 0 none, 1 random tag int XrdSecProtocolpwd::AutoReg = kpAR_none; // [S] Autoreg mode int XrdSecProtocolpwd::LifeCreds = 0; // [S] if > 0, time interval of validity for creds int XrdSecProtocolpwd::MaxPrompts = 3; // [C] Repeating prompt int XrdSecProtocolpwd::MaxFailures = 10;// [S] Max passwd failures before blocking int XrdSecProtocolpwd::AutoLogin = 0; // [C] do-not-check/check/update autologin info int XrdSecProtocolpwd::TimeSkew = 300; // [CS] Allowed skew in secs for time stamps bool XrdSecProtocolpwd::KeepCreds = 0; // [S] Keep / Do-Not-Keep client creds int XrdSecProtocolpwd::FmtExpCreds = 0; // [S] Format for exported credentials // // Debug an tracing XrdSysError XrdSecProtocolpwd::eDest(0, "secpwd_"); XrdSysLogger XrdSecProtocolpwd::Logger; XrdOucTrace *XrdSecProtocolpwd::PWDTrace = 0; XrdOucTrace *pwdTrace = 0; /******************************************************************************/ /* S t a t i c F u n c t i o n s */ /******************************************************************************/ //_____________________________________________________________________________ static const char *ClientStepStr(int kclt) { // Return string with client step static const char *ukn = "Unknown"; kclt = (kclt < 0) ? 0 : kclt; kclt = (kclt > kXPC_reserved) ? 0 : kclt; kclt = (kclt >= kXPC_normal) ? (kclt - kXPC_normal + 1) : kclt; if (kclt < 0 || kclt > (kXPC_reserved - kXPC_normal + 1)) return ukn; else return pwdClientSteps[kclt]; } //_____________________________________________________________________________ static const char *ServerStepStr(int ksrv) { // Return string with server step static const char *ukn = "Unknown"; ksrv = (ksrv < 0) ? 0 : ksrv; ksrv = (ksrv > kXPS_reserved) ? 0 : ksrv; ksrv = (ksrv >= kXPS_init) ? (ksrv - kXPS_init + 1) : ksrv; if (ksrv < 0 || ksrv > (kXPS_reserved - kXPS_init + 1)) return ukn; else return pwdServerSteps[ksrv]; } /******************************************************************************/ /* P r o t o c o l I n i t i a l i z a t i o n M e t h o d s */ /******************************************************************************/ //_____________________________________________________________________________ XrdSecProtocolpwd::XrdSecProtocolpwd(int opts, const char *hname, XrdNetAddrInfo &endPoint, const char *parms) : XrdSecProtocol("pwd") { // Default constructor EPNAME("XrdSecProtocolpwd"); if (QTRACE(Authen)) { PRINT("constructing: "<TimeStamp = time(0); // Local handshake variables hs->CryptoMod = ""; // crypto module in use hs->User = ""; // remote username hs->Tag.resize(256); // tag for credentials hs->RemVers = -1; // Version run by remote counterpart hs->CF = 0; // crypto factory hs->Hcip = 0; // handshake cipher hs->Rcip = 0; // reference cipher hs->ID = ""; // Handshake ID (dummy for clients) hs->Cref = 0; // Cache reference hs->Pent = 0; // Pointer to relevant file entry hs->RtagOK = 0; // Rndm tag checked / not checked hs->Tty = (isatty(0) == 0 || isatty(1) == 0) ? 0 : 1; hs->Step = 0; // Current step hs->LastStep = 0; // Step required at previous iteration } else { PRINT("could not create handshake vars object"); } // Used by servers to store forwarded credentials clientCreds = 0; // Save host name and address if (hname) { Entity.host = strdup(hname); } else { NOTIFY("warning: host name undefined"); } epAddr = endPoint; Entity.addrInfo = &epAddr; // Init client name CName[0] = '?'; CName[1] = '\0'; // // Notify, if required DEBUG("constructing: host: "< 0) { DEBUG("using autologin file: "< 1) { DEBUG("running in update-autologin mode"); } } if (VeriSrv > 0) { DEBUG("server verification ON"); } else { DEBUG("server verification OFF"); } // Decode received buffer if (parms) { XrdOucString p("&P=pwd,"); p += parms; hs->Parms = new XrdSutBuffer(p.c_str(), p.length()); } } // We are done String vers = Version; vers.insert('.',vers.length()-2); vers.insert('.',vers.length()-5); DEBUG("object created: v"< -1) ? opt.debug : Debug; // We must have the tracing object at this point // (initialized in XrdSecProtocolgsiInit) if (!pwdTrace) { ErrF(erp,kPWErrInit,"tracing object (pwdTrace) not initialized! cannot continue"); return Parms; } // Set debug mask ... also for auxilliary libs int trace = 0, traceSut = 0, traceCrypto = 0; if (Debug >= 3) { trace = cryptoTRACE_Dump; traceSut = sutTRACE_Dump; traceCrypto = cryptoTRACE_Dump; PWDTrace->What = TRACE_ALL; } else if (Debug >= 2) { trace = cryptoTRACE_Debug; traceSut = sutTRACE_Debug; traceCrypto = cryptoTRACE_Debug; PWDTrace->What = TRACE_Debug; PWDTrace->What |= TRACE_Authen; } else if (Debug >= 1) { trace = cryptoTRACE_Debug; traceSut = sutTRACE_Notify; traceCrypto = cryptoTRACE_Notify; PWDTrace->What = TRACE_Debug; } // ... also for auxilliary libs XrdSutSetTrace(traceSut); XrdCryptoSetTrace(traceCrypto); // Get user info struct passwd *pw; XrdSysPwd thePwd(getuid(), &pw); if (!pw) { PRINT("no user info available - invalid "); ErrF(erp, kPWErrInit, "could not get user info from pwuid"); return Parms; } // // Operation mode Server = (opt.mode == 's'); // // Directory with admin pwd files bool argdir = 0; String infodir(512); if (opt.dir) { infodir = opt.dir; // Expand if (XrdSutExpand(infodir) != 0) { PRINT("cannot expand "< infodir = XrdSutHome(); infodir += ("/." + Prefix); } if (!infodir.endswith("/")) infodir += "/"; // // If defined, check existence of the infodir and admin file if (infodir.length()) { // Acquire the privileges, if needed XrdSysPrivGuard priv(pw->pw_uid, pw->pw_gid); if (priv.Valid()) { struct stat st; if (stat(infodir.c_str(),&st) == -1) { if (errno == ENOENT) { if (argdir) { DEBUG("infodir non existing: "< -1) ? opt.areg : AutoReg; // // Client verification level VeriClnt = (opt.vericlnt > -1) ? opt.vericlnt : VeriClnt; // // Whether to check pwd files in users' $HOME UserPwd = (opt.upwd > -1) ? opt.upwd : UserPwd; // // Whether to check system pwd files (if allowed) SysPwd = (opt.syspwd > -1) ? opt.syspwd : SysPwd; if (SysPwd) { // Make sure this setting makes sense if (pw) { #ifdef HAVE_SHADOWPW // Acquire the privileges, if needed XrdSysPrivGuard priv((uid_t) 0, (gid_t) 0); if (priv.Valid()) { // System V Rel 4 style shadow passwords struct spwd *spw = getspnam(pw->pw_name); if (!spw) { SysPwd = 0; DEBUG("no privileges to access shadow passwd file"); } } else { DEBUG("problems acquiring credentials" " to access the system password file"); } #else // Normal passwd file if (!pw->pw_passwd && (pw->pw_passwd && strlen(pw->pw_passwd) <= 1)) { SysPwd = 0; DEBUG("no privileges to access system passwd file"); } #endif } else SysPwd = 0; } // // Credential lifetime LifeCreds = (opt.lifecreds > -1) ? opt.lifecreds : LifeCreds; // // Max number of failures MaxFailures = (opt.maxfailures > -1) ? opt.maxfailures : MaxFailures; // // If defined, check existence of the infodir and admin file if (infodir.length()) { // Acquire the privileges, if needed XrdSysPrivGuard priv(pw->pw_uid, pw->pw_gid); if (priv.Valid()) { struct stat st; // // Define admin file and check its existence FileAdmin = infodir + AdminRef; if (stat(FileAdmin.c_str(),&st) == -1) { if (errno == ENOENT) { PRINT("FileAdmin non existing: "< 0) { // // Load server ID PFAdmin.Init(FileAdmin.c_str(),0); if (PFAdmin.IsValid()) { // // Init cache for admin file if (cacheAdmin.Load(FileAdmin.c_str()) != 0) { PRINT("problems init cache for file admin "); ErrF(erp,kPWErrError,"initializing cache for file admin"); return Parms; } if (QTRACE(Authen)) { cacheAdmin.Dump(); } XrdSutPFEntry *ent = cacheAdmin.Get(pfeRef, "+++SrvID"); if (ent) {SrvID.insert(ent->buf1.buf, 0, ent->buf1.len); pfeRef.UnLock(); } ent = cacheAdmin.Get(pfeRef, "+++SrvEmail"); if (ent) SrvEmail.insert(ent->buf1.buf, 0, ent->buf1.len); // Default error message DefError += SrvEmail; pfeRef.UnLock(); } DEBUG("server ID: "< 0 || SysPwd) { if (cacheUser.Init(100) != 0) { PRINT("problems init cache for user pwd info" " - passwd files in user accounts will not be used"); UserPwd = 0; } } // // List of crypto modules String cryptlist = opt.clist ? (const char *)(opt.clist) : DefCrypto; // // Load crypto modules XrdSutPFEntry ent; XrdCryptoFactory *cf = 0; String clist = cryptlist; if (clist.length()) { String ncpt = ""; int from = 0; while ((from = clist.tokenize(ncpt, from, '|')) != -1) { if (ncpt.length() > 0) { // Try loading if ((cf = XrdCryptoFactory::GetCryptoFactory(ncpt.c_str()))) { // Add it to the list cryptID[ncrypt] = cf->ID(); cryptName[ncrypt].insert(cf->Name(),0,strlen(cf->Name())+1); cf->SetTrace(trace); // Ref cipher String ptag("+++SrvPuk_"); ptag += cf->ID(); if (FileAdmin.length() > 0) { // Acquire the privileges, if needed XrdSysPrivGuard priv(pw->pw_uid, pw->pw_gid); if (priv.Valid()) { if (PFAdmin.ReadEntry(ptag.c_str(),ent) <= 0) { PRINT("ref cipher for module "<Cipher(&bck))) { PRINT("ref cipher for module "<= XrdCryptoMax) { PRINT("max number of crypto modules (" << XrdCryptoMax <<") reached "); break; } } } } } } else { PRINT("cannot instantiate crypto factory "< 0) { FileUser = ("/" + UserRef); if (opt.udir) { FileUser.insert(opt.udir,0); if (FileUser[0] != '/') FileUser.insert('/',0); } else { // Use default $(HOME)/. FileUser.insert(Prefix,0); FileUser.insert("/.",0); } // // Crypt-hash file name, if requested if (opt.cpass) { UserPwd = 2; FileCrypt = opt.cpass; if (FileCrypt[0] != '/') FileCrypt.insert('/',0); } } // // Whether to save client creds KeepCreds = (opt.keepcreds > -1) ? opt.keepcreds : KeepCreds; if (KeepCreds > 0) NOTIFY("Exporting client creds to internal buffer"); // // Whether to export client creds to a file FileExpCreds = (opt.expcreds) ? opt.expcreds : FileExpCreds; if (FileExpCreds.length() > 0) { // Export format FmtExpCreds = opt.expfmt; const char *efmts[4] = {"PFile", "hex", "raw", "raw/nokeyword"}; NOTIFY("Exporting client creds (fmt:"<,v:,id: Parms = new char[cryptlist.length()+3+12+SrvID.length()+5+popt.length()+3]; if (Parms) { if (popt.length() > 0) sprintf(Parms,"v:%d,id:%s,c:%s,po:%s", Version,SrvID.c_str(),cryptlist.c_str(),popt.c_str()); else sprintf(Parms,"v:%d,id:%s,c:%s", Version,SrvID.c_str(),cryptlist.c_str()); } else { PRINT("no system resources for 'Parms'"); ErrF(erp,kPWErrInit,"no system resources for 'Parms'"); } // Some notification NOTIFY("using FileAdmin: "< 0) { NOTIFY("using private pwd files: $(HOME)"< 1) { NOTIFY("using private crypt-hash files: $(HOME)"< -1) ? opt.verisrv : VeriSrv; // // Server puks file FileSrvPuk = ""; if (opt.srvpuk) { FileSrvPuk = opt.srvpuk; if (XrdSutExpand(FileSrvPuk) != 0) { PRINT("cannot expand "< 0) FileSrvPuk = infodir + SrvPukRef; if (FileSrvPuk.length() > 0) { kXR_int32 openmode = 0; struct stat st; // if (stat(FileSrvPuk.c_str(),&st) == -1) { if (errno == ENOENT) { PRINT("server public key file "< -1) ? opt.alog : AutoLogin; NOTIFY("AutoLogin level: "< -1) ? opt.maxprompts : MaxPrompts; // // Attach autologin file name, if requested if (AutoLogin > 0) { bool filefound = 0; String fnrc(256); if (opt.alogfile) { fnrc = opt.alogfile; if (XrdSutExpand(fnrc) != 0) { PRINT("cannot expand "< 0) { kXR_int32 openmode = 0; struct stat st; if (stat(fnrc.c_str(),&st) == -1) { if (errno == ENOENT) { PRINT("Autologin file "< 0) { // Attach to file PFAlog.Init(fnrc.c_str(),openmode); if (PFAlog.IsValid()) { // Init cache for autologin file if (cacheAlog.Load(fnrc.c_str()) == 0) { if (QTRACE(Authen)) { cacheAlog.Dump(); } filefound =1; } else { PRINT("problems init cache for autologin file"); } } else { PRINT("problems attaching-to / creating autologin file"); } } } // // Notify if not found if (!filefound) { NOTIFY("could not init properly autologin - switch off "); AutoLogin = 0; } } // // Notify if not found if (AutoLogin <= 0) { // Init anyhow cache to cache information during session if (cacheAlog.Init(100) != 0) { PRINT("problems init cache for user temporary autolog"); } } // We are done Parms = (char *)""; } // We are done return Parms; } /******************************************************************************/ /* D e l e t e */ /******************************************************************************/ void XrdSecProtocolpwd::Delete() { // Deletes the protocol if (Entity.host) free(Entity.host); // Cleanup the handshake variables, if still there SafeDelete(hs); delete this; } /******************************************************************************/ /* C l i e n t O r i e n t e d F u n c t i o n s */ /******************************************************************************/ /******************************************************************************/ /* g e t C r e d e n t i a l s */ /******************************************************************************/ XrdSecCredentials *XrdSecProtocolpwd::getCredentials(XrdSecParameters *parm, XrdOucErrInfo *ei) { // Query client for the password; remote username and host // are specified in 'parm'. File '.rootnetrc' is checked. EPNAME("getCredentials"); // If we are a server the only reason to be here is to get the forwarded // or saved client credentials if (srvMode) { XrdSecCredentials *creds = 0; if (clientCreds) { // Duplicate the buffer (otherwise it will get deleted ...) int sz = clientCreds->size; char *nbuf = (char *) malloc(sz); if (nbuf) { memcpy(nbuf, clientCreds->buffer, sz); creds = new XrdSecCredentials(nbuf, sz); } } return creds; } // Handshake vars conatiner must be initialized at this point if (!hs) return ErrC(ei,0,0,0,kPWErrError, "handshake var container missing","getCredentials"); hs->ErrMsg = ""; // // Nothing to do if buffer is empty and not filled during construction if ((!parm && !hs->Parms) || (parm && (!(parm->buffer) || parm->size <= 0))) return ErrC(ei,0,0,0,kPWErrNoBuffer,"missing parameters","getCredentials"); // Count interations (hs->Iter)++; // Update time stamp hs->TimeStamp = time(0); // Local vars int nextstep = 0; const char *stepstr = 0; kXR_int32 status = 0; char *bpub = 0; int lpub = 0; String CryptList = ""; String Host = ""; String RemID = ""; String Emsg; String specID = ""; // Buffer / Bucket related XrdSutBucket *bck = 0; XrdSutBuffer *bpar = 0; // Global buffer XrdSutBuffer *bmai = 0; // Main buffer // Session status pwdStatus_t SessionSt; memset(&SessionSt,0,sizeof(SessionSt)); // // Unlocks automatically returning XrdSysMutexHelper pwdGuard(&pwdContext); // // Decode received buffer bpar = hs->Parms; if (!bpar && !(bpar = new XrdSutBuffer((const char *)parm->buffer,parm->size))) return ErrC(ei,0,0,0,kPWErrDecodeBuffer,"global",stepstr); // Ownership has been transferred hs->Parms = 0; // // Check protocol ID name if (strcmp(bpar->GetProtocol(),XrdSecPROTOIDENT)) return ErrC(ei,bpar,bmai,0,kPWErrBadProtocol,stepstr); // // The step indicates what we are supposed to do hs->Step = (bpar->GetStep()) ? bpar->GetStep() : kXPS_init; stepstr = ServerStepStr(hs->Step); // Dump, if requested if (QTRACE(Dump)) { bpar->Dump(stepstr); } // // Find first crypto module to be used if (ParseCrypto(bpar) != 0) return ErrC(ei,bpar,0,0,kPWErrLoadCrypto,stepstr); // // Parse input buffer if (ParseClientInput(bpar, &bmai, Emsg) == -1) { PRINT(Emsg); return ErrC(ei,bpar,bmai,0,kPWErrParseBuffer,Emsg.c_str(),stepstr); } // // Version DEBUG("version run by server: "<< hs->RemVers); // // Dump what we got if (QTRACE(Dump)) { bmai->Dump("Main IN"); } // // Print server messages, if any if (hs->Iter > 1) { bmai->Message(); bmai->Deactivate(kXRS_message); } // // Check random challenge if (!CheckRtag(bmai, Emsg)) return ErrC(ei,bpar,bmai,0,kPWErrBadRndmTag,Emsg.c_str(),stepstr); // // Get the status bucket, if any if ((bck = bmai->GetBucket(kXRS_status))) { int pst = 0; memcpy(&pst,bck->buffer,sizeof(pwdStatus_t)); pst = ntohl(pst); memcpy(&SessionSt, &pst, sizeof(pwdStatus_t)); bmai->Deactivate(kXRS_status); } else { SessionSt.ctype = kpCT_normal; } // // Now action depens on the step nextstep = kXPC_none; switch (hs->Step) { case kXPS_init: // The following 3 cases may fall through case kXPS_puk: case kXPS_signedrtag: // (after kXRC_verifysrv) if (hs->Step == kXPS_init) { // // Add bucket with cryptomod to the global list // (This must be always visible from now on) if (bpar->AddBucket(hs->CryptoMod,kXRS_cryptomod) != 0) return ErrC(ei,bpar,bmai,0, kPWErrCreateBucket,XrdSutBuckStr(kXRS_cryptomod),stepstr); // // Add bucket with our version to the main list if (bmai->MarshalBucket(kXRS_version,(kXR_int32)(Version)) != 0) return ErrC(ei,bpar,bmai,0, kPWErrCreateBucket, XrdSutBuckStr(kXRS_version),"(main list)",stepstr); // // We set some options in the option field of a pwdStatus_t structure if (hs->Tty || (AutoLogin > 0)) SessionSt.options = kOptsClntTty; } // case kXPS_puk: if ((hs->Step == kXPS_init) || (hs->Step == kXPS_puk)) { // After auto-reg request, server puk have been saved in ParseClientInput: // we need to start a full normal login now // // If we have a session cipher we extract the public part // and add to the main packet for transmission to server if (hs->Hcip) { // // Extract buffer with public info for the cipher agreement if (!(bpub = hs->Hcip->Public(lpub))) return ErrC(ei,bpar,bmai,0, kPWErrNoPublic,"session",stepstr); // // Add it to the global list if (bpar->UpdateBucket(bpub,lpub,kXRS_puk) != 0) return ErrC(ei,bpar,bmai,0, kPWErrAddBucket, XrdSutBuckStr(kXRS_puk),"global",stepstr); SafeDelArray(bpub); // // If we are requiring server verification of puk ownership // we are done for this step if (VeriSrv == 1) { nextstep = kXPC_verifysrv; break; } } } // case kXPS_signedrtag: // (after kXRC_verifysrv) // // Add the username if (hs->User.length()) { if (bmai->AddBucket(hs->User,kXRS_user) != 0) return ErrC(ei,bpar,bmai,0, kPWErrDuplicateBucket, XrdSutBuckStr(kXRS_user),stepstr); } else return ErrC(ei,bpar,bmai,0, kPWErrNoUser,stepstr); // // If we do not have a session cipher, the only thing we can // try is auto-registration if (!(hs->Hcip)) { nextstep = kXPC_autoreg; break; } // // Normal attempt: add credentials status = kpCT_normal; if (hs->SysPwd == 1) status = kpCT_crypt; if (hs->SysPwd == 2) status = kpCT_afs; if (!(bck = QueryCreds(bmai, (AutoLogin > 0), status))) return ErrC(ei,bpar,bmai,0, kPWErrQueryCreds, hs->Tag.c_str(),stepstr); bmai->AddBucket(bck); // // Tell the server we want to change the password, if so if (hs->Pent->status == kPFE_onetime) SessionSt.options |= kOptsChngPwd; // nextstep = kXPC_normal; break; case kXPS_credsreq: // // If this is not the first time, during the handshake, that // we query credentials, any save buffer must insufficient, // so invalidate it if (hs->Pent) hs->Pent->cnt = 1; // // Server requires additional credentials: the status bucket // tells us what she wants exactly status = SessionSt.ctype; if (!(bck = QueryCreds(bmai, 0, status))) return ErrC(ei,bpar,bmai,0, kPWErrQueryCreds, hs->Tag.c_str(),stepstr); bmai->AddBucket(bck); // nextstep = kXPC_creds; break; case kXPS_failure: // // Failure: invalidate cache hs->Pent->buf1.SetBuf(); hs->Pent->buf2.SetBuf(); // nextstep = kXPC_failureack; break; case kXPS_newpuk: // // New server puk have been saved in ParseClientInput: we // just need to sign the random tag case kXPS_rtag: // // Not much to do: the random tag is signed in AddSerialized nextstep = kXPC_signedrtag; break; default: return ErrC(ei,bpar,bmai,0, kPWErrBadOpt,stepstr); } // // Add / Update status int *pst = (int *) new char[sizeof(pwdStatus_t)]; memcpy(pst,&SessionSt,sizeof(pwdStatus_t)); *pst = htonl(*pst); if (bmai->AddBucket((char *)pst,sizeof(pwdStatus_t), kXRS_status) != 0) { PRINT("problems adding bucket kXRS_status"); } // // Serialize and encrypt if (AddSerialized('c', nextstep, hs->ID, bpar, bmai, kXRS_main, hs->Hcip) != 0) return ErrC(ei,bpar,bmai,0, kPWErrSerialBuffer,"main",stepstr); // // Serialize the global buffer char *bser = 0; int nser = bpar->Serialized(&bser,'f'); if (QTRACE(Dump)) { bpar->Dump(ClientStepStr(bpar->GetStep())); bmai->Dump("Main OUT"); } // // We may release the buffers now REL2(bpar,bmai); // // Return serialized buffer if (nser > 0) { DEBUG("returned " << nser <<" bytes of credentials"); return new XrdSecCredentials(bser, nser); } else { DEBUG("problems with final serialization"); return (XrdSecCredentials *)0; } } /******************************************************************************/ /* S e r v e r O r i e n t e d M e t h o d s */ /******************************************************************************/ /******************************************************************************/ /* A u t h e n t i c a t e */ /******************************************************************************/ int XrdSecProtocolpwd::Authenticate(XrdSecCredentials *cred, XrdSecParameters **parms, XrdOucErrInfo *ei) { // // Check if we have any credentials or if no credentials really needed. // In either case, use host name as client name EPNAME("Authenticate"); // // If cred buffer is two small or empty assume host protocol if (cred->size <= (int)XrdSecPROTOIDLEN || !cred->buffer) { strncpy(Entity.prot, "host", sizeof(Entity.prot)); return 0; } // Handshake vars container must be initialized at this point if (!hs) return ErrS(String("none"),ei,0,0,0,kPWErrError, "handshake var container missing", "protocol initialization problems"); hs->ErrMsg = ""; // // Update time stamp hs->TimeStamp = time(0); // // ID of this handshaking hs->ID = Entity.tident; DEBUG("handshaking ID: " << hs->ID); // Local vars int i = 0; int kS_rc = kpST_more; int rc = 0; int entst = 0; int nextstep = 0; int ctype = kpCT_normal; char *bpub = 0, *bpid = 0; int lpub = 0; const char *stepstr = 0; String Message; String CryptList; String Host; String SrvPuKExp; String Salt; String RndmTag; String ClntMsg(256); // Buffer related XrdSutBuffer *bpar = 0; // Global buffer XrdSutBuffer *bmai = 0; // Main buffer XrdSutBucket *bck = 0; // Generic bucket // The local status info pwdStatus_t SessionSt = { 0, 0, 0}; // // Unlocks automatically returning XrdSysMutexHelper pwdGuard(&pwdContext); // // Decode received buffer if (!(bpar = new XrdSutBuffer((const char *)cred->buffer,cred->size))) return ErrS(hs->ID,ei,0,0,0,kPWErrDecodeBuffer,"global",stepstr); // // Check protocol ID name if (strcmp(bpar->GetProtocol(),XrdSecPROTOIDENT)) return ErrS(hs->ID,ei,bpar,bmai,0,kPWErrBadProtocol,stepstr); // // The step indicates what we are supposed to do hs->Step = bpar->GetStep(); stepstr = ClientStepStr(hs->Step); // Dump, if requested if (QTRACE(Dump)) { bpar->Dump(stepstr); } // // Find first crypto module to be used if (ParseCrypto(bpar) != 0) return ErrS(hs->ID,ei,bpar,0,0,kPWErrLoadCrypto,stepstr); // // Parse input buffer if (ParseServerInput(bpar, &bmai, ClntMsg) == -1) { PRINT(ClntMsg); return ErrS(hs->ID,ei,bpar,bmai,0,kPWErrParseBuffer,ClntMsg.c_str(),stepstr); } // // Get handshake status if ((bck = bmai->GetBucket(kXRS_status))) { int pst = 0; memcpy(&pst,bck->buffer,sizeof(pwdStatus_t)); pst = ntohl(pst); memcpy(&SessionSt, &pst, sizeof(pwdStatus_t)); bmai->Deactivate(kXRS_status); } else { NOTIFY("no bucket kXRS_status found in main buffer"); } hs->Tty = SessionSt.options & kOptsClntTty; // // Client name unsigned int ulen = hs->User.length(); ulen = (ulen > sizeof(CName)-1) ? sizeof(CName)-1 : ulen; if (ulen) strcpy(CName, hs->User.c_str()); // And set link to entity Entity.name = strdup(CName); // // Version DEBUG("version run by client: "<< hs->RemVers); // // Dump, if requested if (QTRACE(Dump)) { bmai->Dump("main IN"); } // // Check random challenge if (!CheckRtag(bmai, ClntMsg)) return ErrS(hs->ID,ei,bpar,bmai,0,kPWErrBadRndmTag,stepstr,ClntMsg.c_str()); // // Check also host / time stamp (it will be done only if really neede) if (!CheckTimeStamp(bmai, TimeSkew, ClntMsg)) return ErrS(hs->ID,ei,bpar,bmai,0,kPWErrBadRndmTag,stepstr,ClntMsg.c_str()); // // Now action depens on the step bool savecreds = (SessionSt.options & kOptsExpCred); switch (hs->Step) { case kXPC_verifysrv: // // Client required us to sign a random challenge: this is done // in AddSerialized, so nothing to do here nextstep = kXPS_signedrtag; break; case kXPC_signedrtag: // // Client signed the random challenge we sent: if we are here, // everything was fine kS_rc = kpST_ok; nextstep = kXPS_none; break; case kXPC_failureack: // // Client acknowledged failure kS_rc = kpST_error; nextstep = kXPS_none; break; case kXPC_autoreg: // // Client has lost the key or requested auto-registration: we // check the username: if it has a good entry or it is allowed // to auto-register (the check is done in QueryUser) we send // the public part of the key; otherwise we fail rc = QueryUser(entst, ClntMsg); if (rc < 0 || (entst == kPFE_disabled)) return ErrS(hs->ID,ei,bpar,bmai,0, kPWErrBadCreds, DefError.c_str(),stepstr); // // We have to send the public key for (i = 0; i < ncrypt; i++) { if (refcip[i]) { // // Extract buffer with public info for the cipher agreement if (!(bpub = refcip[i]->Public(lpub))) return ErrS(hs->ID,ei,bpar,bmai,0, kPWErrNoPublic, "session",stepstr); bpid = new char[lpub+5]; if (bpid) { char cid[5] = {0}; sprintf(cid,"%d",cryptID[i]); memcpy(bpid,cid,5); memcpy(bpid+5, bpub, lpub); // // Add it to the global list if (bmai->AddBucket(bpid,lpub+5,kXRS_puk) != 0) return ErrS(hs->ID,ei,bpar,bmai,0, kPWErrAddBucket, "main",stepstr); } else return ErrS(hs->ID,ei,bpar,bmai,0, kPWErrError, "out-of-memory",stepstr); SafeDelArray(bpub); // bpid is taken by the bucket } } // client should now go through a complete login nextstep = kXPS_puk; break; case kXPC_normal: case kXPC_creds: if (hs->Step == kXPC_normal) { // // Complete login sequence: check user and creds if (QueryUser(entst,ClntMsg) != 0) return ErrS(hs->ID,ei,bpar,bmai,0, kPWErrBadCreds, ": user ",hs->User.c_str(),stepstr); // Nothing to do, if disabled if (entst == kPFE_disabled) return ErrS(hs->ID,ei,bpar,bmai,0, kPWErrBadCreds, ": user ",hs->User.c_str(),stepstr); if (entst == kPFE_expired || entst == kPFE_onetime) { // New credentials should asked upon success first check SessionSt.options |= kOptsExpCred; } if (entst == kPFE_crypt) { // User credentials are either in crypt form (private or // system ones) or of AFS type; in case of failure // this flag allows the client to send the right creds // at next iteration if (ClntMsg.beginswith("afs:")) { SessionSt.options |= kOptsAFSPwd; } else SessionSt.options |= kOptsCrypPwd; // Reset the message ClntMsg = ""; } // Creds, if any, should be checked, unles we allow auto-registration savecreds = (entst != kPFE_allowed) ? 0 : 1; } // case kXPC_creds: (falls into here from _normal) // // Final login sequence: extract and check creds // Extract credentials from main buffer if (!(bck = bmai->GetBucket(kXRS_creds))) { // // If credentials are missing, require them kS_rc = kpST_more; nextstep = kXPS_credsreq; break; } // // If we required new credentials at previous step, just save them if (savecreds) { if (SaveCreds(bck) != 0) { ClntMsg = "Warning: could not correctly update credentials database"; } kS_rc = kpST_ok; nextstep = kXPS_none; bmai->Deactivate(kXRS_creds); break; } // // Credential type ctype = kpCT_normal; if (SessionSt.options & kOptsCrypPwd) ctype = kpCT_crypt; else if (SessionSt.options & kOptsAFSPwd) { ctype = kpCT_afs; String afsInfo; XrdSutBucket *bafs = bmai->GetBucket(kXRS_afsinfo); if (bafs) bafs->ToString(afsInfo); if (afsInfo == "c") ctype = kpCT_afsenc; } // // Check credentials if (!CheckCreds(bck, ctype)) { // // Count temporary failures (hs->Cref->cnt)++; // Reset expired credentials flag SessionSt.options &= ~kOptsExpCred; // Repeat if not too many attempts ClntMsg = DefError; if (hs->Cref->cnt < MaxPrompts) { // Set next step to credential request nextstep = kXPS_credsreq; kS_rc = kpST_more; // request again creds if (hs->Pent->status == kPFE_crypt) { SessionSt.ctype = kpCT_crypt; if (ctype == kpCT_afs || ctype == kpCT_afsenc) { SessionSt.ctype = kpCT_afs; String afsinfo = hs->ErrMsg; bmai->UpdateBucket(afsinfo, kXRS_afsinfo); } ClntMsg = ""; } else { SessionSt.ctype = kpCT_normal; ClntMsg = "insufficient credentials"; } } else { // We communicate failure kS_rc = kpST_more; nextstep = kXPS_failure; // Count failures (hs->Pent->cnt)++; // Count failures hs->Pent->mtime = (kXR_int32)time(0); // Flush cache content to source file XrdSysPrivGuard priv(getuid(), getgid()); if (priv.Valid()) { if (cacheAdmin.Flush() != 0) { PRINT("WARNING: some problem flushing to admin" " file after updating "<Pent->name); } } } } else { // Reset counter for temporary failures hs->Cref->cnt = 0; // Reset counter in file if needed if (hs->Pent->cnt > 0) { hs->Pent->cnt = 0; // Count failures hs->Pent->mtime = (kXR_int32)time(0); // Flush cache content to source file XrdSysPrivGuard priv(getuid(), getgid()); if (priv.Valid()) { if (cacheAdmin.Flush() != 0) { PRINT("WARNING: some problem flushing to admin" " file after updating "<Pent->name); } } } kS_rc = kpST_ok; nextstep = kXPS_none; if (SessionSt.options & kOptsExpCred || // Client requested a pwd change SessionSt.options & kOptsChngPwd) { kS_rc = kpST_more; nextstep = kXPS_credsreq; if (SessionSt.options & kOptsExpCred) { ClntMsg = "Credentials expired"; } else if (SessionSt.options & kOptsChngPwd) { ClntMsg = "Password change requested"; } // request new creds SessionSt.ctype = kpCT_new; // So we can save at next round SessionSt.options |= kOptsExpCred; } // Create buffer to keep the credentials, if required if (KeepCreds) { int sz = bck->size+5; char *buf = (char *) malloc(sz); if (buf) { memcpy(buf, "&pwd", 4); buf[4] = 0; memcpy(buf+5, bck->buffer, bck->size); // Put in hex char *out = new char[2*sz+1]; XrdSutToHex(buf, sz, out); // Cleanup any existing info SafeDelete(clientCreds); clientCreds = new XrdSecCredentials(out, 2*sz+1); } } // Export creds to a file, if required if (FileExpCreds.length() > 0) { if (ExportCreds(bck) != 0) PRINT("WARNING: some problem exporting creds to file;" " template is :"<Deactivate(kXRS_creds); break; default: return ErrS(hs->ID,ei,bpar,bmai,0, kPWErrBadOpt, stepstr); } // // If strong signature checking is required add random tag if (kS_rc == kpST_ok) { if (VeriClnt == 2 && !(hs->RtagOK)) { // Send only the random tag to sign nextstep = kXPS_rtag; kS_rc = kpST_more; } } // // If we need additional info but the client caa not reply, just fail if (kS_rc == kpST_more && !(hs->Tty)) { PRINT("client cannot reply to additional request: failure"); // Deactivate everything bpar->Deactivate(-1); bmai->Deactivate(-1); kS_rc = kpST_error; } // if (kS_rc == kpST_more) { // // Add message to client if (ClntMsg.length() > 0) if (bmai->AddBucket(ClntMsg,kXRS_message) != 0) { PRINT("problems adding bucket with message for client"); } // // We set some options in the option field of a pwdStatus_t structure int *pst = (int *) new char[sizeof(pwdStatus_t)]; memcpy(pst,&SessionSt,sizeof(pwdStatus_t)); *pst = htonl(*pst); if (bmai->AddBucket((char *)pst,sizeof(pwdStatus_t), kXRS_status) != 0) { PRINT("problems adding bucket kXRS_status"); } // // Serialize, encrypt and add to the global list if (AddSerialized('s', nextstep, hs->ID, bpar, bmai, kXRS_main, hs->Hcip) != 0) return ErrS(hs->ID,ei,bpar,bmai,0, kPWErrSerialBuffer, "main / session cipher",stepstr); // // Serialize the global buffer char *bser = 0; int nser = bpar->Serialized(&bser,'f'); // // Dump, if requested if (QTRACE(Dump)) { bpar->Dump(ServerStepStr(bpar->GetStep())); bmai->Dump("Main OUT"); } // // Create buffer for client *parms = new XrdSecParameters(bser,nser); } else { // // Cleanup handshake vars SafeDelete(hs); } // // We may release the buffers now REL2(bpar,bmai); // // All done return kS_rc; } /******************************************************************************/ /* E n a b l e T r a c i n g */ /******************************************************************************/ XrdOucTrace *XrdSecProtocolpwd::EnableTracing() { // Initiate error logging and tracing eDest.logger(&Logger); PWDTrace = new XrdOucTrace(&eDest); return PWDTrace; } /******************************************************************************/ /* p w d O p t i o n s :: P r i n t */ /******************************************************************************/ void pwdOptions::Print(XrdOucTrace *t) { // Dump summary of GSI init options EPNAME("InitOpts"); // For clients print only if really required (for servers we notified it // always once for all) if ((mode == 'c') && debug <= 0) return; POPTS(t, "*** ------------------------------------------------------------ ***"); POPTS(t, " Mode: "<< ((mode == 'c') ? "client" : "server")); POPTS(t, " Debug: "<< debug); if (mode == 'c') { POPTS(t, " Check user's autologin info: " << (alog != 0 ? "yes" : "no")); POPTS(t, " Verification level of server ownership on public key: " << verisrv); POPTS(t, " Max number of empty prompts:" << maxprompts); if (alogfile) POPTS(t, " Autologin file:" << alogfile); if (srvpuk) POPTS(t, " File with known servers public keys:" << srvpuk); POPTS(t, " Update auto-login info option:" << areg); } else { POPTS(t, " Check pwd file in user's home: " << (upwd != 0 ? "yes" : "no")); POPTS(t, " Verification level of client ownership on public key: " << vericlnt); POPTS(t, " Autoregistration option:" << areg); POPTS(t, " Check system pwd file option: " << syspwd); POPTS(t, " Credentials lifetime (seconds): " << lifecreds); POPTS(t, " Max number of failures: " << maxfailures); if (clist) POPTS(t, " List of supported crypto modules: " << clist); if (dir) POPTS(t, " Directory with admin pwd files: " << dir); if (udir) POPTS(t, " User's sub-directory with pwd files: " << udir); if (cpass) POPTS(t, " User's crypt hash pwd file: " << cpass); POPTS(t, " Keep client credentials in memory: " << (keepcreds != 0 ? "yes" : "no")); if (expcreds) { POPTS(t, " File for exported client credentials: " << expcreds); POPTS(t, " Format for exported client credentials: " << expfmt); } else { POPTS(t, " Client credentials not exported to file"); } } POPTS(t, "*** ------------------------------------------------------------ ***"); } /******************************************************************************/ /* X r d S e c P r o t o c o l p w d I n i t */ /******************************************************************************/ XrdVERSIONINFO(XrdSecProtocolpwdInit,secpwd); extern "C" { char *XrdSecProtocolpwdInit(const char mode, const char *parms, XrdOucErrInfo *erp) { // One-time protocol initialization, filling the static flags and options // of the protocol. // For clients (mode == 'c') we use values in envs. // For servers (mode == 's') the command line options are passed through // parms. EPNAME("ProtocolpwdInit"); pwdOptions opts; char *rc = (char *)""; char *cenv = 0; // Initiate error logging and tracing pwdTrace = XrdSecProtocolpwd::EnableTracing(); // // Clients first if (mode == 'c') { // // Decode envs: // "XrdSecDEBUG" debug flag ("0","1","2","3") // "XrdSecPWDVERIFYSRV" "1" server verification ON [default] // "0" server verification OFF // "XrdSecPWDSRVPUK" full path to file with server puks // [default: $HOME/.xrd/pwdsrvpuk] // "XrdSecPWDAUTOLOG" "1" autologin ON [default] // "0" autologin OFF // "XrdSecPWDALOGFILE" full path to file with autologin // info [default: $HOME/.xrd/pwdnetrc] // "XrdSecPWDALOGUPDT" update autologin file option: // "0" never [default] // "1" remove_obsolete_info // "2" "1" + register_new_valid_info // "XrdSecPWDMAXPROMPT" max number of attemts to get valid // input info by prompting the client // opts.mode = mode; // debug cenv = getenv("XrdSecDEBUG"); if (cenv) {if (cenv[0] >= 49 && cenv[0] <= 51) opts.debug = atoi(cenv); else {PRINT("unsupported debug value from env XrdSecDEBUG: "<= 48 && cenv[0] <= 49) opts.verisrv = atoi(cenv); // file with server public keys cenv = getenv("XrdSecPWDSRVPUK"); if (cenv) opts.srvpuk = strdup(cenv); // autologin cenv = getenv("XrdSecPWDAUTOLOG"); if (cenv) if (cenv[0] >= 48 && cenv[0] <= 50) opts.alog = atoi(cenv); // autologin file cenv = getenv("XrdSecPWDALOGFILE"); if (cenv) opts.alogfile = strdup(cenv); // max re-prompts cenv = getenv("XrdSecPWDMAXPROMPT"); if (cenv) { opts.maxprompts = strtol(cenv, (char **)0, 10); if (errno == ERANGE) opts.maxprompts = -1; } // // Setup the object with the chosen options rc = XrdSecProtocolpwd::Init(opts,erp); // Notify init options, if required or in case of init errors if (!rc) opts.debug = 1; opts.Print(pwdTrace); // Some cleanup if (opts.srvpuk) free(opts.srvpuk); if (opts.alogfile) free(opts.alogfile); // We are done return rc; } // Take into account xrootd debug flag cenv = getenv("XRDDEBUG"); if (cenv && !strcmp(cenv,"1")) opts.debug = 1; // // Server initialization if (parms) { // // Duplicate the parms char parmbuff[1024]; strlcpy(parmbuff, parms, sizeof(parmbuff)); // // The tokenizer XrdOucTokenizer inParms(parmbuff); // // Decode parms: // for servers: [-upwd:] // [-a:] // [-vc:] // [-dir:] // [-udir:] // [-c:[-]ssl[:[-]] // [-syspwd] // [-lf:] // [-maxfail:] // [-keepcreds] // [-expcreds:] // [-expfmt:] // // = 0 (do-not-use), 1 (use), 2 (also-crypt-hash) // = 0 (none), 1 (low), 2 (medium), 3 (high) [0] // = 0 (none), 1 (local users + allowed tags), 2 (all) [0] // = 1d, 5h:10m, ... (see XrdSutAux::ParseTime) // = 0 (none), 1 (timestamp), 2 (random tag) [2] // = can be a fully specified path or in the templated form // /path//file, with expanded at the moment // of use with the login name. // = 0 (XrdSutPFEntry in dedicated file), // 1 (hex form), 2 (plain), 3 (plain, no keywords) [0] // int debug = -1; int areg = -1; int vc = -1; int upw = -1; int syspwd = -1; int lifetime = -1; int maxfail = -1; String dir = ""; String udir = ""; String clist = ""; String cpass = ""; int keepcreds = -1; String expcreds = ""; int expfmt = 0; char *op = 0; while (inParms.GetLine()) { while ((op = inParms.GetToken())) { if (!strncmp(op, "-upwd:",6)) { upw = atoi(op+6); } else if (!strncmp(op, "-dir:",5)) { dir = (const char *)(op+5); } else if (!strncmp(op, "-udir:",6)) { udir = (const char *)(op+6); } else if (!strncmp(op, "-c:",3)) { clist = (const char *)(op+3); } else if (!strncmp(op, "-d:",3)) { debug = atoi(op+3); } else if (!strncmp(op, "-a:",3)) { areg = atoi(op+3); } else if (!strncmp(op, "-vc:",4)) { vc = atoi(op+4); } else if (!strncmp(op, "-syspwd",7)) { syspwd = 1; } else if (!strncmp(op, "-lf:",4)) { lifetime = XrdSutParseTime(op+4); } else if (!strncmp(op, "-maxfail:",9)) { maxfail = atoi(op+9); } else if (!strncmp(op, "-cryptfile:",11)) { cpass = (const char *)(op+11); } else if (!strncmp(op, "-keepcreds",10)) { keepcreds = 1; } else if (!strncmp(op, "-expcreds:",10)) { expcreds = (const char *)(op+10); } else if (!strncmp(op, "-expfmt:",8)) { expfmt = atoi(op+8); } } // Check inputs areg = (areg >= 0 && areg <= 2) ? areg : 0; vc = (vc >= 0 && vc <= 2) ? vc : 2; } // // Build the option object opts.debug = (debug > -1) ? debug : opts.debug; opts.mode = 's'; opts.areg = areg; opts.vericlnt = vc; opts.upwd = upw; opts.syspwd = syspwd; opts.lifecreds = lifetime; opts.maxfailures = maxfail; opts.expfmt = expfmt; if (dir.length() > 0) opts.dir = (char *)dir.c_str(); if (udir.length() > 0) opts.udir = (char *)udir.c_str(); if (clist.length() > 0) opts.clist = (char *)clist.c_str(); if (cpass.length() > 0) opts.cpass = (char *)cpass.c_str(); opts.keepcreds = keepcreds; if (expcreds.length() > 0) opts.expcreds = (char *)expcreds.c_str(); // Notify init options, if required opts.Print(pwdTrace); // // Setup the plug-in with the chosen options return XrdSecProtocolpwd::Init(opts,erp); } // Notify init options, if required opts.Print(pwdTrace); // // Setup the plug-in with the defaults return XrdSecProtocolpwd::Init(opts,erp); }} /******************************************************************************/ /* X r d S e c P r o t o c o l p w d O b j e c t */ /******************************************************************************/ XrdVERSIONINFO(XrdSecProtocolpwdObject,secpwd); extern "C" { XrdSecProtocol *XrdSecProtocolpwdObject(const char mode, const char *hostname, XrdNetAddrInfo &endPoint, const char *parms, XrdOucErrInfo *erp) { XrdSecProtocolpwd *prot; int options = XrdSecNOIPCHK; // // Get a new protocol object if (!(prot = new XrdSecProtocolpwd(options, hostname, endPoint, parms))) { const char *msg = "Secpwd: Insufficient memory for protocol."; if (erp) erp->setErrInfo(ENOMEM, msg); else cerr <GetNBuckets()) { // If the bucket list is empty we assume this being the first iteration // step (the step is not defined at this point). // The option field should contain the relevant information String opts = buf->GetOptions(); if (!(opts.length())) { DEBUG("missing options - bad format"); return -1; } // // Extract crypto module list, if any int ii = opts.find("c:"); if (ii >= 0) { clist.assign(opts, ii+2); clist.erase(clist.find(',')); } else { PRINT("crypto information not found in options"); return -1; } } else { // // Extract crypto module name from the buffer if (!(bck = buf->GetBucket(kXRS_cryptomod))) { PRINT("cryptomod buffer missing"); return -1; } bck->ToString(clist); } DEBUG("parsing list: "<CryptoMod = ""; // Parse list if (clist.length()) { int from = 0; while ((from = clist.tokenize(hs->CryptoMod, from, '|')) != -1) { // Check this module if (hs->CryptoMod.length()) { // Load the crypto factory if ((hs->CF = XrdCryptoFactory::GetCryptoFactory(hs->CryptoMod.c_str()))) { int fid = hs->CF->ID(); int i = 0; // Retrieve the index in local table while (i < ncrypt) { if (cryptID[i] == fid) break; i++; } if (i >= ncrypt) { if (ncrypt == XrdCryptoMax) { PRINT("max number of crypto slots reached - do nothing"); return 0; } else { // Add new entry cryptID[i] = fid; ncrypt++; } } // On servers the ref cipher should be defined at this point hs->Rcip = refcip[i]; // we are done return 0; } } } } return 1; } //____________________________________________________________________ bool XrdSecProtocolpwd::CheckCreds(XrdSutBucket *creds, int ctype) { // Check credentials against information in password file EPNAME("CheckCreds"); bool match = 0; // Check inputs if (!hs->CF || !creds || !hs->Pent) { PRINT("Invalid inputs ("<CF<<","<Pent<<")"); return match; } // Make sure there is something to check against if (ctype != kpCT_afs && ctype != kpCT_afsenc && (!(hs->Pent->buf1.buf) || hs->Pent->buf1.len <= 0)) { NOTIFY("Cached information about creds missing"); return match; } // // Create a buffer to store credentials, if required int len = creds->size+4; char *cbuf = (KeepCreds) ? new char[len] : (char *)0; // // Separate treatment for crypt-like creds if (ctype != kpCT_crypt && ctype != kpCT_afs && ctype != kpCT_afsenc) { // // Create a bucket for the salt to easy encryption XrdSutBucket *tmps = new XrdSutBucket(); if (!tmps) { PRINT("Could not allocate working buckets area for the salt"); return match; } tmps->SetBuf(hs->Pent->buf1.buf, hs->Pent->buf1.len); // // Save input bucket if creds have to be kept if (KeepCreds) { memcpy(cbuf, "pwd:", 4); memcpy(cbuf+4, creds->buffer, creds->size); } // // Hash received buffer for the comparison DoubleHash(hs->CF,creds,tmps); // Compare if (hs->Pent->buf2.len == creds->size) if (!memcmp(creds->buffer, hs->Pent->buf2.buf, creds->size)) match = 1; SafeDelete(tmps); // // recover input creds if (match && KeepCreds) creds->SetBuf(cbuf, len); } else { #ifdef HAVE_CRYPT // Crypt-like: get the pwhash String passwd(creds->buffer,creds->size+1); passwd.reset(0,creds->size,creds->size); // Get the crypt char *pass_crypt = crypt(passwd.c_str(), hs->Pent->buf1.buf); // Compare if (!strncmp(pass_crypt, hs->Pent->buf1.buf, hs->Pent->buf1.len + 1)) match = 1; if (match && KeepCreds) { memcpy(cbuf, "cpt:", 4); memcpy(cbuf+4, creds->buffer, creds->size); creds->SetBuf(cbuf, len); } #else NOTIFY("Crypt-like passwords (via crypt(...)) not supported"); match = 0; #endif } // Cleanup if (cbuf) delete[] cbuf; // We are done return match; } //________________________________________________________________________ bool XrdSecProtocolpwd::CheckCredsAFS(XrdSutBucket *, int) { // Check AFS credentials - not supported return 0; } //____________________________________________________________________ int XrdSecProtocolpwd::SaveCreds(XrdSutBucket *creds) { // Save credentials in creds in the password file // Returns 0 if ok, -1 otherwise EPNAME("SaveCreds"); XrdSutPFCacheRef pfeRef; // Check inputs if ((hs->User.length() <= 0) || !hs->CF || !creds) { PRINT("Bad inputs ("<User.length()<<","<CF<<"," <Tag + '_'; wTag += hs->CF->ID(); // // Update entry in cache, if there, or add one XrdSutPFEntry *cent = cacheAdmin.Add(pfeRef, wTag.c_str()); if (!cent) { PRINT("Could not get entry in cache"); return -1; } // Generate a salt and fill it in char *tmps = XrdSutRndm::GetBuffer(8,3); if (!tmps) { PRINT("Could not generate salt: out-of-memory"); return -1; } XrdSutBucket *salt = new XrdSutBucket(tmps,8); if (!salt) { PRINT("Could not create salt bucket"); return -1; } cent->buf1.SetBuf(salt->buffer,salt->size); // // Now we sign the creds with the salt DoubleHash(hs->CF,creds,salt); // and fill in the creds cent->buf2.SetBuf(creds->buffer,creds->size); // // Set entry status OK cent->status = kPFE_ok; // // Save entry cent->mtime = hs->TimeStamp; // DEBUG("Entry for tag: "<User.length() <= 0) || !hs->CF || !creds) { PRINT("Bad inputs ("<User.length()<<","<CF<<"," <Tag + '_'; wTag += hs->CF->ID(); // // Create and fill a new entry XrdSutPFEntry ent; ent.SetName(wTag.c_str()); ent.status = kPFE_ok; ent.cnt = 0; if (!strncmp(creds->buffer, "pwd:", 4)) { // Skip initial "pwd:" ent.buf1.SetBuf(creds->buffer+4, creds->size-4); } else { // For crypt and AFS we keep that to be able to distinguish // later on ent.buf1.SetBuf(creds->buffer,creds->size); } // // Write entry ent.mtime = time(0); pfcreds.WriteEntry(ent); DEBUG("New entry for "<size + 5; if ((buf = (char *) malloc(sz))) { memcpy(buf, "&pwd", 4); buf[4] = 0; memcpy(buf+5, creds->buffer, creds->size); // Put in hex if (FmtExpCreds == 1) { out = new char[2*sz+1]; XrdSutToHex(buf, sz, out); } } else { PRINT("Problem creating buffer for exported credentials!"); return -1; } // Open the file, truncating if already existing int fd = open(filecreds.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0600); if (fd < 0) { PRINT("problems creating file - errno: " << errno); if (buf) {free(buf); buf = 0;} SafeDelete(out); return -1; } const char *pw = 0; int lw = -1; if (FmtExpCreds == 1) { // Hex form pw = (const char *)out; lw = 2*sz + 1; } else if (FmtExpCreds == 3) { // Ignore keywords int offs = (hs->SysPwd == 2) ? 9 : 5; pw = (const char *)(buf + offs); lw = sz - offs; } else { pw = (const char *)buf; lw = sz; } // Write out now int nw = 0, written = 0; while (lw) { if ((nw = write(fd, pw + written, lw)) < 0) { if (errno == EINTR) { errno = 0; continue; } else { break; } } // Count written += nw; lw -= nw; } // Cleanup temporary buffers if (buf) {free(buf); buf = 0;} SafeDelete(out); close(fd); } // We are done return 0; } //____________________________________________________________________ XrdSutBucket *XrdSecProtocolpwd::QueryCreds(XrdSutBuffer *bm, bool netrc, int &status) { // Get credential information to be sent to the server EPNAME("QueryCreds"); XrdSutPFCacheRef pfeRef; // Check inputs if (!bm || !hs->CF || hs->Tag.length() <= 0) { PRINT("bad inputs ("<CF<<","<Tag.length()<<")"); return (XrdSutBucket *)0; } // // Type of creds (for the prompt) int ctype = (status > kpCT_undef) ? status : kpCT_normal; netrc = ((ctype == kpCT_normal || ctype == kpCT_onetime || ctype == kpCT_old || ctype == kpCT_crypt)) ? netrc : 0; // // reset status status = kpCI_undef; // Output bucket XrdSutBucket *creds = new XrdSutBucket(); if (!creds) { PRINT("Could allocate bucket for creds"); return (XrdSutBucket *)0; } creds->type = kXRS_creds; // // Build effective tag String wTag = hs->Tag + '_'; wTag += hs->CF->ID(); // // If creds are available in the environment pick them up and use them char *cf = 0; char *cbuf = getenv("XrdSecCREDS"); if (cbuf) { int len = strlen(cbuf); // From hex int sz = len; char *out = new char[sz/2+2]; XrdSutFromHex((const char *)cbuf, out, len); if ((cf = strstr(out, "&pwd"))) { cf += 5; len -= 5; if (len > 0) { // Get prefix char pfx[5] = {0}; memcpy(pfx, cf, 4); cf += 4; len -= 4; if (len > 0) { DEBUG("using "<Pent = cacheAlog.Add(pfeRef, wTag.c_str()); if (hs->Pent) { // Try only once if (hs->Pent->cnt == 0) { // Set buf creds->SetBuf(cf,len); // Fill entry if (strncmp(pfx,"pwd",3)) hs->Pent->status = kPFE_crypt; hs->Pent->mtime = hs->TimeStamp; hs->Pent->buf1.SetBuf(cf, len); // Just in case we need the passwd itself (like in crypt) hs->Pent->buf2.SetBuf(cf, len); // Tell the server if (!strncmp(pfx,"afs",3)) { String afsInfo = "c"; if (bm->UpdateBucket(afsInfo, kXRS_afsinfo) != 0) PRINT("Warning: problems updating bucket with AFS info"); } // Update status status = kpCI_exact; // We are done return creds; } else { // Cleanup hs->Pent->buf1.SetBuf(); hs->Pent->buf2.SetBuf(); } } else { PRINT("Could create new entry in cache"); return (XrdSutBucket *)0; } } } } } pfeRef.UnLock(); // Unlock pointer if we got a lock on it! // // Extract AFS info (the cell), if any String afsInfo; if (ctype == kpCT_afs || ctype == kpCT_afsenc) { XrdSutBucket *bafs = bm->GetBucket(kXRS_afsinfo); if (bafs) bafs->ToString(afsInfo); } // // Search information in autolog file(s) first, if required if (netrc) { // // Make sure cache it is up-to-date if (PFAlog.IsValid()) { if (cacheAlog.Refresh() != 0) { PRINT("problems assuring cache update for file alog "); } } // // We may already have an entry in the cache bool wild = 0; hs->Pent = cacheAlog.Get(pfeRef, wTag.c_str(),&wild); // Retrieve pwd information if ok if (hs->Pent && hs->Pent->buf1.buf) { if (hs->Pent->cnt == 0) { cf = hs->Pent->buf1.buf; bool afspwd = strncmp(cf,"afs",3) ? 0 : 1; if (!strncmp(cf,"cpt",3) || afspwd) { int len = hs->Pent->buf1.len; cf += 4; len -= 4; hs->Pent->status = kPFE_crypt; hs->Pent->mtime = hs->TimeStamp; hs->Pent->buf1.SetBuf(cf, len); // Just in case we need the passwd itself (like in crypt) hs->Pent->buf2.SetBuf(cf, len); // Tell the server if (afspwd) { afsInfo = "c"; if (bm->UpdateBucket(afsInfo, kXRS_afsinfo) != 0) PRINT("Warning: problems updating bucket with AFS info"); } } // Fill output with double hash creds->SetBuf(hs->Pent->buf1.buf,hs->Pent->buf1.len); // Update status status = wild ? kpCI_wildcard : kpCI_exact; // We are done return creds; } else { // Entry not ok: probably previous attempt failed: discard hs->Pent->buf1.SetBuf(); } } pfeRef.UnLock(); // In case we have the lock release it. // for crypt-like, look also into a .netrc-like file, if any String passwd; String host(hs->Tag,hs->Tag.find("@",0)+1,hs->Tag.find(":",0)-1); if (QueryNetRc(host, passwd, status) == 0) { // Create or Fill entry in cache if ((hs->Pent = cacheAlog.Add(pfeRef, wTag.c_str()))) { // Fill entry hs->Pent->status = kPFE_crypt; hs->Pent->mtime = hs->TimeStamp; hs->Pent->buf1.SetBuf(passwd.c_str(),passwd.length()); // Fill output creds->SetBuf(passwd.c_str(),passwd.length()); // Update status status = kpCI_exact; // We are done return creds; } else { PRINT("Could create new entry in cache"); return (XrdSutBucket *)0; } } } // // Create or Fill entry in cache if (!(hs->Pent) && !(hs->Pent = cacheAlog.Add(pfeRef, wTag.c_str()))) { PRINT("Could create new entry in cache"); return (XrdSutBucket *)0; } // // If a previous attempt was successful re-use same passwd if (hs->Pent && hs->Pent->buf1.buf && hs->Pent->cnt == 0) { // Fill output creds->SetBuf(hs->Pent->buf1.buf,hs->Pent->buf1.len); // Update status status = kpCI_exact; // We are done return creds; } // // We are here because: // 1) autologin disabled or no autologin info found // ==> hs->Pent empty ==> prompt for password // 2) we need to send a new password hash because it was wrong // ==> hs->Pent->buf2 empty ==> prompt for password // 3) we need to send a new password hash because it has expired // (either one-time or too old) // ==> query hs->Pent->buf2 before prompting // 4) we need to send a real password because the server uses crypt() // or AFS // ==> query hs->Pent->buf2 from previous prompt // // If the previously cached entry has a second (final) passwd // use it. This is the case when the real passwd is required (like in // crypt), we may have it in cache from a previous prompt if (ctype == kpCT_crypt || ctype == kpCT_afs) { if (hs->Pent && hs->Pent->buf2.buf) { if (ctype == kpCT_afs) { String passwd(hs->Pent->buf2.buf,hs->Pent->buf2.len); // Fill output creds->SetBuf(hs->Pent->buf2.buf,hs->Pent->buf2.len); // Not needed anymore bm->Deactivate(kXRS_afsinfo); } else { // Fill output creds->SetBuf(hs->Pent->buf2.buf,hs->Pent->buf2.len); } // Save info in the first buffer and reset the second buffer hs->Pent->buf1.SetBuf(hs->Pent->buf2.buf,hs->Pent->buf2.len); hs->Pent->buf2.SetBuf(); // Update status status = kpCI_exact; // We are done return creds; } } // // From now we need to prompt the user: we can do this only if // connected to a terminal if (!(hs->Tty)) { NOTIFY("Not connected to tty: cannot prompt user for credentials"); return (XrdSutBucket *)0; } // // Prompt char prompt[XrdSutMAXPPT] = {0}; if (ctype == kpCT_onetime) snprintf(prompt,XrdSutMAXPPT, "Password for %s not active: " "starting activation handshake.",hs->Tag.c_str()); // // Prepare the prompt if (ctype == kpCT_new) { snprintf(prompt,XrdSutMAXPPT, "Enter new password: "); } else if (ctype == kpCT_crypt) { String host(hs->Tag,hs->Tag.find("@",0)+1,hs->Tag.find(":",0)-1); snprintf(prompt,XrdSutMAXPPT, "Password for %s@%s: ", hs->User.c_str(), host.c_str()); } else if (ctype == kpCT_afs || ctype == kpCT_afsenc) { snprintf(prompt,XrdSutMAXPPT, "AFS password for %s@%s: ", hs->User.c_str(), hs->AFScell.c_str()); } else { // Normal prompt snprintf(prompt,XrdSutMAXPPT,"Password for %s:",hs->Tag.c_str()); } // // Inquire password int natt = MaxPrompts; String passwd = ""; bool changepwd =0; while (natt-- && passwd.length() <= 0) { XrdSutGetPass(prompt, passwd); // If in the format $changepwd$ we are asking for // a password change if (passwd.beginswith("$changepwd$")) { PRINT("Requesting a password change"); changepwd = 1; passwd.erase("$changepwd$",0,strlen("$changepwd$")); } if (passwd.length()) { // Fill in password creds->SetBuf(passwd.c_str(),passwd.length()); if (ctype != kpCT_crypt && ctype != kpCT_afs) { // Self-Hash DoubleHash(hs->CF,creds,creds); // Update status status = kpCI_prompt; } else if (ctype == kpCT_afs) { } // Save creds to update auto-login file later // It will be flushed to file if required if (changepwd) hs->Pent->status = kPFE_onetime; else hs->Pent->status = kPFE_ok; hs->Pent->buf1.SetBuf(creds->buffer,creds->size); // // Just in case we need the passwd itself (like in crypt) hs->Pent->buf2.SetBuf(passwd.c_str(),passwd.length()); // Update autologin, if required if (AutoLogin > 0) UpdateAlog(); } } // Cleanup, if we did not get anything if (passwd.length() <= 0) { delete creds; creds = 0; } // We are done return creds; } //____________________________________________________________________ int XrdSecProtocolpwd::UpdateAlog() { // Save pass hash in autologin file // Returns 0 if ok, -1 otherwise EPNAME("UpdateAlog"); // Check inputs if (hs->Tag.length() <= 0) { PRINT("Tag undefined - do nothing"); return -1; } // Check inputs if (!(hs->Pent) || !(hs->Pent->buf1.buf)) { NOTIFY("Nothing to do"); return 0; } // // Build effective tag String wTag = hs->Tag + '_'; wTag += hs->CF->ID(); // // Make sure the other buffers are reset hs->Pent->buf2.SetBuf(); hs->Pent->buf3.SetBuf(); hs->Pent->buf4.SetBuf(); // // Set entry status OK hs->Pent->status = kPFE_ok; // // Reset count hs->Pent->cnt = 0; // // Save entry hs->Pent->mtime = hs->TimeStamp; // DEBUG("Entry for tag: "<User); // Check inputs if (hs->User.length() <= 0 || !hs->CF || !hs->Cref) { PRINT("Invalid inputs ("<User.length()<<","<CF<<","<Cref<<")"); return -1; } // // Build effective tag String wTag = hs->Tag + '_'; wTag += hs->CF->ID(); // // Default status status = kPFE_disabled; int bad = -1; cmsg = ""; // // Check first info in user's home, if allowed if (UserPwd) { // Get userinfo struct passwd *pw; XrdSysPwd thePwd(hs->User.c_str(), &pw); int rcst = 0; kXR_int32 mtime = -1; bool fcrypt = 0; String File; if (pw) { File.resize(strlen(pw->pw_dir)+FileUser.length()+10); File.assign(pw->pw_dir, 0); File += FileUser; // Get status struct stat st; if ((rcst = stat(File.c_str(),&st)) != 0 && errno == ENOENT) { if (UserPwd > 1) { // Try special crypt like file File.replace(FileUser,FileCrypt); fcrypt = 1; rcst = 0; } } mtime = (rcst == 0) ? st.st_mtime : mtime; } if (rcst == 0) { // // Check cache first hs->Pent = cacheUser.Get(pfeRef, wTag.c_str()); if (!hs->Pent || (hs->Pent->mtime < mtime)) { if (!(hs->Pent)) hs->Pent = cacheUser.Add(pfeRef, wTag.c_str()); if (hs->Pent) { // // Try the files if (!fcrypt) { // Try to attach to File XrdSutPFile ff(File.c_str(), kPFEopen,0,0); if (ff.IsValid()) { // Retrieve pwd information if (ff.ReadEntry(wTag.c_str(),*(hs->Pent)) > 0) { bad = 0; status = hs->Pent->status; ff.Close(); return 0; } ff.Close(); } } else if (UserPwd > 1) { String pwhash; if (QueryCrypt(FileCrypt, pwhash) > 0) { bad = 0; status = kPFE_crypt; // Fill entry hs->Pent->mtime = hs->TimeStamp; hs->Pent->status = status; hs->Pent->cnt = 0; if (!FileCrypt.beginswith("afs:")) hs->Pent->buf1.SetBuf(pwhash.c_str(),pwhash.length()+1); // Trasmit the type of credentials we have found cmsg = FileCrypt; return 0; } } } } else { // Fill entry bad = 0; status = hs->Pent->status; hs->Pent->mtime = hs->TimeStamp; if (status == kPFE_crypt) cmsg = FileCrypt; return 0; } } } // // Check system info, if enabled if (SysPwd) { String pwhash, fn; if (QueryCrypt(fn, pwhash) > 0) { bad = 0; status = kPFE_crypt; // Fill entry hs->Pent = cacheUser.Add(pfeRef, wTag.c_str()); hs->Pent->mtime = hs->TimeStamp; hs->Pent->status = status; hs->Pent->cnt = 0; if (!fn.beginswith("afs:")) hs->Pent->buf1.SetBuf(pwhash.c_str(),pwhash.length()+1); // Trasmit the type of credentials we have found cmsg = fn; return 0; } } // // Check server admin files if (PFAdmin.IsValid()) { // // Make sure it is uptodate XrdSysPrivGuard priv(getuid(), getgid()); if (priv.Valid()) { if (cacheAdmin.Refresh() != 0) { PRINT("problems assuring cache update for file admin "); return -1; } } hs->Pent = cacheAdmin.Get(pfeRef, wTag.c_str()); // Retrieve pwd information if (hs->Pent) { bad = 0; status = hs->Pent->status; if (status == kPFE_allowed) { if (AutoReg == kpAR_none) { // No auto-registration: disable status = kPFE_disabled; bad = 1; } } else if (status >= kPFE_ok) { // Check failure counter, if required if (MaxFailures > 0 && hs->Pent->cnt >= MaxFailures) { status = kPFE_disabled; bad = 2; } // Check expiration time, if required if (LifeCreds > 0) { int expt = hs->Pent->mtime + LifeCreds; int now = hs->TimeStamp; if (expt < now) status = kPFE_expired; } if (status != kPFE_disabled) return 0; } } pfeRef.UnLock(); // Unlock hs->Pent if we ever got a lock on it! } // // If nothing found, auto-registration is enabled, and the tag // corresponds to a local user, propose auto-registration if (bad == -1) { if (AutoReg != kpAR_none) { status = kPFE_allowed; if (AutoReg == kpAR_users) { struct passwd *pw; XrdSysPwd thePwd(hs->User.c_str(), &pw); if (!pw) { status = kPFE_disabled; bad = 1; } } } else bad = 1; } // // If disabled, fill salt string with message for the client if (status == kPFE_disabled) { char msg[XrdSutMAXPPT]; switch (bad) { case 1: snprintf(msg,XrdSutMAXPPT,"user '%s' unknown: auto-registration" " not allowed: contact %s to register", hs->User.c_str(),SrvEmail.c_str()); break; case 2: snprintf(msg,XrdSutMAXPPT,"max number of failures (%d) reached" " for user '%s': contact %s to re-activate", MaxFailures,hs->User.c_str(),SrvEmail.c_str()); break; default: msg[0] = '\0'; } cmsg.insert(msg,0,strlen(msg)); } // // We are done return 0; } //_________________________________________________________________________ int XrdSecProtocolpwd::GetUserHost(String &user, String &host) { // Resolve user and host EPNAME("GetUserHost"); // Host host = Entity.host; if (host.length() <= 0) host = getenv("XrdSecHOST"); // User user = Entity.name; if (user.length() <= 0) user = getenv("XrdSecUSER"); // If user not given, prompt for it if (user.length() <= 0) { // // Make sure somebody can be prompted if (!(hs->Tty)) { NOTIFY("user not defined:" "not tty: cannot prompt for user"); return -1; } // // This is what we want String prompt = "Enter user or tag"; if (host.length()) { prompt.append(" for host "); prompt.append(host); } prompt.append(":"); XrdSutGetLine(user,prompt.c_str()); } DEBUG(" user: "< 0) { bls->SetStep(step); buf->SetStep(step); hs->LastStep = step; } // // If a random tag has been sent and we have a session cipher, // we sign it XrdSutBucket *brt = buf->GetBucket(kXRS_rtag); if (brt && cip) { // // Encrypt random tag with session cipher if (cip->Encrypt(*brt) == 0) { PRINT("error encrypting random tag"); return -1; } // // Update type brt->type = kXRS_signed_rtag; } // Clients send in any case something session dependent: the server // may optionally decide that's enough and save one exchange. if (opt == 'c') { // // Add bucket with our timestamp to the main list if (buf->MarshalBucket(kXRS_timestamp,(kXR_int32)(hs->TimeStamp)) != 0) { PRINT("error adding bucket with time stamp"); return -1; } } // // Add an random challenge: if a next exchange is required this will // allow to prove authenticity of counter part if (opt == 's' || step != kXPC_autoreg) { // // Generate new random tag and create/update bucket String RndmTag; XrdSutRndm::GetRndmTag(RndmTag); // // Get bucket if (!(brt = new XrdSutBucket(RndmTag,kXRS_rtag))) { PRINT("error creating random tag bucket"); return -1; } buf->AddBucket(brt); // // Get cache entry if (!hs->Cref) { PRINT("cache entry not found: protocol error"); return -1; } // // Add random tag to the cache and update timestamp hs->Cref->buf1.SetBuf(brt->buffer,brt->size); hs->Cref->mtime = (kXR_int32)hs->TimeStamp; } // // Now serialize the buffer ... char *bser = 0; int nser = buf->Serialized(&bser); // // Update bucket with this content XrdSutBucket *bck = 0;; if (!(bck = bls->GetBucket(type))) { // or create new bucket, if not existing if (!(bck = new XrdSutBucket(bser,nser,type))) { PRINT("error creating bucket " <<" - type: "<AddBucket(bck); } else { bck->Update(bser,nser); } // // Encrypted the bucket if (cip) { if (cip->Encrypt(*bck) == 0) { PRINT("error encrypting bucket - cipher " <<" - type: "<GetNBuckets()) { // Create the main buffer as a copy of the buffer received if (!((*bm) = new XrdSutBuffer(br->GetProtocol(),br->GetOptions()))) { emsg = "error instantiating main buffer"; return -1; } // // Extract server version from options String opts = br->GetOptions(); int ii = opts.find("v:"); if (ii >= 0) { String sver(opts,ii+2); sver.erase(sver.find(',')); hs->RemVers = atoi(sver.c_str()); } else { hs->RemVers = Version; emsg = "server version information not found in options:" " assume same as local"; } // // Create cache if (!(hs->Cref = new XrdSutPFEntry("c"))) { emsg = "error creating cache"; return -1; } // // Save server version in cache hs->Cref->status = hs->RemVers; // // Extract server ID String srvid; ii = opts.find("id:"); if (ii >= 0) { srvid.assign(opts, ii+3); srvid.erase(srvid.find(',')); } // // Extract priority options String popt; ii = opts.find("po:"); if (ii >= 0) { popt.assign(opts, ii+3); popt.erase(popt.find(',')); // Parse it if (popt.beginswith("sys")) { hs->SysPwd = 1; } else if (popt.beginswith("afs")) { hs->SysPwd = 2; hs->AFScell.assign(popt,3); } } // // Get user and host String host; if (GetUserHost(hs->User,host) != 0) { emsg = "error getting user and host"; return -1; } // // Build tag and save it into the cache hs->Tag.resize(hs->User.length()+host.length()+srvid.length()+5); hs->Tag = hs->User; if (host.length() > 0) hs->Tag += ("@" + host); if (srvid.length() > 0) hs->Tag += (":" + srvid); // // Get server puk from cache and initialize handshake cipher if (!PFSrvPuk.IsValid()) { emsg = "file with server public keys invalid"; return -1; } char *ptag = new char[host.length()+srvid.length()+10]; if (ptag) { sprintf(ptag,"%s:%s_%d",host.c_str(),srvid.c_str(),hs->CF->ID()); bool wild = 0; XrdSutPFEntry *ent = cacheSrvPuk.Get(pfeRef, (const char *)ptag, &wild); if (ent) { // Initialize cipher SafeDelete(hs->Hcip); if (!(hs->Hcip = hs->CF->Cipher(0,ent->buf1.buf,ent->buf1.len))) { PRINT("could not instantiate session cipher " "using cipher public info from server"); emsg = "could not instantiate session cipher "; } else { DEBUG("hsHcip: 0x"<Hcip->AsHexString()); } pfeRef.UnLock(); } else { // Autoreg is the only alternative at this point ... emsg = "server puk not found in cache - tag: "; emsg += ptag; } SafeDelArray(ptag); } else emsg = "could not allocate buffer for server puk tag"; // // And we are done; return 0; } // // make sure the cache is still there if (!hs->Cref) { emsg = "cache entry not found"; return -1; } // // make sure is not too old int reftime = hs->TimeStamp - TimeSkew; if (hs->Cref->mtime < reftime) { emsg = "cache entry expired"; // Remove: should not be checked a second time SafeDelete(hs->Cref); return -1; } // // Get from cache version run by server hs->RemVers = hs->Cref->status; // // Extract the main buffer if (!(bckm = br->GetBucket(kXRS_main))) { emsg = "main buffer missing"; return -1; } // // Decrypt, if it makes sense if (hs->LastStep != kXPC_autoreg) { // // make sure the cache is still there if (!hs->Hcip) { emsg = "session cipher not found"; return -1; } // // Decrypt it if (!(hs->Hcip->Decrypt(*bckm))) { emsg = "error decrypting main buffer with session cipher"; return -1; } } // // Deserialize main buffer if (!((*bm) = new XrdSutBuffer(bckm->buffer,bckm->size))) { emsg = "error deserializing main buffer"; return -1; } // // If (new) server public keys are there extract and save them bool newpuk = 0; XrdSutBuckList *bcklst = (*bm)->GetBuckList(); XrdSutBucket *bp = bcklst->Begin(); while (bp) { if (bp->type == kXRS_puk) { newpuk = 1; // ID is in the first 4 chars ( ....'\0') char cid[5] = {0}; memcpy(cid, bp->buffer, 5); int id = atoi(cid); // Build tag String ptag(hs->Tag); ptag.erase(0,ptag.find('@')+1); ptag += '_'; ptag += cid; // Update or create new entry XrdSutPFEntry *ent = cacheSrvPuk.Add(pfeRef, ptag.c_str()); if (ent) { // Set buffer ent->buf1.SetBuf((bp->buffer)+5,(bp->size)-5); ent->mtime = hs->TimeStamp; if (id == hs->CF->ID()) { // Initialize cipher SafeDelete(hs->Hcip); if (!(hs->Hcip = hs->CF->Cipher(0,ent->buf1.buf,ent->buf1.len))) { PRINT("could not instantiate session cipher " "using cipher public info from server"); emsg = "could not instantiate session cipher "; } else { DEBUG("hsHcip: 0x"<Hcip->AsHexString()); } } pfeRef.UnLock(); } else { // Autoreg is the only alternative at this point ... PRINT("could not create entry in cache - tag: "<Next(); } (*bm)->Deactivate(kXRS_puk); // Update the puk file (for the other sessions ...) if (newpuk) cacheSrvPuk.Flush(); // // We are done return 0; } //_________________________________________________________________________ int XrdSecProtocolpwd::ParseServerInput(XrdSutBuffer *br, XrdSutBuffer **bm, String &cmsg) { // Parse received buffer b, extracting and decrypting the main // buffer *bm and extracting the session // cipher, random tag buckets and user name, if any. // Results used to fill the local handshake variables EPNAME("ParseServerInput"); // Space for pointer to main buffer must be already allocated if (!br || !bm) { PRINT("invalid inputs ("<GetBucket(kXRS_main))) { cmsg = "main buffer missing"; return -1; } // // First get the session cipher if ((bck = br->GetBucket(kXRS_puk))) { // // Cleanup SafeDelete(hs->Hcip); // // Prepare cipher agreement: make sure we have the reference cipher if (!hs->Rcip) { cmsg = "reference cipher missing"; return -1; } // Prepare cipher agreement: get a copy of the reference cipher if (!(hs->Hcip = hs->CF->Cipher(*hs->Rcip))) { cmsg = "cannot get reference cipher"; return -1; } // // Instantiate the session cipher if (!(hs->Hcip->Finalize(bck->buffer,bck->size,0))) { cmsg = "cannot finalize session cipher"; return -1; } // // We need it only once br->Deactivate(kXRS_puk); } // // Decrypt the main buffer with the session cipher, if available if (hs->Hcip) { if (!(hs->Hcip->Decrypt(*bckm))) { cmsg = "error decrypting main buffer with session cipher"; return -1; } } // // Deserialize main buffer if (!((*bm) = new XrdSutBuffer(bckm->buffer,bckm->size))) { cmsg = "error deserializing main buffer"; return -1; } // // Get version run by client, if there if (hs->RemVers == -1) { if ((*bm)->UnmarshalBucket(kXRS_version,hs->RemVers) != 0) { hs->RemVers = Version; cmsg = "client version information not found in options:" " assume same as local"; } else { (*bm)->Deactivate(kXRS_version); } } // // Get cache entry or create a new one if (!hs->Cref) { // Create it if (!(hs->Cref = new XrdSutPFEntry(hs->ID.c_str()))) { cmsg = "cannot create cache entry"; return -1; } } else { // // make sure cache is not too old int reftime = hs->TimeStamp - TimeSkew; if (hs->Cref->mtime < reftime) { cmsg = "cache entry expired"; SafeDelete(hs->Cref); return -1; } } // // Extract user name, if any if ((bck = (*bm)->GetBucket(kXRS_user))) { if (hs->User.length() <= 0) { bck->ToString(hs->User); // Build tag hs->Tag = hs->User; } (*bm)->Deactivate(kXRS_user); } // // We are done return 0; } //__________________________________________________________________ void XrdSecProtocolpwd::ErrF(XrdOucErrInfo *einfo, kXR_int32 ecode, const char *msg1, const char *msg2, const char *msg3) { // Filling the error structure EPNAME("ErrF"); char *msgv[12]; int k, i = 0, sz = strlen("Secpwd"); // // Code message, if any int cm = (ecode >= kPWErrParseBuffer && ecode <= kPWErrError) ? (ecode-kPWErrParseBuffer) : -1; const char *cmsg = (cm > -1) ? gPWErrStr[cm] : 0; // // Build error message array msgv[i++] = (char *)"Secpwd"; //0 if (cmsg) {msgv[i++] = (char *)": "; //1 msgv[i++] = (char *)cmsg; //2 sz += strlen(msgv[i-1]) + 2; } if (msg1) {msgv[i++] = (char *)": "; //3 msgv[i++] = (char *)msg1; //4 sz += strlen(msgv[i-1]) + 2; } if (msg2) {msgv[i++] = (char *)": "; //5 msgv[i++] = (char *)msg2; //6 sz += strlen(msgv[i-1]) + 2; } if (msg3) {msgv[i++] = (char *)": "; //7 msgv[i++] = (char *)msg3; //8 sz += strlen(msgv[i-1]) + 2; } // save it (or print it) if (einfo) { einfo->setErrInfo(ecode, (const char **)msgv, i); } if (QTRACE(Debug)) { char *bout = new char[sz+10]; if (bout) { bout[0] = 0; for (k = 0; k < i; k++) strcat(bout, msgv[k]); PRINT(bout); } else { for (k = 0; k < i; k++) PRINT(msgv[k]); } } } //__________________________________________________________________ XrdSecCredentials *XrdSecProtocolpwd::ErrC(XrdOucErrInfo *einfo, XrdSutBuffer *b1, XrdSutBuffer *b2, XrdSutBuffer *b3, kXR_int32 ecode, const char *msg1, const char *msg2, const char *msg3) { // Error logging client method // Fill the error structure ErrF(einfo, ecode, msg1, msg2, msg3); // Release buffers REL3(b1,b2,b3); // We are done return (XrdSecCredentials *)0; } //__________________________________________________________________ int XrdSecProtocolpwd::ErrS(String ID, XrdOucErrInfo *einfo, XrdSutBuffer *b1, XrdSutBuffer *b2, XrdSutBuffer *b3, kXR_int32 ecode, const char *msg1, const char *msg2, const char *msg3) { // Error logging server method // Fill the error structure ErrF(einfo, ecode, msg1, msg2, msg3); // Release buffers REL3(b1,b2,b3); // We are done return kpST_error; } //_______________________________________________________________________ int XrdSecProtocolpwd::DoubleHash(XrdCryptoFactory *cf, XrdSutBucket *bck, XrdSutBucket *s1, XrdSutBucket *s2, const char *tag) { // Apply single or double hash to bck using salts // in s1 and (if defined) s2. // Store result in *buf, with the new length in len. // Return 0 if ok or -1 otherwise EPNAME("DoubleHash"); // // Check inputs if (!cf || !bck) { PRINT("Bad inputs "<size <= 0) && (!s2 || s2->size <= 0)) { PRINT("Both salts undefined - do nothing"); return 0; } // // Tag length, if there int ltag = (tag) ? strlen(tag) + 1 : 0; // // Get one-way hash function XrdCryptoKDFun_t KDFun = cf->KDFun(); XrdCryptoKDFunLen_t KDFunLen = cf->KDFunLen(); if (!KDFun || !KDFunLen) { PRINT("Could not get hooks to one-way hash functions (" <buffer; int nhlen = bck->size; if (s1 && s1->size > 0) { if (!(nhash = new char[(*KDFunLen)() + ltag])) { PRINT("Could not allocate memory for hash - s1"); return -1; } if ((nhlen = (*KDFun)(thash,nhlen, s1->buffer,s1->size,nhash+ltag,0)) <= 0) { PRINT("Problems hashing - s1"); delete[] nhash; return -1; } thash = nhash; } // // Apply second salt, if defined if (s2 && s2->size > 0) { if (!(nhash = new char[(*KDFunLen)() + ltag])) { PRINT("Could not allocate memory for hash - s2"); return -1; } if (thash && thash != bck->buffer) thash += ltag; if ((nhlen = (*KDFun)(thash,nhlen, s2->buffer,s2->size,nhash+ltag,0)) <= 0) { PRINT("Problems hashing - s2"); delete[] nhash; if (thash && thash != bck->buffer) delete[] thash; return -1; } if (thash && thash != bck->buffer) delete[] thash; thash = nhash; } // // Add tag if there if (tag) memcpy(thash,tag,ltag); // // Save result bck->SetBuf(thash,nhlen+ltag); // // We are done return 0; } //______________________________________________________________________________ int XrdSecProtocolpwd::QueryCrypt(String &fn, String &pwhash) { // Retrieve crypt-like password-hash from $HOME/fn or from system password files, // if accessible. // To avoid problems with NFS-root-squashing, if 'root' changes temporarly the // uid/gid to those of the target user (usr). // If OK, returns pass length and fill 'pass' with the password, null-terminated. // ('pass' is allocated externally to contain max lpwmax bytes). // If the file does not exists, return 0 and an empty pass. // If any problems with the file occurs, return a negative // code, -2 indicating wrong file permissions. // If any problem with changing ugid's occurs, prints a warning trying anyhow // to read the password hash. EPNAME("QueryCrypt"); int rc = -1; int len = 0, n = 0, fid = -1; pwhash = ""; DEBUG("analyzing file: "<User.c_str(), &pw); if (!pw) { PRINT("Cannot get pwnam structure for user "<User); return -1; } // // Check the user specific file first, if requested if (fn.length() > 0) { // target uid int uid = pw->pw_uid; // Acquire the privileges, if needed XrdSysPrivGuard priv(uid, pw->pw_gid); bool go = priv.Valid(); if (!go) { PRINT("problems acquiring temporarly identity: "<User); } // The file String fpw(pw->pw_dir, strlen(pw->pw_dir) + fn.length() + 5); if (go) { fpw += ("/" + fn); DEBUG("checking file "<User); } // Check first the permissions: should be 0600 struct stat st; if (go && stat(fpw.c_str(), &st) == -1) { if (errno != ENOENT) { PRINT("cannot stat password file "< -1) close(fid); // Get rid of special trailing chars if (go) { len = n; while (len-- && (pass[len] == '\n' || pass[len] == 32)) pass[len] = 0; // Null-terminate pass[++len] = 0; rc = len; // Prepare for output pwhash = pass; } } // // If we go a pw-hash we are done if (pwhash.length() > 0) return rc; // // If not, we check the system files #ifdef HAVE_SHADOWPW { // Acquire the privileges; needs to be 'superuser' to access the // shadow password file XrdSysPrivGuard priv((uid_t)0, (gid_t)0); if (priv.Valid()) { struct spwd *spw = 0; // System V Rel 4 style shadow passwords if ((spw = getspnam(hs->User.c_str())) == 0) { NOTIFY("shadow passwd not accessible to this application"); } else pwhash = spw->sp_pwdp; } else { NOTIFY("problems acquiring temporarly superuser privileges"); } } #else pwhash = pw->pw_passwd; #endif // // This is send back to the client to locate autologin info fn = "system"; // Check if successful if ((rc = pwhash.length()) <= 2) { NOTIFY("passwd hash not available for user "<User); pwhash = ""; fn = ""; rc = -1; } // We are done return rc; } //______________________________________________________________________________ int XrdSecProtocolpwd::QueryNetRc(String host, String &passwd, int &status) { // Check netrc-like file defined by env 'XrdSecNETRC' for password information // matching ('user','host') and return the password in 'passwd'. // If found, 'status' is filled with 'kpCI_exact' or 'kpCI_wildcard' // depending the type of match. // Same syntax as $HOME/.netrc is required; wild cards for hosts are // supported: examples // // machine oplapro027.cern.ch login qwerty password Rt8dsAvV0 // machine lxplus*.cern.ch login poiuyt password WtHAyD0iG // // Returns 0 is something found, -1 otherwise. // NB: file permissions must be: readable/writable by the owner only EPNAME("QueryNetRc"); passwd = ""; // // Make sure a file name is defined String fnrc = getenv("XrdSecNETRC"); if (fnrc.length() <= 0) { PRINT("File name undefined"); return -1; } // Resolve place-holders, if any if (XrdSutResolve(fnrc, Entity.host, Entity.vorg, Entity.grps, Entity.name) != 0) { PRINT("Problems resolving templates in "<User); // Check first the permissions: should be 0600 struct stat st; if (stat(fnrc.c_str(), &st) == -1) { if (errno != ENOENT) { PRINT("cannot stat password file "< 0) { // Host matches if (!strcmp(hs->User.c_str(),word[3])) { // User matches: if exact match we are done if (nm == host.length()) { passwd = word[5]; status = kpCI_exact; break; } // Else, we focalise on the best match if (nm > nmmx) { nmmx = nm; passwd = word[5]; status = kpCI_wildcard; } } } } // // Close the file fclose(fid); // // We are done if (passwd.length() > 0) return 0; return -1; } //______________________________________________________________________________ bool XrdSecProtocolpwd::CheckTimeStamp(XrdSutBuffer *bm, int skew, String &emsg) { // Check consistency of the time stamp in bucket kXRS_timestamp in bm; // skew is the allowed difference in times. // Return 1 if ok, 0 if not EPNAME("CheckTimeStamp"); // Check inputs if (!bm || skew <= 0) { if (!bm) emsg = "input buffer undefined "; else emsg = "negative skew: invalid "; return 0; } // We check only if requested and a stronger check has not been done // successfully already if (hs->RtagOK || VeriClnt != 1) { NOTIFY("Nothing to do"); // Deactivate the buffer, if there if (bm->GetBucket(kXRS_timestamp)) bm->Deactivate(kXRS_timestamp); return 1; } // // Add bucket with our version to the main list kXR_int32 tstamp = 0; if (bm->UnmarshalBucket(kXRS_timestamp,tstamp) != 0) { emsg = "bucket with time stamp not found"; return 0; } kXR_int32 dtim = hs->TimeStamp - tstamp; dtim = (dtim < 0) ? -dtim : dtim; if (dtim > skew) { emsg = "time difference too big: "; emsg += (int)dtim; emsg += " - allowed skew: "; emsg += skew; bm->Deactivate(kXRS_timestamp); return 0; } bm->Deactivate(kXRS_timestamp); DEBUG("Time stamp successfully checked"); // Ok return 1; } //______________________________________________________________________________ bool XrdSecProtocolpwd::CheckRtag(XrdSutBuffer *bm, String &emsg) { // Check random tag signature if it was sent with previous packet EPNAME("CheckRtag"); // Make sure we got a buffer if (!bm) { emsg = "Buffer not defined"; return 0; } // // If we sent out a random tag check it signature if (hs->Cref && hs->Cref->buf1.len > 0) { XrdSutBucket *brt = 0; if ((brt = bm->GetBucket(kXRS_signed_rtag))) { // Make suer we got a cipher if (!(hs->Hcip)) { emsg = "Session cipher undefined"; return 0; } // Decrypt it with the session cipher if (!(hs->Hcip->Decrypt(*brt))) { emsg = "error decrypting random tag with session cipher"; return 0; } } else { emsg = "random tag missing - protocol error"; return 0; } // // Random tag cross-check: content if (memcmp(brt->buffer,hs->Cref->buf1.buf,hs->Cref->buf1.len)) { emsg = "random tag content mismatch"; SafeDelete(hs->Cref); // Remove: should not be checked a second time return 0; } // // Reset the cache entry but we will not use the info a second time memset(hs->Cref->buf1.buf,0,hs->Cref->buf1.len); hs->Cref->buf1.SetBuf(); // // Flag successful check hs->RtagOK = 1; bm->Deactivate(kXRS_signed_rtag); DEBUG("Random tag successfully checked"); } else { NOTIFY("Nothing to check"); } // We are done return 1; }