/******************************************************************************/ /* */ /* X r d A c c A c c e s s . c c */ /* */ /* (c) 2003 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 "XrdVersion.hh" #include "XrdAcc/XrdAccAccess.hh" #include "XrdAcc/XrdAccCapability.hh" #include "XrdAcc/XrdAccConfig.hh" #include "XrdAcc/XrdAccGroups.hh" #include "XrdNet/XrdNetAddrInfo.hh" #include "XrdOuc/XrdOucUtils.hh" #include "XrdSys/XrdSysPlugin.hh" /******************************************************************************/ /* E x t e r n a l R e f e r e n c e s */ /******************************************************************************/ extern unsigned long XrdOucHashVal2(const char *KeyVal, int KeyLen); /******************************************************************************/ /* G l o b a l C o n f i g u r a t i o n O b j e c t */ /******************************************************************************/ extern XrdAccConfig XrdAccConfiguration; /******************************************************************************/ /* Autorization Object Creation via XrdAccDefaultAuthorizeObject */ /******************************************************************************/ XrdAccAuthorize *XrdAccDefaultAuthorizeObject(XrdSysLogger *lp, const char *cfn, const char *parm, XrdVersionInfo &urVer) { static XrdVERSIONINFODEF(myVer, XrdAcc, XrdVNUMBER, XrdVERSION); static XrdSysError Eroute(lp, "acc_"); // Verify version compatability // if (urVer.vNum != myVer.vNum && !XrdSysPlugin::VerCmp(urVer,myVer)) return 0; // Configure the authorization system // if (XrdAccConfiguration.Configure(Eroute, cfn)) return (XrdAccAuthorize *)0; // All is well, return the actual pointer to the object // return (XrdAccAuthorize *)XrdAccConfiguration.Authorization; } /******************************************************************************/ /* C o n s t r u c t o r */ /******************************************************************************/ XrdAccAccess::XrdAccAccess(XrdSysError *erp) { // Get the audit option that we should use // Auditor = XrdAccAuditObject(erp); } /******************************************************************************/ /* A c c e s s */ /******************************************************************************/ XrdAccPrivs XrdAccAccess::Access(const XrdSecEntity *Entity, const char *path, const Access_Operation oper, XrdOucEnv *Env) { const char *xP; char *gname, xBuff[64]; XrdAccGroupList *glp; XrdAccPrivCaps caps; XrdAccCapability *cp; const int plen = strlen(path); const long phash = XrdOucHashVal2(path, plen); const char *id = (Entity->name ? (const char *)Entity->name : "*"); const char *host = 0; int n, isuser = (*id && (*id != '*' || id[1])); // Get a shared context for these potentially long running routines // Access_Context.Lock(xs_Shared); // Run through the exclusive list first as only one rule will apply // XrdAccAccess_ID *xlP = Atab.SXList; while (xlP) {if (xlP->Applies(Entity)) {xlP->caps->Privs(caps, path, plen, phash); Access_Context.UnLock(xs_Shared); return Access(caps, Entity, path, oper); } xlP = xlP->next; } // Check if we really need to resolve the host name // if (Atab.D_List || Atab.H_Hash || Atab.N_Hash) host = Resolve(Entity); // Establish default privileges // if (Atab.Z_List) Atab.Z_List->Privs(caps, path, plen, phash); // Next add in the host domain privileges // if (Atab.D_List && host && (cp = Atab.D_List->Find(host))) cp->Privs(caps, path, plen, phash); // Next add in the host-specific privileges // if (Atab.H_Hash && host && (cp = Atab.H_Hash->Find(host))) cp->Privs(caps, path, plen, phash); // Check for user fungible privileges // if (isuser && Atab.X_List) Atab.X_List->Privs(caps, path, plen, phash, id); // Add in specific user privileges // if (isuser && Atab.U_Hash && (cp = Atab.U_Hash->Find(id))) cp->Privs(caps, path, plen, phash); // Next add in the group privileges. The group list either comes from the // credentials, in which case we need not have a username, or from the // standard unix-username group mapping. // if (Atab.G_Hash) {if (Entity->grps) {xP = Entity->grps; while((n = XrdOucUtils::Token(&xP, ' ', xBuff, sizeof(xBuff)))) {if (n < (int)sizeof(xBuff) && (cp = Atab.G_Hash->Find(xBuff))) cp->Privs(caps, path, plen, phash); } } else if (isuser && (glp=XrdAccConfiguration.GroupMaster.Groups(id))) {while((gname = (char *)glp->Next())) if ((cp = Atab.G_Hash->Find((const char *)gname))) cp->Privs(caps, path, plen, phash); delete glp; } } // Now add in the netgroup privileges // if (Atab.N_Hash && id && host && (glp = XrdAccConfiguration.GroupMaster.NetGroups(id, host))) {while((gname = (char *)glp->Next())) if ((cp = Atab.N_Hash->Find((const char *)gname))) cp->Privs(caps, path, plen, phash); delete glp; } // Next add in the org-specific privileges // if (Atab.O_Hash && Entity->vorg) {xP = Entity->vorg; while((n = XrdOucUtils::Token(&xP, ' ', xBuff, sizeof(xBuff)))) {if (n < (int)sizeof(xBuff) && (cp = Atab.O_Hash->Find(xBuff))) cp->Privs(caps, path, plen, phash); } } // Next add in the role-specific privileges // if (Atab.R_Hash && Entity->role) {xP = Entity->role; while((n = XrdOucUtils::Token(&xP, ' ', xBuff, sizeof(xBuff)))) {if (n < (int)sizeof(xBuff) && (cp = Atab.R_Hash->Find(xBuff))) cp->Privs(caps, path, plen, phash); } } // Finally run through the inclusive list and apply arr relevant rules // XrdAccAccess_ID *ylP = Atab.SYList; while (ylP) {if (ylP->Applies(Entity)) ylP->caps->Privs(caps, path, plen, phash); ylP = ylP->next; } // We are now done with looking at changeable data // Access_Context.UnLock(xs_Shared); // Return the privileges as needed // return Access(caps, Entity, path, oper); } /******************************************************************************/ XrdAccPrivs XrdAccAccess::Access( XrdAccPrivCaps &caps, const XrdSecEntity *Entity, const char *path, const Access_Operation oper ) { XrdAccPrivs myprivs; XrdAccAudit_Options audits = (XrdAccAudit_Options)Auditor->Auditing(); int accok; // Compute composite privileges and see if privs need to be returned // myprivs = (XrdAccPrivs)(caps.pprivs & ~caps.nprivs); if (!oper) return (XrdAccPrivs)myprivs; // Check if auditing is enabled or whether we can do a fastaroo test // if (!audits) return (XrdAccPrivs)Test(myprivs, oper); if ((accok = Test(myprivs, oper)) && !(audits & audit_grant)) return (XrdAccPrivs)accok; // Call the auditing routine and exit // return (XrdAccPrivs)Audit(accok, Entity, path, oper); } /******************************************************************************/ /* A u d i t */ /******************************************************************************/ int XrdAccAccess::Audit(const int accok, const XrdSecEntity *Entity, const char *path, const Access_Operation oper, XrdOucEnv *Env) { // Warning! This table must be in 1-to-1 correspondence with Access_Operation // static const char *Opername[] = {"any", // 0 "chmod", // 1 "chown", // 2 "create", // 3 "delete", // 4 "insert", // 5 "lock", // 6 "mkdir", // 7 "read", // 8 "readdir", // 9 "rename", // 10 "stat", // 10 "update" // 12 }; const char *opname = (oper > AOP_LastOp ? "???" : Opername[oper]); const char *id = (Entity->name ? (const char *)Entity->name : "*"); const char *host = (Entity->host ? (const char *)Entity->host : "?"); char atype[XrdSecPROTOIDSIZE+1]; // Get the protocol type in a printable format // strncpy(atype, Entity->prot, XrdSecPROTOIDSIZE); atype[XrdSecPROTOIDSIZE] = '\0'; // Route the message appropriately // if (accok) Auditor->Grant(opname, Entity->tident, atype, id, host, path); else Auditor->Deny( opname, Entity->tident, atype, id, host, path); // All done, finally // return accok; } /******************************************************************************/ /* R e s o l v e */ /******************************************************************************/ const char *XrdAccAccess::Resolve(const XrdSecEntity *Entity) { // Make a quick test for IPv6 (as that's the future) and a minimal one for ipV4 // to see if we have to do a DNS lookup. // if (Entity->host == 0 || *(Entity->host) == '[' || isdigit(*(Entity->host))) return Entity->addrInfo->Name("?"); return Entity->host; } /******************************************************************************/ /* S w a p T a b s */ /******************************************************************************/ #define XrdAccSWAP(x) oldtab.x = Atab.x; Atab.x = newtab.x; \ newtab.x = oldtab.x; oldtab.x = 0; void XrdAccAccess::SwapTabs(struct XrdAccAccess_Tables &newtab) { struct XrdAccAccess_Tables oldtab; // Get an exclusive context to change the table pointers // Access_Context.Lock(xs_Exclusive); // Save the old pointer while replacing it with the new pointer // XrdAccSWAP(D_List); XrdAccSWAP(E_List); XrdAccSWAP(G_Hash); XrdAccSWAP(H_Hash); XrdAccSWAP(N_Hash); XrdAccSWAP(O_Hash); XrdAccSWAP(R_Hash); XrdAccSWAP(S_Hash); XrdAccSWAP(T_Hash); XrdAccSWAP(U_Hash); XrdAccSWAP(X_List); XrdAccSWAP(Z_List); XrdAccSWAP(SXList); XrdAccSWAP(SYList); // When we set new access tables, we should purge the group cache // XrdAccConfiguration.GroupMaster.PurgeCache(); // We can now let loose new table searchers // Access_Context.UnLock(xs_Exclusive); } /******************************************************************************/ /* T e s t */ /******************************************************************************/ int XrdAccAccess::Test(const XrdAccPrivs priv,const Access_Operation oper) { // Warning! This table must be in 1-to-1 correspondence with Access_Operation // static XrdAccPrivs need[] = {XrdAccPriv_None, // 0 XrdAccPriv_Chmod, // 1 XrdAccPriv_Chown, // 2 XrdAccPriv_Create, // 3 XrdAccPriv_Delete, // 4 XrdAccPriv_Insert, // 5 XrdAccPriv_Lock, // 6 XrdAccPriv_Mkdir, // 7 XrdAccPriv_Read, // 8 XrdAccPriv_Readdir, // 9 XrdAccPriv_Rename, // 10 XrdAccPriv_Lookup, // 11 XrdAccPriv_Update // 12 }; if (oper < 0 || oper > AOP_LastOp) return 0; return (int)(need[oper] & priv) == need[oper]; } /******************************************************************************/ /* X r d A c c A c c e s s _ I D : : A p p l i e s */ /******************************************************************************/ bool XrdAccAccess_ID::Applies(const XrdSecEntity *Entity) { const char *hName, *gList, *gEnd; int eLen; // Check single value items in the most probable use order // if (org && (!Entity->vorg || strcmp(org, Entity->vorg))) return false; if (role && (!Entity->role || strcmp(role, Entity->role))) return false; if (user && (!Entity->name || strcmp(user, Entity->name))) return false; // The check is more complicated as the host field may be an address. We make // a quick test for IPv6 (as that's the future) and take the long road for ipV4. // if (host) {hName = XrdAccAccess::Resolve(Entity); if (*host == '.') {eLen = strlen(hName); if (eLen <= hlen) return false; hName = hName + eLen - hlen; } if (strcmp(host, hName)) return false; } // Groups are most problematic as there may be many of them. So it's last. // if (!grp) return true; if (!Entity->grps) return false; eLen = strlen(Entity->grps); if (eLen < glen) return false; // Search through the group list // gList = Entity->grps; while(true) {if (!strncmp(grp, Entity->grps, glen)) {gEnd = Entity->grps + glen; if (*gEnd == ' ' || *gEnd == 0) return true; } if(!(gList = index(gList, ' '))) break; do {gList++;} while(*gList == ' '); } // This entry is not applicable // return false; }