/******************************************************************************/
/* */
/* X r d O f s T P C . c c */
/* */
/* (c) 2012 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 "XrdAcc/XrdAccAccess.hh"
#include "XrdNet/XrdNetAddr.hh"
#include "XrdOuc/XrdOucPList.hh"
#include "XrdOfs/XrdOfsSecurity.hh"
#include "XrdOfs/XrdOfsStats.hh"
#include "XrdOfs/XrdOfsTPC.hh"
#include "XrdOfs/XrdOfsTPCAuth.hh"
#include "XrdOfs/XrdOfsTPCJob.hh"
#include "XrdOfs/XrdOfsTPCProg.hh"
#include "XrdOfs/XrdOfsTrace.hh"
#include "XrdOss/XrdOss.hh"
#include "XrdOuc/XrdOucCallBack.hh"
#include "XrdOuc/XrdOucEnv.hh"
#include "XrdOuc/XrdOucProg.hh"
#include "XrdOuc/XrdOucNList.hh"
#include "XrdOuc/XrdOucTList.hh"
#include "XrdOuc/XrdOucTPC.hh"
#include "XrdOuc/XrdOucTrace.hh"
#include "XrdSec/XrdSecEntity.hh"
#include "XrdSys/XrdSysError.hh"
#include "XrdSys/XrdSysTimer.hh"
/******************************************************************************/
/* G l o b a l O b j e c t s */
/******************************************************************************/
extern XrdSysError OfsEroute;
extern XrdOfsStats OfsStats;
extern XrdOucTrace OfsTrace;
extern XrdOss *XrdOfsOss;
namespace XrdOfsTPCParms
{
static const int fcMax = 8;
struct fcTb {char *aVar;
char aProt[XrdSecPROTOIDSIZE];
bool aOpt;
bool aGSI;
} fcAuth[fcMax];
char *XfrProg = 0;
char *cksType = 0;
const char *gsiPKH = "-----BEGIN PRIVATE KEY-----\n";
int tcpSTRM = 0;
int tcpSMax = 15;
int LogOK = 0;
int xfrMax = 9;
int tpcOK = 0;
int encTPC = 0;
int errMon =-3;
int fcNum = 0;
bool doEcho = false;
bool autoRM = false;
bool noids = true;
}
using namespace XrdOfsTPCParms;
/******************************************************************************/
/* L o c a l C l a s s e s */
/******************************************************************************/
/******************************************************************************/
/* X r d O f s T P C A l l o w */
/******************************************************************************/
class XrdOfsTPCAllow
{
public:
XrdOfsTPCAllow *Next;
char *theDN;
char *theGN;
XrdOucNList *theHN;
char *theVO;
int Match(const XrdSecEntity *Who, const char *Host);
XrdOfsTPCAllow(char *vDN, char *vGN, char *vHN, char *vVO,
XrdOfsTPCAllow *Prev)
: Next(Prev), theDN(vDN), theGN(vGN), theVO(vVO)
{if (vHN) theHN = new XrdOucNList(vHN);
else theHN = 0;
}
~XrdOfsTPCAllow() {if (theHN) delete theHN;}
};
/******************************************************************************/
/* X r d O f s T P C A l l o w : : M a t c h */
/******************************************************************************/
int XrdOfsTPCAllow::Match(const XrdSecEntity *Who, const char *Host)
{
// Host name comparisons should be case insensitive. However, DN's and VO's
// do take case into account.
//
if (theHN && (!Host || !(theHN->NameKO(Host )))) return 0;
if (theDN && (!(Who->name) || strcmp(theDN, Who->name))) return 0;
if (theVO && (!(Who->vorg) || strcmp(theDN, Who->vorg))) return 0;
if (!theGN) return 1;
if (Who->grps)
{char gBuff[1028], Group[64];
strlcpy(gBuff+1, Who->grps, sizeof(gBuff)-1); *gBuff = ' ';
strlcpy(Group+1, theGN, sizeof(Group)-1); *Group = ' ';
return strstr(gBuff, Group) != 0;
} else return 0;
return 1;
}
/******************************************************************************/
/* S t a t i c V a r i a b l e s */
/******************************************************************************/
XrdOucTList *XrdOfsTPC::AuthDst = 0;
XrdOucTList *XrdOfsTPC::AuthOrg = 0;
XrdOucPListAnchor *XrdOfsTPC::RPList;
XrdOfsTPCAllow *XrdOfsTPC::ALList = 0;
XrdAccAuthorize *XrdOfsTPC::fsAuth = 0;
char *XrdOfsTPC::cPath = 0;
int XrdOfsTPC::maxTTL =15;
int XrdOfsTPC::dflTTL = 7;
/******************************************************************************/
/* A d d A u t h */
/******************************************************************************/
const char *XrdOfsTPC::AddAuth(const char *auth, const char *avar)
{
bool aOpt, aGSI;
// Check if credentials are optional
//
if (*auth != '?') aOpt = false;
else {aOpt = true;
auth++;
}
aGSI = strcmp("gsi", auth) == 0;
// Verify that the authname is not too long
//
if (strlen(auth) >= XrdSecPROTOIDSIZE) return "Invalid auth";
// Check if auth is already in the table
//
for (int i = 0; i < fcNum; i++)
if (!strcmp(auth, fcAuth[i].aProt))
{if (fcAuth[i].aVar) free(fcAuth[i].aVar);
fcAuth[i].aVar = strdup(avar);
fcAuth[i].aOpt = aOpt;
fcAuth[i].aGSI = aGSI;
return 0;
}
// Check if we have room to add an auth
//
if (fcNum >= fcMax) return "Too many fcred auths";
// Add an auth
//
strcpy(fcAuth[fcNum].aProt, auth);
fcAuth[fcNum].aVar = strdup(avar);
fcAuth[fcNum].aOpt = aOpt;
fcAuth[fcNum].aGSI = aGSI;
fcNum++;
return 0;
}
/******************************************************************************/
/* A l l o w */
/******************************************************************************/
void XrdOfsTPC::Allow(char *vDN, char *vGN, char *vHN, char *vVO)
{
// Add the entry
//
ALList = new XrdOfsTPCAllow(vDN, vGN, vHN, vVO, ALList);
}
/******************************************************************************/
/* A u t h o r i z e */
/******************************************************************************/
int XrdOfsTPC::Authorize(XrdOfsTPC **pTPC,
XrdOfsTPC::Facts &Args,
int isPLE)
{
XrdOfsTPCAuth *myTPC;
const char *dstHost;
int rc, NoGo = 0;
// Determine if we can handle any TPC requests
//
if (!tpcOK || !Args.Usr)
return Fatal(Args, "tpc not supported", ENOTSUP);
// If we are restricting paths, make sure this meets the restriction
//
if (RPList && !(RPList->Find(Args.Lfn)))
return Fatal(Args, "tpc not allowed for path", EACCES);
// The origin and the destination in the arguments
//
Args.Org = Args.Env->Get(XrdOucTPC::tpcOrg);
Args.Dst = Args.Env->Get(XrdOucTPC::tpcDst);
// Determine if this is the origin or the destination.
// Origin: dst and key required but org may not be specified
// Dest: org and key required but dst may not be specified
//
if (Args.Dst && !Args.Org)
{if (fsAuth && !fsAuth->Access(Args.Usr, Args.Lfn, AOP_Read, Args.Env))
return Fatal(Args, "permission denied", EACCES);
if (AuthOrg && !Screen(Args, AuthOrg, isPLE)) return SFS_ERROR;
if (!(myTPC = new XrdOfsTPCAuth(getTTL(Args.Env))))
return Fatal(Args, "insufficient memory", ENOMEM);
if (!(myTPC->Add(Args))) {delete myTPC; return SFS_ERROR;}
*pTPC = (XrdOfsTPC *)myTPC;
return SFS_OK;
}
else if (!Args.Org || Args.Dst)
return Fatal(Args, "conflicting tpc cgi", EINVAL);
// If we need to enforce authentication, do so now
//
if (AuthDst && !Screen(Args, AuthDst, isPLE)) return SFS_ERROR;
// Avoid nodnr manglement of the host name, we always will need one. If we have
// see if we should restrict the destinations and if so, do it.
//
if (!(dstHost = Args.Usr->addrInfo->Name())) NoGo = 1;
else if (ALList)
{XrdOfsTPCAllow *aP = ALList;
while(aP && !aP->Match(Args.Usr, dstHost)) aP = aP->Next;
if (!aP) NoGo = 1;
}
// Check if this destination is actually authorized
//
if (NoGo)
{OfsEroute.Emsg("TPC", Args.eRR->getErrUser(),
"denied tpc access to", Args.Lfn);
OfsStats.Add(OfsStats.Data.numTPCdeny);
return Fatal(Args, "dest not authorized for tpc" ,EACCES, 1);
}
// This is the destination trying to open a source file. We must make sure
// that the origin has authorized this action for this destination.
//
Args.Dst = dstHost;
if ((rc = XrdOfsTPCAuth::Get(Args, &myTPC))) return rc;
// Check if entry already expired
//
if (myTPC->Expired())
{myTPC->Expired(Args.Usr->tident);
myTPC->Del();
return Fatal(Args, "authorization expired", EACCES, 1);
}
// Log the grant if so wanted
//
if (LogOK)
{char Buff[1024];
snprintf(Buff, sizeof(Buff), "%s granted tpc access by %s to",
Args.Usr->tident, Args.Org);
Buff[sizeof(Buff)-1] = 0;
OfsEroute.Emsg("TPC", Buff, Args.Lfn);
}
// All done
//
OfsStats.Add(OfsStats.Data.numTPCgrant);
*pTPC = (XrdOfsTPC *)myTPC;
return SFS_OK;
}
/******************************************************************************/
/* Private: D e a t h */
/******************************************************************************/
int XrdOfsTPC::Death(XrdOfsTPC::Facts &Args, const char *eMsg, int eCode, int nomsg)
{
// If automatc removal is wanted, remove the file.
//
if (autoRM && Args.Pfn) XrdOfsOss->Unlink(Args.Lfn);
// Return error information
//
return Fatal(Args, eMsg, eCode, nomsg);
}
/******************************************************************************/
/* Private: F a t a l */
/******************************************************************************/
int XrdOfsTPC::Fatal(XrdOfsTPC::Facts &Args, const char *eMsg, int eCode, int nomsg)
{
char Buff[2048];
// Format the error message
//
snprintf(Buff, sizeof(Buff), "Unable to open %s; %s", Args.Lfn, eMsg);
// Print it out if debugging is enabled
//
#ifndef NODEBUG
if (!nomsg) OfsEroute.Emsg("TPC", Args.eRR->getErrUser(), Buff);
#endif
// Place the error message in the error object and return
//
Args.eRR->setErrInfo(eCode, Buff);
OfsStats.Add(OfsStats.Data.numTPCerrs);
return SFS_ERROR;
}
/******************************************************************************/
/* g e n O r g */
/******************************************************************************/
int XrdOfsTPC::genOrg(const XrdSecEntity *client, char *Buff, int Blen)
{
const char *Colon, *cOrg = client->tident;
char *Name;
int n;
// Extract out the login name and pid
//
if (!(Colon = index(cOrg, ':'))) return 0;
n = (Colon - cOrg);
// Expand out client's full name
//
if (!(Name = Verify("origin", client->host, Buff, Blen))) return 0;
// Make sure this all fits
//
if (((n + 1) + int(strlen(Name))) >= Blen)
{strncpy(Buff, "origin ID too long", Blen);
Buff[Blen-1] = 0;
free(Name);
return 0;
}
// Construct the origin information
//
strncpy(Buff, cOrg, n);
Buff += n; *Buff++ = '@';
strcpy(Buff, Name);
free(Name);
return 1;
}
/******************************************************************************/
/* Private: g e t T T L */
/******************************************************************************/
int XrdOfsTPC::getTTL(XrdOucEnv *Env)
{
const char *vTTL = Env->Get(XrdOucTPC::tpcTtl);
if (vTTL)
{char *ePtr;
int n;
n = strtol(vTTL, &ePtr, 10);
if (n < 0 || *ePtr) return dflTTL;
return (n > maxTTL ? maxTTL : n);
}
return dflTTL;
}
/******************************************************************************/
/* I n i t */
/******************************************************************************/
void XrdOfsTPC::Init(XrdOfsTPC::iParm &Parms)
{
std::string aStr;
// Set program if specified
//
if (Parms.Pgm)
{if (XfrProg) free(XfrProg);
XfrProg = Parms.Pgm;
Parms.Pgm = 0;
}
// Set checksum type if specified
//
if (Parms.Ckst)
{if (cksType) free(cksType);
cksType = Parms.Ckst;
}
// Create credential forwarding template, if cred path specified. It is
// gauranteed to end with a slash (it better be).
//
if (Parms.cpath && Parms.fCreds) cPath = strdup(Parms.cpath);
else cPath = 0;
// Check for streams option
//
if (Parms.Strm > 15) Parms.Strm = 15;
// Set all other static values
//
if (Parms.Dflttl > 0) dflTTL = Parms.Dflttl;
if (Parms.Maxttl > 0) maxTTL = Parms.Maxttl;
if (Parms.Logok >= 0) LogOK = Parms.Logok;
if (Parms.Strm > 0) tcpSTRM= Parms.Strm;
if (Parms.SMax > 0) tcpSMax= Parms.SMax;
if (Parms.Xmax > 0) xfrMax = Parms.Xmax;
if (Parms.Grab < 0) errMon = Parms.Grab;
if (Parms.xEcho >= 0) doEcho = Parms.xEcho != 0;
if (Parms.autoRM >= 0) autoRM = Parms.autoRM != 0;
noids = Parms.oidsOK == 0;
// Record all delegated auths
//
for (int i = 0; i < fcNum; i++)
{aStr += ' '; aStr += fcAuth[i].aProt;}
// Export the delegated auths
//
if (aStr.length())
XrdOucEnv::Export("XRDTPCDLG", strdup(aStr.c_str()+1));
}
/******************************************************************************/
/* R e q u i r e */
/******************************************************************************/
void XrdOfsTPC::Require(const char *Auth, int rType)
{
int n = strlen(Auth), doEnc = (Auth[n-1] == '+');
if (!rType || rType == reqDST)
{AuthDst = new XrdOucTList(Auth, doEnc, AuthDst);
if (doEnc) AuthDst->text[n-1] = 0;
}
if (!rType || rType == reqORG)
{AuthOrg = new XrdOucTList(Auth, doEnc, AuthOrg);
if (doEnc) AuthOrg->text[n-1] = 0;
}
encTPC |= doEnc;
}
/******************************************************************************/
/* R e s t r i c t */
/******************************************************************************/
int XrdOfsTPC::Restrict(const char *Path)
{
XrdOucPList *plp;
char pBuff[MAXPATHLEN];
int n = strlen(Path);
if (n >= MAXPATHLEN)
{OfsEroute.Emsg("Config", "tpc restrict path too long");
return 0;
}
if (Path[n-1] != '/')
{strcpy(pBuff, Path);
pBuff[n++] = '/'; pBuff[n] = 0;
Path = pBuff;
}
if (!RPList) RPList = new XrdOucPListAnchor;
if (!(plp = RPList->Match(pBuff)))
{plp = new XrdOucPList(pBuff);
RPList->Insert(plp);
}
return 1;
}
/******************************************************************************/
/* Private: S c r e e n */
/******************************************************************************/
int XrdOfsTPC::Screen(XrdOfsTPC::Facts &Args, XrdOucTList *tP, int wasEnc)
{
const char *aProt = Args.Usr->prot;
while(tP)
{if (!strcmp(tP->text, aProt))
{if (tP->val && wasEnc) return 1;
Fatal(Args, "unencrypted tpc disallowed", EACCES);
break;
}
tP = tP->next;
}
if (!tP) Fatal(Args, "improper tpc authentication", EACCES);
OfsStats.Add(OfsStats.Data.numTPCdeny);
return 0;
}
/******************************************************************************/
/* S t a r t */
/******************************************************************************/
int XrdOfsTPC::Start()
{
// If there is a path restriction list then setup it up
//
if (RPList) RPList->Default(1);
// If there is no copy program then we use the default one
//
if (!XfrProg) XfrProg = strdup("xrdcp --server");
// Allocate copy program objects
//
if (!XrdOfsTPCProg::Init()) return 0;
// Start the expiration thread
//
if (!XrdOfsTPCAuth::RunTTL(1)) return 0;
// All done
//
XrdOucEnv::Export("XRDTPC", (encTPC ? "+1" : "1"));
tpcOK = 1;
return 1;
}
/******************************************************************************/
/* V a l i d a t e */
/******************************************************************************/
int XrdOfsTPC::Validate(XrdOfsTPC **theTPC, XrdOfsTPC::Facts &Args)
{
XrdOfsTPCJob *myTPC;
const char *tpcLfn = Args.Env->Get(XrdOucTPC::tpcLfn);
const char *tpcSrc = Args.Env->Get(XrdOucTPC::tpcSrc);
const char *tpcCks = Args.Env->Get(XrdOucTPC::tpcCks);
const char *tpcStr = Args.Env->Get(XrdOucTPC::tpcStr);
const char *tpcSpr = Args.Env->Get(XrdOucTPC::tpcSpr);
const char *tpcTpr = Args.Env->Get(XrdOucTPC::tpcTpr);
const char *theCGI, *enVar = 0;
char Buff[512], myURL[4096], sVal = 0;
int n, doRN = 0, myURLen = sizeof(myURL);
short lfnLoc[2];
// Determine if we can handle any TPC requests
//
if (!tpcOK || !Args.Usr) return Death(Args, "tpc not supported", ENOTSUP);
// If we will be forwarding credentials, then verify that we have some
//
for (int i = 0; i < fcNum; i++)
{if (!strcmp(Args.Usr->prot, fcAuth[i].aProt))
{if (Args.Usr->creds == 0 || Args.Usr->credslen < 1
|| (fcAuth[i].aGSI && !strstr(Args.Usr->creds, gsiPKH)))
{if (!fcAuth[i].aOpt)
return Death(Args,"no delegated credentials for tpc",EACCES);
} else enVar = fcAuth[i].aVar;
const char *tpcDlg = Args.Env->Get(XrdOucTPC::tpcDlg);
if (tpcDlg) tpcSrc = tpcDlg;
break;
}
}
// This is a request by a writer to get data from another party. Make sure
// the source has been specified.
//
if (!tpcSrc) return Death(Args, "tpc source not specified", EINVAL);
if (!Args.Pfn) return Death(Args, "tpc pfn not specified", EINVAL);
// If the lfn, if present, it must be absolute.
//
if (!tpcLfn) tpcLfn = Args.Lfn;
else if (noids && *tpcLfn != '/')
return Death(Args,"source lfn not absolute",EINVAL);
else doRN = (strcmp(Args.Lfn, tpcLfn) != 0);
// Validate number of streams and adjust accordingly
//
if (tpcStr)
{char *eP;
long nStrm = strtol(tpcStr, &eP, 10);
if (nStrm < 0 || *eP)
return Death(Args, "tpc streams value is invalid", EINVAL);
if (nStrm > tcpSMax) nStrm = tcpSMax;
sVal = static_cast(nStrm);
} else sVal = static_cast(tcpSTRM);
// Generate the origin id
//
if (!genOrg(Args.Usr, Buff, sizeof(Buff))) return Death(Args, Buff, EINVAL);
// Construct the source url (it may be very big)
//
n = snprintf(myURL, myURLen, "xroot://%s/%s?", tpcSrc, tpcLfn);
if (n >= int(sizeof(myURL))) return Death(Args, "url too long", EINVAL);
// Set lfn location in the URL but only if we need to do a rename
//
if (doRN) {lfnLoc[1] = strlen(tpcLfn); lfnLoc[0] = n - lfnLoc[1];}
else lfnLoc[1] = lfnLoc[0] = 0;
theCGI = XrdOucTPC::cgiD2Src(Args.Key, Buff, myURL+n, myURLen-n);
if (*theCGI == '!') return Death(Args, theCGI+1, EINVAL);
// Create a pseudo tpc object that will contain the information we need to
// actually peform this copy.
//
if (!(myTPC = new XrdOfsTPCJob(myURL, Args.Usr->tident,
Args.Lfn, Args.Pfn, tpcCks, lfnLoc,
tpcSpr, tpcTpr)))
return Death(Args, "insufficient memory", ENOMEM);
// Set credentials for the job if we need to
//
if (enVar && Args.Usr->credslen > 0)
myTPC->Info.SetCreds(enVar, Args.Usr->creds, Args.Usr->credslen);
// Set number of streams to use
//
if (sVal > 1) myTPC->Info.SetStreams(sVal);
// All done
//
myTPC->Info.isDest();
*theTPC = (XrdOfsTPC *)myTPC;
return SFS_OK;
}
/******************************************************************************/
/* Private: V e r i f y */
/******************************************************************************/
char *XrdOfsTPC::Verify(const char *Who, const char *Name,
char *Buf, int Blen)
{
XrdNetAddr vAddr;
const char *etext, *Host;
// Obtain full host name and return it if successful
//
if (!(etext = vAddr.Set(Name,0)) && (Host = vAddr.Name(0, &etext)))
return strdup(Host);
// Generate error
//
snprintf(Buf, Blen, "unable to verify %s %s (%s)", Who, Name, etext);
Buf[Blen-1] = 0;
return 0;
}