XRootD
XrdMacaroonsHandler.cc
Go to the documentation of this file.
1 #include "XrdMacaroonsHandler.hh"
2 
4 #include "XrdAcc/XrdAccPrivs.hh"
5 #include "XrdOuc/XrdOucTUtils.hh"
6 #include "XrdSec/XrdSecEntity.hh"
7 #include "XrdSys/XrdSysError.hh"
8 
9 #include <cstring>
10 #include <iostream>
11 #include <sstream>
12 #include <string>
13 
14 #include <json.h>
15 #include <macaroons.h>
16 #include <uuid/uuid.h>
17 
18 using namespace Macaroons;
19 
20 char *unquote(const char *str) {
21  int l = strlen(str);
22  char *r = (char *) malloc(l + 1);
23  r[0] = '\0';
24  int i, j = 0;
25 
26  for (i = 0; i < l; i++) {
27 
28  if (str[i] == '%') {
29  char savec[3];
30  if (l <= i + 3) {
31  free(r);
32  return nullptr;
33  }
34  savec[0] = str[i + 1];
35  savec[1] = str[i + 2];
36  savec[2] = '\0';
37 
38  r[j] = strtol(savec, 0, 16);
39 
40  i += 2;
41  } else if (str[i] == '+') r[j] = ' ';
42  else r[j] = str[i];
43 
44  j++;
45  }
46 
47  r[j] = '\0';
48 
49  return r;
50 
51 }
52 
53 std::string Macaroons::NormalizeSlashes(const std::string &input)
54 {
55  std::string output;
56  // In most cases, the output should be "about as large"
57  // as the input
58  output.reserve(input.size());
59  char prior_chr = '\0';
60  size_t output_idx = 0;
61  for (size_t idx = 0; idx < input.size(); idx++) {
62  char chr = input[idx];
63  if (prior_chr == '/' && chr == '/') {
64  output_idx++;
65  continue;
66  }
67  output += input[output_idx];
68  prior_chr = chr;
69  output_idx++;
70  }
71  return output;
72 }
73 
74 static bool is_reserved_caveat(const std::string &cv)
75 {
76  return cv.compare(0, 5, "name:") == 0 ||
77  cv.compare(0, 5, "path:") == 0 ||
78  cv.compare(0, 7, "before:") == 0;
79 }
80 
81 static bool is_supported_caveat(const std::string &cv)
82 {
83  return cv.compare(0, 9, "activity:") == 0;
84 }
85 
86 static
87 ssize_t determine_validity(const std::string& input)
88 {
89  ssize_t duration = 0;
90  if (input.find("PT") != 0)
91  {
92  return -1;
93  }
94  size_t pos = 2;
95  std::string remaining = input;
96  do
97  {
98  remaining = remaining.substr(pos);
99  if (remaining.size() == 0) break;
100  long cur_duration;
101  try
102  {
103  cur_duration = stol(remaining, &pos);
104  } catch (...)
105  {
106  return -1;
107  }
108  if (pos >= remaining.size())
109  {
110  return -1;
111  }
112  char unit = remaining[pos];
113  switch (unit) {
114  case 'S':
115  break;
116  case 'M':
117  cur_duration *= 60;
118  break;
119  case 'H':
120  cur_duration *= 3600;
121  break;
122  default:
123  return -1;
124  };
125  pos ++;
126  duration += cur_duration;
127  } while (1);
128  return duration;
129 }
130 
132 {
133  delete m_chain;
134 }
135 
136 
137 std::string
138 Handler::GenerateID(const std::string &resource,
139  const XrdSecEntity &entity,
140  const std::string &activities,
141  const std::vector<std::string> &other_caveats,
142  const std::string &before)
143 {
144  uuid_t uu;
145  uuid_generate_random(uu);
146  char uuid_buf[37];
147  uuid_unparse(uu, uuid_buf);
148  std::string result(uuid_buf);
149 
150 // The following code shoul have been strictly for debugging purposes. This
151 // added code skips it unless debug logging has been enabled. Due to the code
152 // structure, indentation is a bit of a struggle as this is a minimal fix.
153 //
154 if (m_log->getMsgMask() & LogMask::Debug)
155  {
156  std::stringstream ss;
157  ss << "ID=" << result << ", ";
158  ss << "resource=" << NormalizeSlashes(resource) << ", ";
159  if (entity.prot[0] != '\0') {ss << "protocol=" << entity.prot << ", ";}
160  if (entity.name) {ss << "name=" << entity.name << ", ";}
161  if (entity.host) {ss << "host=" << entity.host << ", ";}
162  if (entity.vorg) {ss << "vorg=" << entity.vorg << ", ";}
163  if (entity.role) {ss << "role=" << entity.role << ", ";}
164  if (entity.grps) {ss << "groups=" << entity.grps << ", ";}
165  if (entity.endorsements) {ss << "endorsements=" << entity.endorsements << ", ";}
166  if (activities.size()) {ss << "base_activities=" << activities << ", ";}
167 
168  for (std::vector<std::string>::const_iterator iter = other_caveats.begin();
169  iter != other_caveats.end();
170  iter++)
171  {
172  ss << "user_caveat=" << *iter << ", ";
173  }
174 
175  ss << "expires=" << before;
176 
177  m_log->Emsg("MacaroonGen", ss.str().c_str()); // Mask::Debug
178  }
179  return result;
180 }
181 
182 std::string
183 Handler::GenerateActivities(const XrdHttpExtReq & req, const std::string &resource) const
184 {
185  std::string result = "activity:READ_METADATA";
186  // TODO - generate environment object that includes the Authorization header.
187  XrdAccPrivs privs = m_chain ? m_chain->Access(&req.GetSecEntity(), resource.c_str(), AOP_Any, nullptr) : XrdAccPriv_None;
188  if ((privs & XrdAccPriv_Create) == XrdAccPriv_Create) {result += ",UPLOAD";}
189  if (privs & XrdAccPriv_Read) {result += ",DOWNLOAD";}
190  if (privs & XrdAccPriv_Delete) {result += ",DELETE";}
191  if ((privs & XrdAccPriv_Chown) == XrdAccPriv_Chown) {result += ",MANAGE,UPDATE_METADATA";}
192  if (privs & XrdAccPriv_Readdir) {result += ",LIST";}
193  return result;
194 }
195 
196 // See if the macaroon handler is interested in this request.
197 // We intercept all POST requests as we will be looking for a particular
198 // header.
199 bool
200 Handler::MatchesPath(const char *verb, const char *path)
201 {
202  return !strcmp(verb, "POST") || !strncmp(path, "/.well-known/", 13) ||
203  !strncmp(path, "/.oauth2/", 9);
204 }
205 
206 int Handler::ProcessOAuthConfig(XrdHttpExtReq &req) {
207  if (req.verb != "GET")
208  {
209  return req.SendSimpleResp(405, nullptr, nullptr, "Only GET is valid for oauth config.", 0);
210  }
211  auto header = XrdOucTUtils::caseInsensitiveFind(req.headers,"host");
212  if (header == req.headers.end())
213  {
214  return req.SendSimpleResp(400, nullptr, nullptr, "Host header is required.", 0);
215  }
216 
217  json_object *response_obj = json_object_new_object();
218  if (!response_obj)
219  {
220  return req.SendSimpleResp(500, nullptr, nullptr, "Unable to create new JSON response object.", 0);
221  }
222  std::string token_endpoint = "https://" + header->second + "/.oauth2/token";
223  json_object *endpoint_obj =
224  json_object_new_string_len(token_endpoint.c_str(), token_endpoint.size());
225  if (!endpoint_obj)
226  {
227  return req.SendSimpleResp(500, nullptr, nullptr, "Unable to create a new JSON macaroon string.", 0);
228  }
229  json_object_object_add(response_obj, "token_endpoint", endpoint_obj);
230 
231  const char *response_result = json_object_to_json_string_ext(response_obj, JSON_C_TO_STRING_PRETTY);
232  int retval = req.SendSimpleResp(200, nullptr, nullptr, response_result, 0);
233  json_object_put(response_obj);
234  return retval;
235 }
236 
237 int Handler::ProcessTokenRequest(XrdHttpExtReq &req)
238 {
239  if (req.verb != "POST")
240  return req.SendSimpleResp(405, nullptr, "allow: POST",
241  "Only POST method is allowed to request a macaroon", false);
242 
243  auto header = XrdOucTUtils::caseInsensitiveFind(req.headers, "content-type");
244  if (header == req.headers.end() || header->second != "application/x-www-form-urlencoded")
245  return req.SendSimpleResp(415, nullptr, "accept: application/x-www-form-urlencoded",
246  "Content-Type must be 'application/macaroon-request' to request a macaroon", false);
247 
248  if (req.length > 4096)
249  return req.SendSimpleResp(413, nullptr, nullptr, "Macaroon request too large (must be less than 4KB)", false);
250 
251  // Note: this does not null-terminate the buffer contents.
252  char *request_data_raw = nullptr;
253 
254  if (req.length <= 0 || req.BuffgetData(req.length, &request_data_raw, true) != req.length)
255  return req.SendSimpleResp(400, nullptr, nullptr, "Missing or invalid body of request.", 0);
256 
257  std::string request_data(request_data_raw, req.length);
258  bool found_grant_type = false;
259  ssize_t validity = -1;
260  std::string scope;
261  std::string token;
262  std::istringstream token_stream(request_data);
263  while (std::getline(token_stream, token, '&'))
264  {
265  std::string::size_type eq = token.find("=");
266  if (eq == std::string::npos)
267  {
268  return req.SendSimpleResp(400, nullptr, nullptr, "Invalid format for form-encoding", 0);
269  }
270  std::string key = token.substr(0, eq);
271  std::string value = token.substr(eq + 1);
272  //std::cout << "Found key " << key << ", value " << value << std::endl;
273  if (key == "grant_type")
274  {
275  found_grant_type = true;
276  if (value != "client_credentials")
277  {
278  return req.SendSimpleResp(400, nullptr, nullptr, "Invalid grant type specified.", 0);
279  }
280  }
281  else if (key == "expire_in")
282  {
283  if ((validity = std::strtoll(value.c_str(), nullptr, 10)) <= 0)
284  return req.SendSimpleResp(400, nullptr, nullptr, "Expiration request has invalid value.", 0);
285  }
286  else if (key == "scope")
287  {
288  char *value_raw = unquote(value.c_str());
289  if (value_raw == nullptr)
290  {
291  return req.SendSimpleResp(400, nullptr, nullptr, "Unable to unquote scope.", 0);
292  }
293  scope = value_raw;
294  free(value_raw);
295  }
296  }
297  if (!found_grant_type)
298  {
299  return req.SendSimpleResp(400, nullptr, nullptr, "Grant type not specified.", 0);
300  }
301  if (scope.empty())
302  {
303  return req.SendSimpleResp(400, nullptr, nullptr, "Scope was not specified.", 0);
304  }
305  std::istringstream token_stream_scope(scope);
306  std::string path;
307  std::vector<std::string> other_caveats;
308  while (std::getline(token_stream_scope, token, ' '))
309  {
310  std::string::size_type col = token.find(":");
311  if (col == std::string::npos)
312  {
313  return req.SendSimpleResp(400, nullptr, nullptr, "Invalid format for requested scope", 0);
314  }
315  std::string key = token.substr(0, col);
316  std::string value = token.substr(col + 1);
317  //std::cout << "Found activity " << key << ", path " << value << std::endl;
318  if (path.empty())
319  {
320  path = value;
321  }
322  else if (value != path)
323  {
324  if (m_log->getMsgMask() & LogMask::Error) {
325  std::stringstream ss;
326  ss << "Encountered requested scope request for authorization " << key
327  << " with resource path " << value << "; however, prior request had path "
328  << path;
329  m_log->Emsg("MacaroonRequest", ss.str().c_str()); // Mask::Error
330  }
331  return req.SendSimpleResp(500, nullptr, nullptr, "Server only supports all scopes having the same path", 0);
332  }
333  other_caveats.push_back(key);
334  }
335  if (path.empty())
336  {
337  path = "/";
338  }
339  std::vector<std::string> other_caveats_final;
340  if (!other_caveats.empty()) {
341  std::stringstream ss;
342  ss << "activity:";
343  for (std::vector<std::string>::const_iterator iter = other_caveats.begin();
344  iter != other_caveats.end();
345  iter++)
346  {
347  ss << *iter << ",";
348  }
349  const std::string &final_str = ss.str();
350  other_caveats_final.push_back(final_str.substr(0, final_str.size() - 1));
351  }
352  return GenerateMacaroonResponse(req, path, other_caveats_final, validity, true);
353 }
354 
355 // Process a macaroon request.
357 {
358  if (req.resource == "/.well-known/oauth-authorization-server") {
359  return ProcessOAuthConfig(req);
360  } else if (req.resource == "/.oauth2/token") {
361  return ProcessTokenRequest(req);
362  }
363 
364  auto header = XrdOucTUtils::caseInsensitiveFind(req.headers,"content-type");
365  if (header == req.headers.end() || header->second != "application/macaroon-request")
366  return req.SendSimpleResp(415, nullptr, "accept: application/macaroon-request",
367  "Content-Type must be 'application/macaroon-request' to request a macaroon", false);
368 
369  header = XrdOucTUtils::caseInsensitiveFind(req.headers,"content-length");
370  if (header == req.headers.end())
371  return req.SendSimpleResp(411, nullptr, nullptr, "Content-Length missing; not a valid POST", false);
372 
373  ssize_t blen = std::strtoll(header->second.c_str(), nullptr, 10);
374 
375  if (blen <= 0)
376  return req.SendSimpleResp(400, nullptr, nullptr, "Content-Length has invalid value.", false);
377 
378  if (blen > 4096)
379  return req.SendSimpleResp(413, nullptr, nullptr, "Macaroon request too large (must be less than 4KB)", false);
380 
381  // request_data is not necessarily null-terminated; hence, we use the more advanced _ex variant
382  // of the tokener to avoid making a copy of the character buffer.
383  char *request_data;
384  if (req.BuffgetData(blen, &request_data, true) != blen)
385  {
386  return req.SendSimpleResp(400, nullptr, nullptr, "Missing or invalid body of request.", 0);
387  }
388  json_tokener *tokener = json_tokener_new();
389  if (!tokener)
390  {
391  return req.SendSimpleResp(500, nullptr, nullptr, "Internal error when allocating token parser.", 0);
392  }
393  json_object *macaroon_req = json_tokener_parse_ex(tokener, request_data, blen);
394  enum json_tokener_error err = json_tokener_get_error(tokener);
395  json_tokener_free(tokener);
396  if (err != json_tokener_success)
397  {
398  if (macaroon_req) json_object_put(macaroon_req);
399  return req.SendSimpleResp(400, nullptr, nullptr, "Invalid JSON serialization of macaroon request.", 0);
400  }
401  json_object *validity_obj;
402  if (!json_object_object_get_ex(macaroon_req, "validity", &validity_obj))
403  {
404  json_object_put(macaroon_req);
405  return req.SendSimpleResp(400, nullptr, nullptr, "JSON request does not include a `validity`", 0);
406  }
407  const char *validity_cstr = json_object_get_string(validity_obj);
408  if (!validity_cstr)
409  {
410  json_object_put(macaroon_req);
411  return req.SendSimpleResp(400, nullptr, nullptr, "validity key cannot be cast to a string", 0);
412  }
413  std::string validity_str(validity_cstr);
414  ssize_t validity = determine_validity(validity_str);
415  if (validity <= 0)
416  {
417  json_object_put(macaroon_req);
418  return req.SendSimpleResp(400, nullptr, nullptr, "Invalid ISO 8601 duration for validity key", 0);
419  }
420  json_object *caveats_obj;
421  std::vector<std::string> other_caveats;
422  if (json_object_object_get_ex(macaroon_req, "caveats", &caveats_obj))
423  {
424  if (json_object_is_type(caveats_obj, json_type_array))
425  { // Caveats were provided. Let's record them.
426  // TODO - could just add these in-situ. No need for the other_caveats vector.
427  int array_length = json_object_array_length(caveats_obj);
428  other_caveats.reserve(array_length);
429  for (int idx=0; idx<array_length; idx++)
430  {
431  json_object *caveat_item = json_object_array_get_idx(caveats_obj, idx);
432  if (caveat_item)
433  {
434  const char *caveat_item_str = json_object_get_string(caveat_item);
435 
436  if (!caveat_item_str) {
437  json_object_put(macaroon_req);
438  return req.SendSimpleResp(400, nullptr, nullptr, "Malformed or invalid caveat", 0);
439  }
440 
441  if (is_reserved_caveat(caveat_item_str)) {
442  json_object_put(macaroon_req);
443  return req.SendSimpleResp(400, nullptr, nullptr,
444  "Cannot accept caveat with reserved key (name, path, before)\n", 0);
445  }
446 
447  if (!is_supported_caveat(caveat_item_str)) {
448  json_object_put(macaroon_req);
449  return req.SendSimpleResp(400, nullptr, nullptr,
450  "Cannot accept caveat of unsupported type (supported types: activity)\n", 0);
451  }
452 
453  other_caveats.emplace_back(caveat_item_str);
454  }
455  }
456  }
457  }
458  json_object_put(macaroon_req);
459 
460  return GenerateMacaroonResponse(req, req.resource, other_caveats, validity, false);
461 }
462 
463 
464 int
465 Handler::GenerateMacaroonResponse(XrdHttpExtReq &req, const std::string &resource,
466  const std::vector<std::string> &other_caveats, ssize_t validity, bool oauth_response)
467 {
468  time_t now;
469  time(&now);
470  if (m_max_duration > 0)
471  {
472  validity = (validity > m_max_duration) ? m_max_duration : validity;
473  }
474  now += validity;
475 
476  char utc_time_buf[21];
477  if (!strftime(utc_time_buf, 21, "%FT%TZ", gmtime(&now)))
478  {
479  return req.SendSimpleResp(500, nullptr, nullptr, "Internal error constructing UTC time", 0);
480  }
481  std::string utc_time_str(utc_time_buf);
482  std::stringstream ss;
483  ss << "before:" << utc_time_str;
484  std::string utc_time_caveat = ss.str();
485 
486  std::string activities = GenerateActivities(req, resource);
487 
488  // Overwrite activities with user-provided caveat if present (last one wins)
489  for (const auto &caveat : other_caveats) {
490  if (caveat.compare(0, 9, "activity:") == 0)
491  activities = caveat;
492  }
493 
494  std::string macaroon_id = GenerateID(resource, req.GetSecEntity(), activities, other_caveats, utc_time_str);
495  enum macaroon_returncode mac_err;
496 
497  struct macaroon *mac = macaroon_create(reinterpret_cast<const unsigned char*>(m_location.c_str()),
498  m_location.size(),
499  reinterpret_cast<const unsigned char*>(m_secret.c_str()),
500  m_secret.size(),
501  reinterpret_cast<const unsigned char*>(macaroon_id.c_str()),
502  macaroon_id.size(), &mac_err);
503  if (!mac) {
504  return req.SendSimpleResp(500, nullptr, nullptr, "Internal error constructing the macaroon", 0);
505  }
506 
507  // Embed the SecEntity name, if present.
508  struct macaroon *mac_with_name;
509  const char * sec_name = req.GetSecEntity().name;
510  if (sec_name) {
511  std::stringstream name_caveat_ss;
512  name_caveat_ss << "name:" << sec_name;
513  std::string name_caveat = name_caveat_ss.str();
514  mac_with_name = macaroon_add_first_party_caveat(mac,
515  reinterpret_cast<const unsigned char*>(name_caveat.c_str()),
516  name_caveat.size(),
517  &mac_err);
518  macaroon_destroy(mac);
519  } else {
520  mac_with_name = mac;
521  }
522  if (!mac_with_name)
523  {
524  return req.SendSimpleResp(500, nullptr, nullptr, "Internal error adding 'name' caveat to macaroon", 0);
525  }
526 
527  struct macaroon *mac_with_activities = macaroon_add_first_party_caveat(mac_with_name,
528  reinterpret_cast<const unsigned char*>(activities.c_str()),
529  activities.size(),
530  &mac_err);
531  macaroon_destroy(mac_with_name);
532  if (!mac_with_activities)
533  {
534  return req.SendSimpleResp(500, nullptr, nullptr, "Internal error adding 'activity' caveat to macaroon", 0);
535  }
536 
537  // Note we don't call `NormalizeSlashes` here; for backward compatibility reasons, we ensure the
538  // token issued is identical to what was working with prior versions of XRootD. This allows for a
539  // mix of old/new versions in a single cluster to interoperate. In a few years, it might be reasonable
540  // to invoke it here as well.
541  std::string path_caveat = "path:" + resource;
542  struct macaroon *mac_with_path = macaroon_add_first_party_caveat(mac_with_activities,
543  reinterpret_cast<const unsigned char*>(path_caveat.c_str()),
544  path_caveat.size(),
545  &mac_err);
546  macaroon_destroy(mac_with_activities);
547  if (!mac_with_path) {
548  return req.SendSimpleResp(500, nullptr, nullptr, "Internal error adding 'path' caveat to macaroon", 0);
549  }
550 
551  struct macaroon *mac_with_date = macaroon_add_first_party_caveat(mac_with_path,
552  reinterpret_cast<const unsigned char*>(utc_time_caveat.c_str()),
553  utc_time_caveat.size(),
554  &mac_err);
555  macaroon_destroy(mac_with_path);
556  if (!mac_with_date) {
557  return req.SendSimpleResp(500, nullptr, nullptr, "Internal error adding date to macaroon", 0);
558  }
559 
560  size_t size_hint = macaroon_serialize_size_hint(mac_with_date);
561 
562  std::vector<char> macaroon_resp; macaroon_resp.resize(size_hint);
563  if (macaroon_serialize(mac_with_date, &macaroon_resp[0], size_hint, &mac_err))
564  {
565  printf("Returned macaroon_serialize code: %zu\n", size_hint);
566  return req.SendSimpleResp(500, nullptr, nullptr, "Internal error serializing macaroon", 0);
567  }
568  macaroon_destroy(mac_with_date);
569 
570  json_object *response_obj = json_object_new_object();
571  if (!response_obj)
572  {
573  return req.SendSimpleResp(500, nullptr, nullptr, "Unable to create new JSON response object.", 0);
574  }
575  json_object *macaroon_obj = json_object_new_string_len(&macaroon_resp[0], strlen(&macaroon_resp[0]));
576  if (!macaroon_obj)
577  {
578  return req.SendSimpleResp(500, nullptr, nullptr, "Unable to create a new JSON macaroon string.", 0);
579  }
580  json_object_object_add(response_obj, oauth_response ? "access_token" : "macaroon", macaroon_obj);
581 
582  json_object *expire_in_obj = json_object_new_int64(validity);
583  if (!expire_in_obj)
584  {
585  return req.SendSimpleResp(500, nullptr, nullptr, "Unable to create a new JSON validity object.", 0);
586  }
587  json_object_object_add(response_obj, "expires_in", expire_in_obj);
588 
589  const char *macaroon_result = json_object_to_json_string_ext(response_obj, JSON_C_TO_STRING_PRETTY);
590  int retval = req.SendSimpleResp(200, nullptr, nullptr, macaroon_result, 0);
591  json_object_put(response_obj);
592  return retval;
593 }
@ AOP_Any
Special for getting privs.
XrdAccPrivs
Definition: XrdAccPrivs.hh:39
@ XrdAccPriv_Chown
Definition: XrdAccPrivs.hh:41
@ XrdAccPriv_Read
Definition: XrdAccPrivs.hh:49
@ XrdAccPriv_None
Definition: XrdAccPrivs.hh:53
@ XrdAccPriv_Delete
Definition: XrdAccPrivs.hh:43
@ XrdAccPriv_Create
Definition: XrdAccPrivs.hh:42
@ XrdAccPriv_Readdir
Definition: XrdAccPrivs.hh:50
static bool is_supported_caveat(const std::string &cv)
static bool is_reserved_caveat(const std::string &cv)
char * unquote(const char *str)
static ssize_t determine_validity(const std::string &input)
bool Debug
void getline(uchar *buff, int blen)
@ Error
virtual bool MatchesPath(const char *verb, const char *path) override
Tells if the incoming path is recognized as one of the paths that have to be processed.
virtual int ProcessReq(XrdHttpExtReq &req) override
virtual XrdAccPrivs Access(const XrdSecEntity *Entity, const char *path, const Access_Operation oper, XrdOucEnv *Env=0)=0
std::map< std::string, std::string > & headers
std::string resource
std::string verb
int BuffgetData(int blen, char **data, bool wait)
Get a pointer to data read from the client, valid for up to blen bytes from the buffer....
const XrdSecEntity & GetSecEntity() const
int SendSimpleResp(int code, const char *desc, const char *header_to_add, const char *body, long long bodylen)
Sends a basic response. If the length is < 0 then it is calculated internally.
static std::map< std::string, T >::const_iterator caseInsensitiveFind(const std::map< std::string, T > &m, const std::string &lowerCaseSearchKey)
Definition: XrdOucTUtils.hh:79
char * vorg
Entity's virtual organization(s)
Definition: XrdSecEntity.hh:71
char prot[XrdSecPROTOIDSIZE]
Auth protocol used (e.g. krb5)
Definition: XrdSecEntity.hh:67
char * grps
Entity's group name(s)
Definition: XrdSecEntity.hh:73
char * name
Entity's name.
Definition: XrdSecEntity.hh:69
char * role
Entity's role(s)
Definition: XrdSecEntity.hh:72
char * endorsements
Protocol specific endorsements.
Definition: XrdSecEntity.hh:75
char * host
Entity's host name dnr dependent.
Definition: XrdSecEntity.hh:70
int Emsg(const char *esfx, int ecode, const char *text1, const char *text2=0)
Definition: XrdSysError.cc:95
int getMsgMask()
Definition: XrdSysError.hh:156
std::string NormalizeSlashes(const std::string &)