XRootD
XrdMacaroonsAuthz.cc
Go to the documentation of this file.
1 #include "XrdMacaroonsAuthz.hh"
2 #include "XrdMacaroonsHandler.hh"
3 
4 #include "XrdOuc/XrdOucEnv.hh"
6 #include "XrdSec/XrdSecEntity.hh"
8 
9 #include <ctime>
10 #include <sstream>
11 #include <stdexcept>
12 
13 #include <macaroons.h>
14 
15 using namespace Macaroons;
16 
17 namespace {
18 
19 class AuthzCheck
20 {
21 public:
22  AuthzCheck(const char *req_path, const Access_Operation req_oper, ssize_t max_duration, XrdSysError &log);
23 
24  const std::string &GetSecName() const {return m_sec_name;}
25  const std::string &GetErrorMessage() const {return m_emsg;}
26 
27  static int verify_before_s(void *authz_ptr,
28  const unsigned char *pred,
29  size_t pred_sz);
30 
31  static int verify_activity_s(void *authz_ptr,
32  const unsigned char *pred,
33  size_t pred_sz);
34 
35  static int verify_path_s(void *authz_ptr,
36  const unsigned char *pred,
37  size_t pred_sz);
38 
39  static int verify_name_s(void *authz_ptr,
40  const unsigned char *pred,
41  size_t pred_sz);
42 
43 private:
44  int verify_before(const unsigned char *pred, size_t pred_sz);
45  int verify_activity(const unsigned char *pred, size_t pred_sz);
46  int verify_path(const unsigned char *pred, size_t pred_sz);
47  int verify_name(const unsigned char *pred, size_t pred_sz);
48 
49  ssize_t m_max_duration;
50  XrdSysError &m_log;
51  std::string m_emsg;
52  const std::string m_path;
53  std::string m_desired_activity;
54  std::string m_sec_name;
55  Access_Operation m_oper;
56  time_t m_now;
57 };
58 
59 static XrdAccPrivs AddPriv(Access_Operation op, XrdAccPrivs privs)
60 {
61  int new_privs = privs;
62  switch (op) {
63  case AOP_Any:
64  break;
65  case AOP_Chmod:
66  new_privs |= static_cast<int>(XrdAccPriv_Chmod);
67  break;
68  case AOP_Chown:
69  new_privs |= static_cast<int>(XrdAccPriv_Chown);
70  break;
71  case AOP_Excl_Create: // fallthrough
72  case AOP_Create:
73  new_privs |= static_cast<int>(XrdAccPriv_Create);
74  break;
75  case AOP_Delete:
76  new_privs |= static_cast<int>(XrdAccPriv_Delete);
77  break;
78  case AOP_Excl_Insert: // fallthrough
79  case AOP_Insert:
80  new_privs |= static_cast<int>(XrdAccPriv_Insert);
81  break;
82  case AOP_Lock:
83  new_privs |= static_cast<int>(XrdAccPriv_Lock);
84  break;
85  case AOP_Mkdir:
86  new_privs |= static_cast<int>(XrdAccPriv_Mkdir);
87  break;
88  case AOP_Read:
89  new_privs |= static_cast<int>(XrdAccPriv_Read);
90  break;
91  case AOP_Readdir:
92  new_privs |= static_cast<int>(XrdAccPriv_Readdir);
93  break;
94  case AOP_Rename:
95  new_privs |= static_cast<int>(XrdAccPriv_Rename);
96  break;
97  case AOP_Stat:
98  new_privs |= static_cast<int>(XrdAccPriv_Lookup);
99  break;
100  case AOP_Update:
101  new_privs |= static_cast<int>(XrdAccPriv_Update);
102  break;
103  };
104  return static_cast<XrdAccPrivs>(new_privs);
105 }
106 
107 // Accept any value of the path, name, or activity caveats
108 int validate_verify_empty(void *emsg_ptr,
109  const unsigned char *pred,
110  size_t pred_sz)
111 {
112  if ((pred_sz >= 5) && (!memcmp(reinterpret_cast<const char *>(pred), "path:", 5) ||
113  !memcmp(reinterpret_cast<const char *>(pred), "name:", 5)))
114  {
115  return 0;
116  }
117  if ((pred_sz >= 9) && (!memcmp(reinterpret_cast<const char *>(pred), "activity:", 9)))
118  {
119  return 0;
120  }
121  return 1;
122 }
123 
124 } // unnamed namespace
125 
126 Authz::Authz(XrdSysLogger *log, char const *config, XrdAccAuthorize *chain)
127  : m_max_duration(86400),
128  m_chain(chain),
129  m_log(log, "macarons_"),
130  m_authz_behavior(static_cast<int>(Handler::AuthzBehavior::PASSTHROUGH))
131 {
132  Handler::AuthzBehavior behavior(Handler::AuthzBehavior::PASSTHROUGH);
133  XrdOucEnv env;
134  if (!Handler::Config(config, &env, &m_log, m_location, m_secret, m_max_duration, behavior))
135  {
136  throw std::runtime_error("Macaroon authorization config failed.");
137  }
138  m_authz_behavior = static_cast<int>(behavior);
139 }
140 
142 Authz::OnMissing(const XrdSecEntity *Entity, const char *path,
143  const Access_Operation oper, XrdOucEnv *env)
144 {
145  switch (m_authz_behavior) {
146  case Handler::AuthzBehavior::PASSTHROUGH:
147  return m_chain ? m_chain->Access(Entity, path, oper, env) : XrdAccPriv_None;
148  case Handler::AuthzBehavior::ALLOW:
149  return AddPriv(oper, XrdAccPriv_None);;
150  case Handler::AuthzBehavior::DENY:
151  return XrdAccPriv_None;
152  }
153  // Code should be unreachable.
154  return XrdAccPriv_None;
155 }
156 
158 Authz::Access(const XrdSecEntity *Entity, const char *path,
159  const Access_Operation oper, XrdOucEnv *env)
160 {
161  // We don't allow any testing to occur in this authz module, preventing
162  // a macaroon to be used to receive further macaroons.
163  if (oper == AOP_Any)
164  {
165  return m_chain ? m_chain->Access(Entity, path, oper, env) : XrdAccPriv_None;
166  }
167 
168  const char *authz = env ? env->Get("authz") : nullptr;
169  if (authz && !strncmp(authz, "Bearer%20", 9))
170  {
171  authz += 9;
172  }
173  else if (!authz && (authz = env ? env->Get("access_token") : nullptr) && !strncmp(authz, "Bearer%20", 9))
174  {
175  authz += 9;
176  }
177 
178  // If there's no request-specific token, check for a ZTN session token
179  if (!authz && Entity && !strcmp("ztn", Entity->prot) && Entity->creds &&
180  Entity->credslen && Entity->creds[Entity->credslen] == '\0')
181  {
182  authz = Entity->creds;
183  }
184 
185  if (!authz) {
186  return OnMissing(Entity, path, oper, env);
187  }
188 
189  macaroon_returncode mac_err = MACAROON_SUCCESS;
190  struct macaroon* macaroon = macaroon_deserialize(
191  authz,
192  &mac_err);
193  if (!macaroon)
194  {
195  // Do not log - might be other token type!
196  //m_log.Emsg("Access", "Failed to parse the macaroon");
197  return OnMissing(Entity, path, oper, env);
198  }
199 
200  struct macaroon_verifier *verifier = macaroon_verifier_create();
201  if (!verifier)
202  {
203  m_log.Emsg("Access", "Failed to create a new macaroon verifier");
204  return XrdAccPriv_None;
205  }
206  if (!path)
207  {
208  m_log.Emsg("Access", "Request with no provided path.");
209  macaroon_verifier_destroy(verifier);
210  return XrdAccPriv_None;
211  }
212 
213  AuthzCheck check_helper(path, oper, m_max_duration, m_log);
214 
215  if (macaroon_verifier_satisfy_general(verifier, AuthzCheck::verify_before_s, &check_helper, &mac_err) ||
216  macaroon_verifier_satisfy_general(verifier, AuthzCheck::verify_activity_s, &check_helper, &mac_err) ||
217  macaroon_verifier_satisfy_general(verifier, AuthzCheck::verify_name_s, &check_helper, &mac_err) ||
218  macaroon_verifier_satisfy_general(verifier, AuthzCheck::verify_path_s, &check_helper, &mac_err))
219  {
220  m_log.Emsg("Access", "Failed to configure caveat verifier:");
221  macaroon_verifier_destroy(verifier);
222  return XrdAccPriv_None;
223  }
224 
225  const unsigned char *macaroon_loc;
226  size_t location_sz;
227  macaroon_location(macaroon, &macaroon_loc, &location_sz);
228  if (strncmp(reinterpret_cast<const char *>(macaroon_loc), m_location.c_str(), location_sz))
229  {
230  std::string location_str(reinterpret_cast<const char *>(macaroon_loc), location_sz);
231  m_log.Emsg("Access", "Macaroon is for incorrect location", location_str.c_str());
232  macaroon_verifier_destroy(verifier);
233  macaroon_destroy(macaroon);
234  return m_chain ? m_chain->Access(Entity, path, oper, env) : XrdAccPriv_None;
235  }
236 
237  if (macaroon_verify(verifier, macaroon,
238  reinterpret_cast<const unsigned char *>(m_secret.c_str()),
239  m_secret.size(),
240  nullptr, 0, // discharge macaroons
241  &mac_err))
242  {
243  m_log.Log(LogMask::Debug, "Access", "Macaroon verification failed");
244  macaroon_verifier_destroy(verifier);
245  macaroon_destroy(macaroon);
246  return m_chain ? m_chain->Access(Entity, path, oper, env) : XrdAccPriv_None;
247  }
248  macaroon_verifier_destroy(verifier);
249 
250  const unsigned char *macaroon_id;
251  size_t id_sz;
252  macaroon_identifier(macaroon, &macaroon_id, &id_sz);
253 
254  std::string macaroon_id_str(reinterpret_cast<const char *>(macaroon_id), id_sz);
255  m_log.Log(LogMask::Info, "Access", "Macaroon verification successful; ID", macaroon_id_str.c_str());
256  macaroon_destroy(macaroon);
257 
258  // Copy the name, if present into the macaroon, into the credential object.
259  if (Entity && check_helper.GetSecName().size()) {
260  const std::string &username = check_helper.GetSecName();
261  m_log.Log(LogMask::Debug, "Access", "Setting the request name to", username.c_str());
262  Entity->eaAPI->Add("request.name", username,true);
263  }
264 
265  // We passed verification - give the correct privilege.
266  return AddPriv(oper, XrdAccPriv_None);
267 }
268 
269 bool Authz::Validate(const char *token,
270  std::string &emsg,
271  long long *expT,
272  XrdSecEntity *entP)
273 {
274  macaroon_returncode mac_err = MACAROON_SUCCESS;
275  std::unique_ptr<struct macaroon, decltype(&macaroon_destroy)> macaroon(
276  macaroon_deserialize(token, &mac_err),
277  &macaroon_destroy);
278 
279  if (!macaroon)
280  {
281  emsg = "Failed to deserialize the token as a macaroon";
282  // Purposely log at debug level in case if this validation is ever
283  // chained so we don't have overly-chatty logs.
284  m_log.Log(LogMask::Debug, "Validate", emsg.c_str());
285  return false;
286  }
287 
288  std::unique_ptr<struct macaroon_verifier, decltype(&macaroon_verifier_destroy)> verifier(
289  macaroon_verifier_create(), &macaroon_verifier_destroy);
290  if (!verifier)
291  {
292  emsg = "Internal error: failed to create a verifier.";
293  m_log.Log(LogMask::Error, "Validate", emsg.c_str());
294  return false;
295  }
296 
297  // Note the path and operation here are ignored as we won't use those validators
298  AuthzCheck check_helper("/", AOP_Read, m_max_duration, m_log);
299 
300  if (macaroon_verifier_satisfy_general(verifier.get(), AuthzCheck::verify_before_s, &check_helper, &mac_err) ||
301  macaroon_verifier_satisfy_general(verifier.get(), validate_verify_empty, nullptr, &mac_err))
302  {
303  emsg = "Failed to configure the verifier";
304  m_log.Log(LogMask::Error, "Validate", emsg.c_str());
305  return false;
306  }
307 
308  const unsigned char *macaroon_loc;
309  size_t location_sz;
310  macaroon_location(macaroon.get(), &macaroon_loc, &location_sz);
311  if (strncmp(reinterpret_cast<const char *>(macaroon_loc), m_location.c_str(), location_sz))
312  {
313  emsg = "Macaroon contains incorrect location: " +
314  std::string(reinterpret_cast<const char *>(macaroon_loc), location_sz);
315  m_log.Log(LogMask::Warning, "Validate", emsg.c_str(), ("all.sitename is " + m_location).c_str());
316  return false;
317  }
318 
319  if (macaroon_verify(verifier.get(), macaroon.get(),
320  reinterpret_cast<const unsigned char *>(m_secret.c_str()),
321  m_secret.size(),
322  nullptr, 0,
323  &mac_err))
324  {
325  emsg = "Macaroon verification error" + (check_helper.GetErrorMessage().size() ?
326  (", " + check_helper.GetErrorMessage()) : "");
327  m_log.Log(LogMask::Warning, "Validate", emsg.c_str());
328  return false;
329  }
330 
331  const unsigned char *macaroon_id;
332  size_t id_sz;
333  macaroon_identifier(macaroon.get(), &macaroon_id, &id_sz);
334  m_log.Log(LogMask::Info, "Validate", ("Macaroon verification successful; ID " +
335  std::string(reinterpret_cast<const char *>(macaroon_id), id_sz)).c_str());
336 
337  return true;
338 }
339 
340 AuthzCheck::AuthzCheck(const char *req_path, const Access_Operation req_oper, ssize_t max_duration, XrdSysError &log)
341  : m_max_duration(max_duration),
342  m_log(log),
343  m_path(NormalizeSlashes(req_path)),
344  m_oper(req_oper),
345  m_now(time(nullptr))
346 {
347  switch (m_oper)
348  {
349  case AOP_Any:
350  break;
351  case AOP_Chmod:
352  case AOP_Chown:
353  m_desired_activity = "UPDATE_METADATA";
354  break;
355  case AOP_Insert:
356  case AOP_Lock:
357  case AOP_Mkdir:
358  case AOP_Update:
359  case AOP_Create:
360  m_desired_activity = "MANAGE";
361  break;
362  case AOP_Rename:
363  case AOP_Excl_Create:
364  case AOP_Excl_Insert:
365  m_desired_activity = "UPLOAD";
366  break;
367  case AOP_Delete:
368  m_desired_activity = "DELETE";
369  break;
370  case AOP_Read:
371  m_desired_activity = "DOWNLOAD";
372  break;
373  case AOP_Readdir:
374  m_desired_activity = "LIST";
375  break;
376  case AOP_Stat:
377  m_desired_activity = "READ_METADATA";
378  };
379 }
380 
381 int
382 AuthzCheck::verify_before_s(void *authz_ptr,
383  const unsigned char *pred,
384  size_t pred_sz)
385 {
386  return static_cast<AuthzCheck*>(authz_ptr)->verify_before(pred, pred_sz);
387 }
388 
389 int
390 AuthzCheck::verify_activity_s(void *authz_ptr,
391  const unsigned char *pred,
392  size_t pred_sz)
393 {
394  return static_cast<AuthzCheck*>(authz_ptr)->verify_activity(pred, pred_sz);
395 }
396 
397 int
398 AuthzCheck::verify_path_s(void *authz_ptr,
399  const unsigned char *pred,
400  size_t pred_sz)
401 {
402  return static_cast<AuthzCheck*>(authz_ptr)->verify_path(pred, pred_sz);
403 }
404 
405 int
406 AuthzCheck::verify_name_s(void *authz_ptr,
407  const unsigned char *pred,
408  size_t pred_sz)
409 {
410  return static_cast<AuthzCheck*>(authz_ptr)->verify_name(pred, pred_sz);
411 }
412 
413 int
414 AuthzCheck::verify_before(const unsigned char * pred, size_t pred_sz)
415 {
416  std::string pred_str(reinterpret_cast<const char *>(pred), pred_sz);
417  if (strncmp("before:", pred_str.c_str(), 7))
418  {
419  return 1;
420  }
421  m_log.Log(LogMask::Debug, "AuthzCheck", "Checking macaroon for expiration; caveat:", pred_str.c_str());
422 
423  struct tm caveat_tm;
424  if (strptime(&pred_str[7], "%Y-%m-%dT%H:%M:%SZ", &caveat_tm) == nullptr)
425  {
426  m_emsg = "Failed to parse time string: " + pred_str.substr(7);
427  m_log.Log(LogMask::Warning, "AuthzCheck", m_emsg.c_str());
428  return 1;
429  }
430  caveat_tm.tm_isdst = -1;
431 
432  time_t caveat_time = timegm(&caveat_tm);
433  if (-1 == caveat_time)
434  {
435  m_emsg = "Failed to generate unix time: " + pred_str.substr(7);
436  m_log.Log(LogMask::Warning, "AuthzCheck", m_emsg.c_str());
437  return 1;
438  }
439  if ((m_max_duration > 0) && (caveat_time > m_now + m_max_duration))
440  {
441  m_emsg = "Max token age is greater than configured max duration; rejecting";
442  m_log.Log(LogMask::Warning, "AuthzCheck", m_emsg.c_str());
443  return 1;
444  }
445 
446  int result = (m_now >= caveat_time);
447  if (!result)
448  {
449  m_log.Log(LogMask::Debug, "AuthzCheck", "Macaroon has not expired.");
450  }
451  else
452  {
453  m_emsg = "Macaroon expired at " + pred_str.substr(7);
454  m_log.Log(LogMask::Debug, "AuthzCheck", m_emsg.c_str());
455  }
456  return result;
457 }
458 
459 int
460 AuthzCheck::verify_activity(const unsigned char * pred, size_t pred_sz)
461 {
462  if (!m_desired_activity.size()) {return 1;}
463  std::string pred_str(reinterpret_cast<const char *>(pred), pred_sz);
464  if (strncmp("activity:", pred_str.c_str(), 9)) {return 1;}
465  m_log.Log(LogMask::Debug, "AuthzCheck", "running verify activity", pred_str.c_str());
466 
467  std::stringstream ss(pred_str.substr(9));
468  for (std::string activity; std::getline(ss, activity, ','); )
469  {
470  // Any allowed activity also implies "READ_METADATA"
471  if (m_desired_activity == "READ_METADATA") {return 0;}
472  if ((activity == m_desired_activity) || ((m_desired_activity == "UPLOAD") && (activity == "MANAGE")))
473  {
474  m_log.Log(LogMask::Debug, "AuthzCheck", "macaroon has desired activity", activity.c_str());
475  return 0;
476  }
477  }
478  m_log.Log(LogMask::Info, "AuthzCheck", "macaroon does NOT have desired activity", m_desired_activity.c_str());
479  return 1;
480 }
481 
482 int
483 AuthzCheck::verify_path(const unsigned char * pred, size_t pred_sz)
484 {
485  std::string pred_str_raw(reinterpret_cast<const char *>(pred), pred_sz);
486  if (strncmp("path:", pred_str_raw.c_str(), 5)) {return 1;}
487  std::string pred_str = NormalizeSlashes(pred_str_raw.substr(5));
488  m_log.Log(LogMask::Debug, "AuthzCheck", "running verify path", pred_str.c_str());
489 
490  if ((m_path.find("/./") != std::string::npos) ||
491  (m_path.find("/../") != std::string::npos))
492  {
493  m_log.Log(LogMask::Info, "AuthzCheck", "invalid requested path", m_path.c_str());
494  return 1;
495  }
496 
497  // Allow operations under subdirectories and not substrings
498  // For e.g. pred_str = "/data/sudir/mydir"
499  // Allows m_path = /data/subdir/mydir/newdir
500  // But rejects, m_path = /data/subdir/mydirmycoolname/newdir
501  int is_subdir = is_subdirectory(pred_str, m_path);
502  if (is_subdir)
503  {
504  m_log.Log(LogMask::Debug, "AuthzCheck", "path request verified for", m_path.c_str());
505  }
506 
507  // READ_METADATA (i.e AOP_Stat) permission for /foo/bar automatically implies permission
508  // to READ_METADATA for /foo.
509  // Similarly, MKDIR Pemissions for a parent path is implied.
510  else if (m_oper == AOP_Stat || m_oper == AOP_Mkdir)
511  {
512  is_subdir = is_subdirectory(m_path, pred_str);
513  const char *opName = (m_oper == AOP_Stat) ? "READ_METADATA" : "MKDIR";
514  m_log.Log(LogMask::Debug, "AuthzCheck",
515  (std::string(opName) + (is_subdir? " Path request verified for" : " Path request NOT allowed for")).c_str(),
516  m_path.c_str());
517  }
518  else
519  {
520  m_log.Log(LogMask::Debug, "AuthzCheck", "path request NOT allowed", m_path.c_str());
521  }
522 
523  return !is_subdir;
524 }
525 
526 int
527 AuthzCheck::verify_name(const unsigned char * pred, size_t pred_sz)
528 {
529  std::string pred_str(reinterpret_cast<const char *>(pred), pred_sz);
530  if (strncmp("name:", pred_str.c_str(), 5)) {return 1;}
531  if (pred_str.size() < 6) {return 1;}
532  m_log.Log(LogMask::Debug, "AuthzCheck", "Verifying macaroon with", pred_str.c_str());
533 
534  // Make a copy of the name for the XrdSecEntity; this will be used later.
535  m_sec_name = pred_str.substr(5);
536 
537  return 0;
538 }
Access_Operation
The following are supported operations.
@ AOP_Delete
rm() or rmdir()
@ AOP_Mkdir
mkdir()
@ AOP_Update
open() r/w or append
@ AOP_Create
open() with create
@ AOP_Readdir
opendir()
@ AOP_Chmod
chmod()
@ AOP_Any
Special for getting privs.
@ AOP_Stat
exists(), stat()
@ AOP_Rename
mv() for source
@ AOP_Read
open() r/o, prepare()
@ AOP_Excl_Create
open() with O_EXCL|O_CREAT
@ AOP_Insert
mv() for target
@ AOP_Lock
n/a
@ AOP_Chown
chown()
@ AOP_Excl_Insert
mv() where destination doesn't exist.
XrdAccPrivs
Definition: XrdAccPrivs.hh:39
@ XrdAccPriv_Mkdir
Definition: XrdAccPrivs.hh:46
@ XrdAccPriv_Chown
Definition: XrdAccPrivs.hh:41
@ XrdAccPriv_Insert
Definition: XrdAccPrivs.hh:44
@ XrdAccPriv_Lookup
Definition: XrdAccPrivs.hh:47
@ XrdAccPriv_Rename
Definition: XrdAccPrivs.hh:48
@ XrdAccPriv_Update
Definition: XrdAccPrivs.hh:52
@ XrdAccPriv_Read
Definition: XrdAccPrivs.hh:49
@ XrdAccPriv_Lock
Definition: XrdAccPrivs.hh:45
@ XrdAccPriv_None
Definition: XrdAccPrivs.hh:53
@ XrdAccPriv_Delete
Definition: XrdAccPrivs.hh:43
@ XrdAccPriv_Create
Definition: XrdAccPrivs.hh:42
@ XrdAccPriv_Readdir
Definition: XrdAccPrivs.hh:50
@ XrdAccPriv_Chmod
Definition: XrdAccPrivs.hh:40
static bool is_subdirectory(const std::string_view dir, const std::string_view subdir)
bool Debug
void getline(uchar *buff, int blen)
int emsg(int rc, char *msg)
@ Error
virtual bool Validate(const char *token, std::string &emsg, long long *expT, XrdSecEntity *entP) override
Authz(XrdSysLogger *lp, const char *parms, XrdAccAuthorize *chain)
virtual XrdAccPrivs Access(const XrdSecEntity *Entity, const char *path, const Access_Operation oper, XrdOucEnv *env) override
static bool Config(const char *config, XrdOucEnv *env, XrdSysError *log, std::string &location, std::string &secret, ssize_t &max_duration, AuthzBehavior &behavior)
virtual XrdAccPrivs Access(const XrdSecEntity *Entity, const char *path, const Access_Operation oper, XrdOucEnv *Env=0)=0
char * Get(const char *varname)
Definition: XrdOucEnv.hh:69
bool Add(XrdSecAttr &attr)
int credslen
Length of the 'creds' data.
Definition: XrdSecEntity.hh:78
XrdSecEntityAttr * eaAPI
non-const API to attributes
Definition: XrdSecEntity.hh:92
char prot[XrdSecPROTOIDSIZE]
Auth protocol used (e.g. krb5)
Definition: XrdSecEntity.hh:67
char * creds
Raw entity credentials or cert.
Definition: XrdSecEntity.hh:77
int Emsg(const char *esfx, int ecode, const char *text1, const char *text2=0)
Definition: XrdSysError.cc:95
void Log(int mask, const char *esfx, const char *text1, const char *text2=0, const char *text3=0)
Definition: XrdSysError.hh:133
std::string NormalizeSlashes(const std::string &)
@ Warning