/******************************************************************************/
/* */
/* X r d C m s B l a c k L i s t . c c */
/* */
/* (c) 2014 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 "Xrd/XrdScheduler.hh"
#include "XrdCms/XrdCmsBlackList.hh"
#include "XrdCms/XrdCmsCluster.hh"
#include "XrdCms/XrdCmsTrace.hh"
#include "XrdCms/XrdCmsUtils.hh"
#include "XrdNet/XrdNetAddr.hh"
#include "XrdOuc/XrdOucEnv.hh"
#include "XrdOuc/XrdOucTList.hh"
#include "XrdOuc/XrdOucStream.hh"
#include "XrdOuc/XrdOucTokenizer.hh"
#include "XrdSys/XrdSysError.hh"
#include "XrdSys/XrdSysLogger.hh"
#include "XrdSys/XrdSysPthread.hh"
/******************************************************************************/
/* L o c a l C l a s s e s */
/******************************************************************************/
class BL_Grip
{public:
void Add(XrdOucTList *tP)
{if (last) last->next = tP;
else first = tP;
last = tP;
}
XrdOucTList **Array(int &anum)
{XrdOucTList *tP = first;
anum = Count();
if (!anum) return 0;
XrdOucTList **vec = new XrdOucTList *[anum];
for (int i = 0; i < anum; i++) {vec[i] = tP; tP = tP->next;}
first = last = 0;
return vec;
}
int Count()
{XrdOucTList *tP = first;
int n = 0;
while(tP) {tP=tP->next; n++;}
return n;
}
XrdOucTList *Export()
{XrdOucTList *tP = first;
first = last = 0;
return tP;
}
bool Include(const char *item, int &i)
{XrdOucTList *tP = first;
i = 0;
while(tP && strcmp(item,tP->text)) {tP=tP->next; i++;}
if (!tP) {Add(new XrdOucTList(item)); return false;}
return true;
}
BL_Grip() : first(0), last(0) {}
~BL_Grip() {XrdOucTList *tP;
while((tP = first)) {first = tP->next; delete tP;}
last = 0;
}
private:
XrdOucTList *first;
XrdOucTList *last;
};
union BL_Info
{long long info;
struct {short flags;
short pfxLen;
short sfxLen;
short totLen;
} v;
enum {exact = 0x8000,
redir = 0x4000,
rmask = 0x00ff
};
};
/******************************************************************************/
/* G l o b a l O b j e c t s */
/******************************************************************************/
namespace XrdCms
{
XrdScheduler *blSched = 0;
XrdCmsCluster *blCluster;
class MidNightTask : public XrdSysLogger::Task
{public:
void Ring();
MidNightTask() {}
~MidNightTask() {}
};
MidNightTask blMN;
XrdCmsBlackList BlackList;
XrdSysMutex blMutex;
XrdOucTList *blReal = 0;
XrdOucTList **blRedr = 0;
char *blFN;
time_t blTime = 0;
int blChk;
int blRcnt = 0;
bool isWList=false;
extern XrdSysError Say;
};
using namespace XrdCms;
/******************************************************************************/
/* Private: A d d B L */
/******************************************************************************/
bool XrdCmsBlackList::AddBL(BL_Grip &bAnchor, char *hSpec,
BL_Grip *rAnchor, char *rSpec)
{
const char *bwTag = (isWList ? "whitelist '" : "blacklist '");
XrdNetAddr blAddr;
XrdOucTList *bP;
BL_Info Hdr;
const char *eText;
char *Ast, blBuff[512];
// Initialize the header
//
Hdr.info = 0;
// Check if we are processing a redirect
//
if (rSpec)
{int i = AddRD(rAnchor, rSpec, hSpec);
if (i < 0) return false;
Hdr.v.flags |= BL_Info::redir | i;
}
// Get the full name if this is not generic
//
if (!(Ast = index(hSpec, '*')))
{if ((eText = blAddr.Set(hSpec,0)))
{snprintf(blBuff, sizeof(blBuff), "'; %s", eText);
Say.Say("Config ", "Unable to ", bwTag, hSpec, blBuff);
return false;
}
blAddr.Format(blBuff, sizeof(blBuff), XrdNetAddrInfo::fmtName,
XrdNetAddrInfo::noPort);
hSpec = blBuff;
Hdr.v.flags |= BL_Info::exact;
} else {
Hdr.v.pfxLen = Ast - hSpec;
Hdr.v.sfxLen = strlen(hSpec + Hdr.v.pfxLen + 1);
Hdr.v.totLen = Hdr.v.pfxLen + Hdr.v.sfxLen;
}
// Add specification to the list
//
bP = new XrdOucTList(hSpec, &Hdr.info);
bAnchor.Add(bP);
return true;
}
/******************************************************************************/
/* A d d R D */
/******************************************************************************/
int XrdCmsBlackList::AddRD(BL_Grip *rAnchor, char *rSpec, char *hSpec)
{
XrdOucTList *rP, *rList = 0;
char *rTarg;
int ival;
bool aOK = true;
// First see if we have this entry already
//
if (rAnchor[0].Include(rSpec, ival)) return ival;
// Make sure we did not exceed the maximum number of redirect entries
//
if (ival > BL_Info::rmask)
{Say.Say("Config ", "Too many different redirects at ", hSpec,
"redirect", rSpec);
return -1;
}
// We now ned to tokenize the specification
//
XrdOucTokenizer rToks(rSpec);
rToks.GetLine();
// Process each item
//
while((rTarg = rToks.GetToken()) && *rTarg) aOK &= AddRD(&rList,rTarg,hSpec);
if (!aOK) return -1;
// Flatten the list and add it to out list of redirect targets
//
rP = Flatten(rList, rList->val);
rAnchor[1].Add(rP);
// Delete the rlist
//
while((rP = rList)) {rList = rList->next; delete rP;}
// All done
//
return ival;
}
/******************************************************************************/
bool XrdCmsBlackList::AddRD(XrdOucTList **rList, char *rSpec, char *hSpec)
{
char *rPort;
// Screen out IPV6 specifications
//
if (*rSpec == '[')
{if (!(rPort = index(rSpec, ']')))
{Say.Say("Config ","Invalid ",hSpec," redirect specification - ",rSpec);
return -1;
}
} else rPort = rSpec;
// Grab the port number
//
if ((rPort = index(rPort, ':')))
{if (!(*(rPort+1))) rPort = 0;
else *rPort++ = '\0';
}
// We should have a port specification now
//
if (!rPort) {Say.Say("Config ", "redirect port not specified for ", hSpec);
return -1;
}
// Convert this to a list of redirect targets
//
return XrdCmsUtils::ParseMan(&Say, rList, rSpec, rPort, 0, true);
}
/******************************************************************************/
/* D o I t */
/******************************************************************************/
void XrdCmsBlackList::DoIt()
{
struct stat Stat;
XrdOucTList **blOldRedr = 0, **blNewRedr = 0, *blNewReal = 0, *tP = 0, *nP;
int rc, blOldRcnt = 0, blNewRcnt;
bool doUpdt = false, doPrt = false;
// Check if the black list file was modified
//
rc = stat(blFN, &Stat);
if ((!rc && blTime != Stat.st_mtime) || (rc && blTime && errno == ENOENT))
{blTime = (rc ? 0 : Stat.st_mtime);
if (GetBL(blNewReal, blNewRedr, blNewRcnt))
{blMutex.Lock();
tP = blReal; blReal = blNewReal;
blOldRedr = blRedr; blRedr = blNewRedr;
blOldRcnt = blRcnt; blRcnt = blNewRcnt;
blMutex.UnLock();
if (!blReal && tP) doPrt = !isWList;
else blMN.Ring();
doUpdt = true;
}
}
// Delete any list we need to release
//
while(tP)
{if (doPrt) Say.Say("Config ", tP->text, " removed from blacklist.");
nP = tP->next; delete tP; tP = nP;
}
// Delete the old redirect array
//
if (blOldRedr)
{for (int i = 0; i < blOldRcnt; i++) delete blOldRedr[i];
delete [] blOldRedr;
}
// Do real-time update if need be
//
if (doUpdt) blCluster->BlackList(blReal);
// Reschedule this to check any modifications
//
blSched->Schedule((XrdJob *)&BlackList, time(0) + blChk);
}
/******************************************************************************/
/* Private: F l a t t e n */
/******************************************************************************/
XrdOucTList *XrdCmsBlackList::Flatten(XrdOucTList *tList, int tPort)
{
XrdOucTList *tP = tList;
char buff[4096], bPort[8], *bP = buff;
int n, pLen, bleft = sizeof(buff);
short xdata[4] = {0};
// Convert port to a suffix
//
pLen = sprintf(bPort, ":%d", tPort);
*buff = 0;
// Fill the buffer and truncate as necessary
//
while(tP)
{n = strlen(tP->text)+pLen+2;
if (n >= bleft) break;
n = sprintf(bP, " %s%s", tP->text, bPort);
bP += n; bleft -= n;
tP = tP->next;
}
// Get actual length including null byte
//
xdata[0] = strlen(buff+1) + 1;
xdata[1] = xdata[0] + sizeof(short);
xdata[2] = htons(xdata[0]);
// Create a new tlist item
//
tP = new XrdOucTList(buff+1, xdata);
return tP;
}
/******************************************************************************/
/* Private: G e t B L */
/******************************************************************************/
bool XrdCmsBlackList::GetBL(XrdOucTList *&bList,
XrdOucTList **&rList, int &rcnt)
{
static int msgCnt = 0;
XrdOucEnv myEnv;
XrdOucStream blFile(&Say, getenv("XRDINSTANCE"), &myEnv, "=====> ");
BL_Grip bAnchor, rAnchor[2];
const char *fType, *oEmsg, *rEmsg;
char *hsp, *rsp, hspBuff[512], rSpec[4096];
int blFD, retc;
bool aOK = true;
// Setup message plugins
//
if (isWList)
{oEmsg = "open whitelist file";
rEmsg = "read whitelist file";
fType = "whitelist";
} else {
oEmsg = "open blacklist file";
rEmsg = "read blacklist file";
fType = "blacklist";
}
// Open the config file
//
if ( (blFD = open(blFN, O_RDONLY, 0)) < 0)
{if (errno == ENOENT) return true;
if (!(msgCnt & 0x03)) Say.Emsg("GetBL", errno, oEmsg, blFN);
return false;
}
blFile.Attach(blFD, 4096);
// Trace this now
//
Say.Say("Config processing ", fType, " file ", blFN);
// Start reading the black list
//
while((hsp = blFile.GetMyFirstWord()))
{if (strlen(hsp) >= sizeof(hspBuff))
{Say.Say("Config ", hsp, " is too long."); aOK = false; continue;}
strcpy(hspBuff, hsp); hsp = hspBuff;
if ( (rsp = blFile.GetWord()) && *rsp)
{if (strcmp("redirect", rsp))
{Say.Say("Config ", rsp, " is an invalid modifier for ", hsp);
aOK = false;
continue;
}
*rSpec = 0; rsp = rSpec;
if (!blFile.GetRest(rSpec, sizeof(rSpec)))
{Say.Say("Config ", "redirect target too long ", hsp);
aOK = false;
continue;
}
if (!(*rSpec))
{Say.Say("Config ", "redirect target missing for ", hsp);
aOK = false;
continue;
}
} else rsp = 0;
blFile.noEcho();
if (!AddBL(bAnchor, hsp, rAnchor, rsp)) aOK = false;
}
// Now check if any errors occured during file i/o
//
if ((retc = blFile.LastError()))
{Say.Emsg("GetBL", retc, rEmsg, blFN); aOK = false;}
else if (!aOK) Say.Emsg("GetBL", "Error(s) encountered in",fType,"file!");
// Return ending status
//
blFile.Close();
bList = (aOK ? bAnchor.Export() : 0);
rList = rAnchor[1].Array(rcnt);
return aOK;
}
/******************************************************************************/
/* I n i t */
/******************************************************************************/
void XrdCmsBlackList::Init(XrdScheduler *sP, XrdCmsCluster *cP,
const char *blfn, int chkt)
{
struct stat Stat;
const char *cfn;
// Copy out the scheduler and cluster pointers
//
blSched = sP;
blCluster = cP;
// Determine if this is a black or white list
//
if (chkt < 0) {isWList = true; chkt = -chkt;}
// Copy the file path (this is a one time call during config)
//
if (blfn) blFN = strdup(blfn);
else if (!(cfn = getenv("XRDCONFIGFN"))) return;
else {char pBuff[2048], *Slash;
strcpy(pBuff, cfn);
if (!(Slash = rindex(pBuff, '/'))) return;
strcpy(Slash+1, (isWList ? "cms.whitelist" : "cms.blacklist"));
blFN = strdup(pBuff);
}
// Check if the black list file exists, it might not. If it does, process it
//
if (!stat(blFN, &Stat))
{blTime = Stat.st_mtime;
GetBL(blReal, blRedr, blRcnt);
if (blReal) blMN.Ring();
}
// Schedule this to recheck any modifications
//
blChk = chkt;
blSched->Schedule((XrdJob *)&BlackList, time(0) + chkt);
// Add ourselves to the midnight run list
//
Say.logger()->AtMidnight(&blMN);
}
/******************************************************************************/
/* P r e s e n t */
/******************************************************************************/
int XrdCmsBlackList::Present(const char *hName, XrdOucTList *bList,
char *rBuff, int rBLen)
{
BL_Info Hdr;
int hLen, retval;
bool doUnLk;
// Check if we really have a name here
//
if (!hName || !blSched) return 0;
// Check if we need to supply our list
//
if (bList) doUnLk = false;
else {doUnLk = true;
blMutex.Lock();
bList = blReal;
}
// By definition, if there is no list at all then everybody is allowed
//
if (!bList)
{if (doUnLk) blMutex.UnLock();
return 0;
}
// Run through the list and try to compare
//
hLen = strlen(hName);
while(bList)
{Hdr.info = bList->dval;
if (Hdr.v.flags & BL_Info::exact)
{if (!strcmp(hName, bList->text)) break;}
else if (hLen >= Hdr.v.totLen)
{if (!Hdr.v.pfxLen
|| !strncmp(bList->text, hName, Hdr.v.pfxLen))
{if (!Hdr.v.sfxLen
|| !strncmp(bList->text+Hdr.v.pfxLen+1,
hName + (hLen - Hdr.v.sfxLen),
Hdr.v.sfxLen)) break;
}
}
bList = bList->next;
}
// If we have a black list check if we should redirect
//
if (bList)
{if (!(Hdr.v.flags & BL_Info::redir)) retval = (isWList ? 0 : -1);
else {XrdOucTList *rP = blRedr[Hdr.v.flags & BL_Info::rmask];
if (rP)
{retval = rP->sval[1];
if (!rBuff || retval > rBLen) retval = -retval;
else {memcpy(rBuff, &(rP->sval[2]), sizeof(short));
memcpy(rBuff+sizeof(short), rP->text, rP->sval[0]);
}
} else retval = -1;
}
} else retval = (isWList ? -1 : 0);
// Unlock ourselves if need be and return result
//
if (doUnLk) blMutex.UnLock();
return retval;
}
/******************************************************************************/
/* R i n g */
/******************************************************************************/
void MidNightTask::Ring()
{
BL_Info Hdr;
XrdOucTList *tP;
const char *bwTag = (isWList ? "Whitelisting " : "Blacklisting ");
// Get the list lock
//
blMutex.Lock();
tP = blReal;
// Print the list
//
while(tP)
{Hdr.info = tP->dval;
if (!(Hdr.v.flags & BL_Info::redir))
Say.Say("Config ", bwTag, tP->text);
else {XrdOucTList *rP = blRedr[Hdr.v.flags & BL_Info::rmask];
Say.Say("Config Blacklisting ",tP->text," redirect ",rP->text);
}
tP = tP->next;
}
// All done
//
blMutex.UnLock();
}