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