// ---------------------------------------------------------------------- // File: Access.cc // Author: Andreas-Joachim Peters - CERN // ---------------------------------------------------------------------- /************************************************************************ * EOS - the CERN Disk Storage System * * Copyright (C) 2011 CERN/Switzerland * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see .* ************************************************************************/ // ----------------------------------------------------------------------- // This file is included source code in XrdMgmOfs.cc to make the code more // transparent without slowing down the compilation time. // ----------------------------------------------------------------------- /*----------------------------------------------------------------------------*/ int XrdMgmOfs::access(const char* inpath, int mode, XrdOucErrInfo& error, const XrdSecEntity* client, const char* ininfo) /*----------------------------------------------------------------------------*/ /* * @brief check access permissions for file/directories * * @param inpath path to access * @param mode access mode can be R_OK |& W_OK |& X_OK or F_OK * @param client XRootD authentication object * @param ininfo CGI * @return SFS_OK if possible otherwise SFS_ERROR * * See the internal implementation _access for details */ /*----------------------------------------------------------------------------*/ { static const char* epname = "access"; const char* tident = error.getErrUser(); NAMESPACEMAP; BOUNCE_ILLEGAL_NAMES; XrdOucEnv access_Env(ininfo); AUTHORIZE(client, &access_Env, AOP_Stat, "access", inpath, error); // use a thread private vid eos::common::VirtualIdentity vid; EXEC_TIMING_BEGIN("IdMap"); eos::common::Mapping::IdMap(client, ininfo, tident, vid, gOFS->mTokenAuthz, AOP_Stat, inpath); EXEC_TIMING_END("IdMap"); gOFS->MgmStats.Add("IdMap", vid.uid, vid.gid, 1); BOUNCE_NOT_ALLOWED; TOKEN_SCOPE; ACCESSMODE_R; MAYSTALL; MAYREDIRECT; return _access(path, mode, error, vid, ininfo); } /*----------------------------------------------------------------------------*/ int XrdMgmOfs::_access(const char* path, int mode, XrdOucErrInfo& error, eos::common::VirtualIdentity& vid, const char* info) /*----------------------------------------------------------------------------*/ /* * @brief check access permissions for file/directories * * @param inpath path to access * @param mode access mode can be R_OK |& W_OK |& X_OK |& F_OK or P_OK * @param client XRootD authentication object * @param ininfo CGI * @return SFS_OK if possible otherwise SFS_ERROR * * If F_OK is specified we just check for the existence of the path, which can * be a file or directory. We don't support X_OK since it cannot be mapped * in case of files (we don't have explicit execution permissions). */ /*----------------------------------------------------------------------------*/ { static const char* epname = "_access"; eos_debug("path=%s mode=%x uid=%u gid=%u", path, mode, vid.uid, vid.gid); gOFS->MgmStats.Add("Access", vid.uid, vid.gid, 1); eos::common::Path cPath(path); std::shared_ptr dh; std::shared_ptr fh; bool permok = false; std::string attr_path = cPath.GetPath(); // --------------------------------------------------------------------------- eos::Prefetcher::prefetchItemAndWait(gOFS->eosView, cPath.GetPath()); eos::IFileMD::IFileMDReadLockerPtr fhLock; eos::IContainerMD::IContainerMDReadLockerPtr dhLock; // check for existing file try { fhLock = gOFS->eosView->getFileReadLocked(cPath.GetPath()); fh = fhLock->getUnderlyingPtr(); } catch (eos::MDException& e) { eos_debug("msg=\"exception\" ec=%d emsg=\"%s\"", e.getErrno(), e.getMessage().str().c_str()); } try { dhLock = gOFS->eosView->getContainerReadLocked(cPath.GetPath()); dh = dhLock->getUnderlyingPtr(); } catch (eos::MDException& e) { eos_debug("msg=\"exception\" ec=%d emsg=\"%s\"", e.getErrno(), e.getMessage().str().c_str()); } errno = 0; try { eos::IContainerMD::XAttrMap attrmap; eos::IContainerMD::XAttrMap fattrmap; if (fh || (!dh)) { std::string uri; // if this is a file or a not existing directory we check the access on the parent directory if (fh) { uri = gOFS->eosView->getUri(fh.get()); fattrmap = fh->getAttributes(); } else { uri = cPath.GetPath(); } eos::common::Path pPath(uri.c_str()); dhLock = gOFS->eosView->getContainerReadLocked(pPath.GetParentPath()); dh = dhLock->getUnderlyingPtr(); attr_path = pPath.GetParentPath(); } // ACL and permission check Acl acl(attr_path.c_str(), error, vid, attrmap, false); if (fattrmap.size()) { // take into account file acls acl.SetFromAttrMap(attrmap, vid, &fattrmap); } eos_info("acl=%d r=%d w=%d wo=%d x=%d egroup=%d mutable=%d", acl.HasAcl(), acl.CanRead(), acl.CanWrite(), acl.CanWriteOnce(), acl.CanBrowse(), acl.HasEgroup(), acl.IsMutable()); if (!AccessChecker::checkContainer(dh.get(), acl, mode, vid)) { errno = EPERM; return Emsg(epname, error, EPERM, "access", path); } if (fh && !AccessChecker::checkFile(fh.get(), mode, vid)) { errno = EPERM; return Emsg(epname, error, EPERM, "access", path); } permok = true; } catch (eos::MDException& e) { dh.reset(); errno = e.getErrno(); eos_debug("msg=\"exception\" ec=%d emsg=\"%s\"\n", e.getErrno(), e.getMessage().str().c_str()); } // Check permissions if (!dh) { eos_debug("msg=\"access\" errno=ENOENT"); errno = ENOENT; return Emsg(epname, error, ENOENT, "access", path); } // root/daemon can always access, daemon only for reading! if ((vid.uid == 0) || ((vid.uid == DAEMONUID) && (!(mode & W_OK)))) { permok = true; } if (dh) { eos_debug("msg=\"access\" uid=%d gid=%d retc=%d mode=%o", vid.uid, vid.gid, permok, dh->getMode()); } if (dh && (mode & F_OK)) { return SFS_OK; } if (dh && permok) { return SFS_OK; } if (dh && (!permok)) { errno = EACCES; return Emsg(epname, error, EACCES, "access", path); } // check publicaccess level if (!allow_public_access(path, vid)) { errno = EACCES; return Emsg(epname, error, EACCES, "access - public access level restriction", path); } errno = EOPNOTSUPP; return Emsg(epname, error, EOPNOTSUPP, "access", path); } //------------------------------------------------------------------------------ // Define access permissions by vid for a file/directory //------------------------------------------------------------------------------ int XrdMgmOfs::acc_access(const char* path, XrdOucErrInfo& error, eos::common::VirtualIdentity& vid, std::string& accperm) { eos_debug("path=%s mode=%x uid=%u gid=%u", path, vid.uid, vid.gid); gOFS->MgmStats.Add("Access", vid.uid, vid.gid, 1); eos::common::Path cPath(path); std::shared_ptr dh; std::shared_ptr fh; std::string attr_path = cPath.GetPath(); bool r_ok = false; bool w_ok = false; bool x_ok = false; bool d_ok = false; // --------------------------------------------------------------------------- eos::Prefetcher::prefetchItemAndWait(gOFS->eosView, cPath.GetPath()); eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex); // check for existing file try { fh = gOFS->eosView->getFile(cPath.GetPath()); } catch (eos::MDException& e) { eos_debug("msg=\"exception\" ec=%d emsg=\"%s\"\n", e.getErrno(), e.getMessage().str().c_str()); } if (!fh) { // check for existing dir if not a file try { dh = gOFS->eosView->getContainer(cPath.GetPath()); } catch (eos::MDException& e) { eos_debug("msg=\"exception\" ec=%d emsg=\"%s\"\n", e.getErrno(), e.getMessage().str().c_str()); } } try { eos::IContainerMD::XAttrMap attrmap; if (fh || (!dh)) { std::string uri; // if this is a file or a not existing directory we check the access on the parent directory if (fh) { uri = gOFS->eosView->getUri(fh.get()); } else { uri = cPath.GetPath(); } eos::common::Path pPath(uri.c_str()); dh = gOFS->eosView->getContainer(pPath.GetParentPath()); attr_path = pPath.GetParentPath(); } std::set gids; if (eos::common::Mapping::gSecondaryGroups) { gids = vid.allowed_gids; } else { gids.insert(vid.gid); } for (auto g : gids) { if (dh->access(vid.uid, g, R_OK)) { r_ok = true; } if (dh->access(vid.uid, g, W_OK)) { w_ok = true; d_ok = true; } if (dh->access(vid.uid, g, X_OK)) { x_ok = true; } } lock.Release(); // ACL and permission check Acl acl(attr_path.c_str(), error, vid, attrmap, false); eos_info("acl=%d r=%d w=%d wo=%d x=%d egroup=%d mutable=%d", acl.HasAcl(), acl.CanRead(), acl.CanWrite(), acl.CanWriteOnce(), acl.CanBrowse(), acl.HasEgroup(), acl.IsMutable()); // browse permission by ACL if (acl.HasAcl()) { if (acl.CanWrite()) { w_ok = true; d_ok = true; } // write-once or write is fine for OC write permission if (!(acl.CanWrite() || acl.CanWriteOnce())) { w_ok = false; } // deletion might be overwritten/forbidden if (acl.CanNotDelete()) { d_ok = false; } // the r/x are added to the posix permissions already set if (acl.CanRead()) { r_ok |= true; } if (acl.CanBrowse()) { x_ok |= true; } if (!acl.IsMutable()) { w_ok = d_ok = false; } } } catch (eos::MDException& e) { dh = std::shared_ptr((eos::IContainerMD*)0); errno = e.getErrno(); eos_debug("msg=\"exception\" ec=%d emsg=\"%s\"\n", e.getErrno(), e.getMessage().str().c_str()); } // check permissions if (!dh) { accperm = ""; return SFS_ERROR; } // return the OC string; if (r_ok) { accperm += "R"; } if (w_ok) { accperm += "WCKNV"; } if (d_ok) { accperm += "D"; } return SFS_OK; } //------------------------------------------------------------------------------ // Test if this is eosnobody accessing a squashfs file //------------------------------------------------------------------------------ int XrdMgmOfs::is_squashfs_access(const char* path, eos::common::VirtualIdentity& vid) { static int errc=0; static int eosnobody = eos::common::Mapping::UserNameToUid(std::string("eosnobody"),errc); if ((vid.prot == "sss") && (eosnobody == vid.uid) && !errc) { // eosnobody can access all squash files eos::common::Path cPath(path); if (!cPath.isSquashFile()) { errno = EACCES; return 1; } return 2; } return 0; } //------------------------------------------------------------------------------ // Test if public access is allowed for a given path //------------------------------------------------------------------------------ bool XrdMgmOfs::allow_public_access(const char* path, eos::common::VirtualIdentity& vid) { int sq = is_squashfs_access(path, vid); if (sq == 2) { // eosnobody squashfs file access is allowed return true; } if (sq == 1) { // eosnobody access is not allowed in general return false; } // check only for anonymous access if (vid.uid != 99) { return true; } // check publicaccess level int level = eos::common::Mapping::GetPublicAccessLevel(); if (level >= 1024) { // short cut return true; } eos::common::Path cPath(path); if ((int)cPath.GetSubPathSize() >= level) { // forbid everything to nobody in that case errno = EACCES; return false; } return true; } //------------------------------------------------------------------------------ // Get the allowed XrdAccPrivs i.e. allowed operations on the given path // for the client in the XrdSecEntity //------------------------------------------------------------------------------ XrdAccPrivs XrdMgmOfs::GetXrdAccPrivs(const std::string& path, const XrdSecEntity* client, XrdOucEnv* env) { std::string eos_path; eos::common::VirtualIdentity vid; auto basic_checks = [&, this]() -> int { const char* epname = "access"; char* ininfo = nullptr; XrdOucErrInfo error; const char* inpath = path.c_str(); const char* tident = client->tident; NAMESPACEMAP; BOUNCE_ILLEGAL_NAMES; AUTHORIZE(client, env, AOP_Stat, "access", inpath, error); EXEC_TIMING_BEGIN("IdMap"); eos::common::Mapping::IdMap(client, ininfo, tident, vid); EXEC_TIMING_END("IdMap"); gOFS->MgmStats.Add("IdMap", vid.uid, vid.gid, 1); BOUNCE_NOT_ALLOWED; ACCESSMODE_R; MAYSTALL; MAYREDIRECT; eos_path = path; return SFS_OK; }; if (basic_checks()) { eos_err("msg=\"failed basic checks for access privilege resolution\" " "path=\"%s\", user=\"%s\"", path.c_str(), (client->name ? client->name : "")); return XrdAccPriv_None; } return XrdAccPriv_All; }