/******************************************************************************/ /* */ /* X r d D i g C o n f i g . c c */ /* */ /* (C) 2013 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 Deprtment of Energy */ /* */ /* This file is part of the XRootD software suite. */ /* */ /* XRootD is free software: you can redistribute it and/or modify it under */ /* the terms of the GNU Lesser General Public License as published by the */ /* Free Software Foundation, either version 3 of the License, or (at your */ /* option) any later version. */ /* */ /* XRootD is distributed in the hope that it will be useful, but WITHOUT */ /* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ /* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ /* License for more details. */ /* */ /* You should have received a copy of the GNU Lesser General Public License */ /* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ /* COPYING (GPL license). If not, see . */ /* */ /* The copyright holder's institutional names and contributor's names may not */ /* be used to endorse or promote products derived from this software without */ /* specific prior written permission of the institution or contributor. */ /******************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include "XrdOuc/XrdOucEnv.hh" #include "XrdOuc/XrdOucErrInfo.hh" #include "XrdOuc/XrdOucStream.hh" #include "XrdOuc/XrdOucTokenizer.hh" #include "XrdOuc/XrdOucUtils.hh" #include "XrdNet/XrdNetAddr.hh" #include "XrdNet/XrdNetUtils.hh" #include "XrdSys/XrdSysError.hh" #include "XrdSys/XrdSysHeaders.hh" #include "XrdDig/XrdDigAuth.hh" #include "XrdDig/XrdDigConfig.hh" /******************************************************************************/ /* d e f i n e s */ /******************************************************************************/ #define TS_Xeq(x,m) if (!strcmp(x,var)) return m(cFile); /******************************************************************************/ /* S t a t i c G l o b a l O b j e c t s */ /******************************************************************************/ namespace XrdDig { extern XrdSysError *eDest; extern XrdDigAuth Auth; XrdDigConfig Config; }; using namespace XrdDig; namespace { struct pTV {const char *pfx; XrdDigAuthEnt::aType aType; const char pfxlen; char isOK; } pTab[] = {{"conf", XrdDigAuthEnt::aConf, 4, 0}, {"core", XrdDigAuthEnt::aCore, 4, 0}, {"logs", XrdDigAuthEnt::aLogs, 4, 0}, {"proc", XrdDigAuthEnt::aProc, 4, 0} }; static const int pNum = 4; struct stat rootStat; }; /******************************************************************************/ /* C o n f i g u r e */ /******************************************************************************/ bool XrdDigConfig::Configure(const char *cFN, const char *parms) { /* Function: Establish default values using configuration parameters. Input: None. Output: true upon success or false otherwise. */ char buff[4096], *afile, *var; XrdOucTokenizer cParms(buff); struct stat Stat; int n; bool isOK = true; // Get the adminpath (this better succeed). // if (!(var = getenv("XRDADMINPATH")) || (n = strlen(var)) >= MAXPATHLEN) {eDest->Emsg("Config", "Unable to deterine adminpath!"); return false; } // Create a template for file remapping // strcpy(buff, var); if (buff[n-1] != '/') {buff[n] = '/'; n++;} strcpy(buff+n, ".xrd/=/%s"); fnTmplt = strdup(buff); // Make sure that conf/etc no longer exists as a previous start may have // exported something that we no longer wish to export. // if (snprintf(buff, sizeof(buff), fnTmplt, "conf/etc") < (int)sizeof(buff)) Empty(buff); // Pake sure there are parameters here // if(!parms || !*parms) {eDest->Emsg("Config", "DigFS parameters not specified."); return false; } // Copy the parms as they will be altered and attach it to the tokenizer // n = strlen(parms); if (n >= (int)sizeof(buff)) {eDest->Emsg("Config", "DigFS parm string is too long."); return false; } strcpy(buff, parms); // First token is the authfile // cParms.GetLine(); if (!(afile = cParms.GetToken()) || !afile[0]) {eDest->Emsg("Config", "DigFS authfile not specified."); return false; } // If we have a config file, process it now // if (cFN && *cFN) isOK = ConfigProc(cFN); // Config authorization. The config may have failed but we want to generate // all of the rror messages in one go. // if (!Auth.Configure(afile)) isOK = false; // Setup locate response // SetLocResp(); // Get a valid stat structure for the root directory // stat("/", &rootStat); // Validate base entries // for (n = 0; n < pNum; n++) {sprintf(buff, fnTmplt, pTab[n].pfx); pTab[n].isOK = stat(buff, &Stat) == 0; } // All done // return isOK; } /******************************************************************************/ /* G e n A c c e s s */ /******************************************************************************/ int XrdDigConfig::GenAccess(const XrdSecEntity *client, const char *aList[], int aMax ) { bool aOK[XrdDigAuthEnt::aNum], hasAcc = false; int i, n = 0; // Validate aMax // if (aMax < 1) return -1; // Get access right for this client // Auth.Authorize(client, XrdDigAuthEnt::aNum, aOK); // Return entries that are allowed // for (i = (int)sizeof(aOK)-1; i >= 0 && n < aMax; i--) {hasAcc |= aOK[i]; if (aOK[i] && pTab[i].isOK) aList[n++] = pTab[i].pfx; } // Return permission denied if no access allowed // if (!hasAcc) return -1; // Return something if we had an error setting up as empty dirs cause problems. // if (!n) {aList[0] = "."; n = 1;} return n; } /******************************************************************************/ /* G e n P a t h */ /******************************************************************************/ char *XrdDigConfig::GenPath(int &rc, const XrdSecEntity *client, const char *opname, const char *fname, XrdDigConfig::pType lfnType) { char path[2048]; int i, n; // First we better have a client object // if (!client) {rc = EPERM; return 0;} // Translate the fname to the right file type // for (i = 0; i < pNum; i++) {if (!strncmp(pTab[i].pfx, fname, pTab[i].pfxlen) && (*(fname+pTab[i].pfxlen) == '/' || !*(fname+pTab[i].pfxlen))) break; } // Make sure we found a valid entry // if (i >= pNum || !pTab[i].isOK) {rc = ENOENT; return 0;} // Authorize this access // if (!Auth.Authorize(client, pTab[i].aType)) {if (lfnType == isFile && logRej) Audit(client, "denied", opname, fname); rc = EACCES; return 0; } // If the entry is being suffixed and it's proc, make sure we are not trying // to gain access to something outside of the proc directory tree // if (pTab[i].aType == XrdDigAuthEnt::aProc && (rc = ValProc(fname))) {if (logRej && rc == EPERM) Audit(client, "denied", opname, fname); return 0; } // Log this access if so wanted // if (lfnType == isFile && logAcc) Audit(client, "allowed", opname, fname); // Construct the name to be returned // i = (lfnType == isDir ? 1 : 0); n = snprintf(path, sizeof(path), fnTmplt, fname); if (n >= (int)sizeof(path)-1) {rc = ENAMETOOLONG; return 0;} // Attach a trailing slash if there is none if this is a directory // if (lfnType == isDir && path[n-1] != '/') {path[n] = '/'; path[n+1] = 0;} // Return the composite name // rc = 0; return strdup(path); } /******************************************************************************/ /* G e t L o c R e s p */ /******************************************************************************/ void XrdDigConfig::GetLocResp(XrdOucErrInfo &eInfo, bool nameok) { // Return desired value // if (nameok) eInfo.setErrInfo(locRlenHP, locRespHP); else if (eInfo.getUCap() & XrdOucEI::uIPv4) eInfo.setErrInfo(locRlenV4, locRespV4); else eInfo.setErrInfo(locRlenV6, locRespV6); } /******************************************************************************/ /* S t a t R o o t */ /******************************************************************************/ void XrdDigConfig::StatRoot(struct stat *sP) { memcpy(sP, &rootStat, sizeof(struct stat)); } /******************************************************************************/ /* p r i v a t e f u n c t i o n s */ /******************************************************************************/ /******************************************************************************/ /* Private: A d d P a t h */ /******************************************************************************/ const char *XrdDigConfig::AddPath(XrdDigConfig::pType sType, const char *src, const char *tpd, const char *tfn) { struct stat Stat; char *pP, tBuff[MAXPATHLEN], pBuff[MAXPATHLEN], nBuff[MAXNAMELEN]; int fd, rc; // Make sure the source path is absolute and is readable and is of proper type // if (*src != '/' || *(src+1) == 0) return "not absolute path"; if ((fd = open(src, O_RDONLY)) < 0) return strerror(errno); rc = (fstat(fd, &Stat) ? errno : 0); close(fd); if (rc) return strerror(rc); switch(sType) {case isFile: if (!S_ISREG(Stat.st_mode)) return "not a file"; break; case isDir: if (!S_ISDIR(Stat.st_mode)) return "not a directory"; break; default: break; } // If no target name specified it becomes the last components of src // if (!tfn) {const char *tbeg = (strncmp(src, "/etc/", 5) ? src+1 : src+5); tfn = src; while(strlen(tbeg) >= sizeof(nBuff)/2 && (tfn = index(tbeg,'/'))) tbeg = tfn + 1; if (!tfn) tfn = rindex(src, '/')+1; else {strcpy(nBuff, tbeg); tfn = pP = nBuff; while((pP = index(pP, '/'))) *pP++ = '.'; } } if (!(*tfn)) return "invalid derived target name"; // Construct the target path // if (snprintf(tBuff, sizeof(tBuff), "%s%s", tpd, tfn) > (int)sizeof(tBuff)) return "target name too long"; if (snprintf(pBuff, sizeof(pBuff), fnTmplt, tBuff) > (int)sizeof(pBuff)) return "target path too long"; // Create the link and return // if ((rc = XrdOucUtils::ReLink(pBuff, src))) return strerror(rc); return 0; } /******************************************************************************/ /* Private: A u d i t */ /******************************************************************************/ void XrdDigConfig::Audit(const XrdSecEntity *client, const char *what, const char *opn, const char *trg) { const char *name = (client->name ? client->name : "anon"); char hbuff[512], buff[1024]; // Get the hostname // client->addrInfo->Format(hbuff, sizeof(hbuff), XrdNetAddrInfo::fmtName, XrdNetAddrInfo::noPort); // Format the message and print it // snprintf(buff, sizeof(buff), "%s@%s %s", name, hbuff, what); eDest->Emsg(opn, client->tident, buff, trg); } /******************************************************************************/ /* C o n f i g P r o c */ /******************************************************************************/ bool XrdDigConfig::ConfigProc(const char *ConfigFN) { char *var; int cfgFD, retc, NoGo = 0; XrdOucEnv myEnv; XrdOucStream cFile(eDest, getenv("XRDINSTANCE"), &myEnv, "=====> "); // Try to open the configuration file. // if ( (cfgFD = open(ConfigFN, O_RDONLY, 0)) < 0) {eDest->Emsg("Config", errno, "open config file", ConfigFN); return 1; } cFile.Attach(cfgFD); // Now start reading records until eof. // while((var = cFile.GetMyFirstWord())) {if (!strncmp(var, "dig.", 4)) if (!ConfigXeq(var+4, cFile)) {cFile.Echo(); NoGo = 1;} } // Now check if any errors occured during file i/o // if ((retc = cFile.LastError())) NoGo = eDest->Emsg("Config", retc, "read config file", ConfigFN); cFile.Close(); // Return final return code // return !NoGo; } /******************************************************************************/ /* Private: C o n f i g X e q */ /******************************************************************************/ bool XrdDigConfig::ConfigXeq(char *var, XrdOucStream &cFile) { // Process items. for either a local or a remote configuration // TS_Xeq("addconf", xacf); TS_Xeq("log", xlog); return true; } /******************************************************************************/ /* Private: E m p t y */ /******************************************************************************/ void XrdDigConfig::Empty(const char *path) { DIR *dh; struct dirent *dP; char pBuff[MAXPATHLEN+8], *pB; int n, bLeft; // Copy the path. We will need it for deletions. This should never fail. // if ((n = snprintf(pBuff,sizeof(pBuff),"%s/",path)) >= (int)sizeof(pBuff)) return; bLeft = sizeof(pBuff) - n - 1; pB = pBuff + n; // Open the directory // if (!(dh = opendir(path))) return; // Delete each entry in this directory (no need to be thread safe here) // while((dP = readdir(dh))) {if (bLeft > (int)strlen(dP->d_name)) {strcpy(pB, dP->d_name); unlink(pBuff); } } // Now remove the actual directory // rmdir(path); } /******************************************************************************/ /* Private: S e t L o c R e s p */ /******************************************************************************/ void XrdDigConfig::SetLocResp() { static const int fmtopts = XrdNetAddr::old6Map4; XrdNetAddr myAddr(0); char *pP, buff[512], *bp = buff+2; int myPort, bsz = sizeof(buff)-2; // Obtain port number we will be using. Note that the constructor must occur // after the port number is known (i.e., this cannot be a global static). // myPort = (pP = getenv("XRDPORT")) ? strtol(pP, (char **)NULL, 10) : 0; strcpy(buff, "Sr"); // Establish hostname locate response // myAddr.Port(myPort); myAddr.Format(bp, bsz, XrdNetAddr::fmtName); locRespHP = strdup(buff); locRlenHP = strlen(buff)+1; // Extablish IPV6 locate response // myAddr.Format(bp, bsz, XrdNetAddr::fmtAdv6, fmtopts); locRespV6 = locRespV4 = strdup(buff); locRlenV6 = locRlenV4 = strlen(buff)+1; // If we are truly IPv6 then see if we also have an IPv4 address // if (myAddr.isIPType(XrdNetAddrInfo::IPv6) && !myAddr.isMapped()) {XrdNetAddr *iP; int iN; if (!XrdNetUtils::GetAddrs(myAddr.Name(""), &iP, iN, XrdNetUtils::onlyIPv4, 0) && iN) {iP[0].Port(myPort); iP[0].Format(bp, bsz, XrdNetAddr::fmtAdv6, fmtopts); locRespV4 = strdup(buff); locRlenV4 = strlen(buff)+1; delete [] iP; } } } /******************************************************************************/ /* V a l P r o c */ /******************************************************************************/ int XrdDigConfig::ValProc(const char *path) { struct stat Stat; char *Slash, cpath[1040], ppath[1040]; int n; // Copy the path so we can modify it and make sure it ends with a slash // n = snprintf(ppath, sizeof(ppath), "%s", path); if (n >= (int)sizeof(ppath)-2) return ENAMETOOLONG; if (ppath[n-1] != '/') {ppath[n] = '/'; ppath[n+1] = 0;} // We accept proc/x/y where y and any other path suffix is not a symlink // if (!(Slash = index(ppath, '/')) || !(Slash = index(Slash+1, '/')) || !(Slash = index(Slash+1, '/'))) return 0; // Now check each component // while(Slash) {*Slash = 0; n = snprintf(cpath, sizeof(cpath), fnTmplt, ppath); if (n >= (int)sizeof(cpath)) return ENAMETOOLONG; if (lstat(cpath, &Stat)) return errno; if (!S_ISDIR(Stat.st_mode) && !S_ISREG(Stat.st_mode)) return EPERM; *Slash = '/'; Slash = index(Slash+1, '/'); } // It's OK to use proc // return 0; } /******************************************************************************/ /* Private: x a c f */ /******************************************************************************/ /* Function: xacf Purpose: Parse the directive: addconf [] path to a configuration file the file name it is to have in "/=/conf/etc/" Output: true upon success or false upon failure. */ bool XrdDigConfig::xacf(XrdOucStream &cFile) { const char *eTxt; char *val, src[MAXPATHLEN+8]; // Check out first token // if (!(val = cFile.GetWord()) || !val[0]) {eDest->Emsg("Config", "addconf path not specified."); return false;} // Copy the path // if (strlen(val) >= sizeof(src)) {eDest->Emsg("Config", "addconf path is too long."); return false;} strcpy(src, val); // Check if we have a special filename here // if (!(val = cFile.GetWord()) || !val[0]) val = 0; else {if (index(val, '/')) {eDest->Emsg("Config", "invalid addconf fname -", val); return false; } } // Now add the file path // if ((eTxt = AddPath(isFile, src, "conf/etc/", val))) {char eBuff[256]; snprintf(eBuff, sizeof(eBuff), "- %s", eTxt); eDest->Emsg("Config", "Unable to addconf" , src, eBuff); return false; } // All done // return true; } /******************************************************************************/ /* Private: x l o g */ /******************************************************************************/ /* Function: xlog Purpose: Parse the directive: log [grant] [deny] | none grant log successful access to information deny log unsuccessful access to information none do not log anything Output: true upon success or false upon failure. */ bool XrdDigConfig::xlog(XrdOucStream &cFile) { char *val; // Check out first token // if (!(val = cFile.GetWord()) || !val[0]) {eDest->Emsg("Config", "log parameter not specified"); return false;} // Check for appropriate words // logAcc = logRej = false; do { if (!strcmp("grant", val)) logAcc = true; else if (!strcmp("deny", val)) logRej = true; else if (!strcmp("none", val)) logRej = logAcc = false; else {eDest->Emsg("Config","invalid log option -",val); return false;} val = cFile.GetWord(); } while(val && *val); // All done // return true; }