XRootD
XrdVomsMapfile.cc
Go to the documentation of this file.
1 /******************************************************************************/
2 /* */
3 /* X r d V o m s M a p f i l e . c c */
4 /* */
5 /* This file is part of the XRootD software suite. */
6 /* */
7 /* XRootD is free software: you can redistribute it and/or modify it under */
8 /* the terms of the GNU Lesser General Public License as published by the */
9 /* Free Software Foundation, either version 3 of the License, or (at your */
10 /* option) any later version. */
11 /* */
12 /* XRootD is distributed in the hope that it will be useful, but WITHOUT */
13 /* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */
14 /* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */
15 /* License for more details. */
16 /* */
17 /* You should have received a copy of the GNU Lesser General Public License */
18 /* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */
19 /* COPYING (GPL license). If not, see <http://www.gnu.org/licenses/>. */
20 /* */
21 /* The copyright holder's institutional names and contributor's names may not */
22 /* be used to endorse or promote products derived from this software without */
23 /* specific prior written permission of the institution or contributor. */
24 /******************************************************************************/
25 
27 
28 #include "XrdOuc/XrdOucEnv.hh"
29 #include "XrdOuc/XrdOucString.hh"
30 #include "XrdOuc/XrdOucStream.hh"
31 #include "XrdSec/XrdSecEntity.hh"
33 #include "XrdSys/XrdSysError.hh"
34 #include "XrdSys/XrdSysFD.hh"
35 #include "XrdSys/XrdSysPthread.hh"
36 
37 #include <memory>
38 #include <fstream>
39 #include <sstream>
40 #include <vector>
41 #include <string>
42 #include <fcntl.h>
43 #include <poll.h>
44 
45 #if defined(__APPLE__) || defined(__NetBSD__)
46 #define st_ctim st_ctimespec
47 #endif
48 
49 bool XrdVomsMapfile::tried_configure = false;
50 std::unique_ptr<XrdVomsMapfile> XrdVomsMapfile::mapper;
51 
52 namespace {
53 
54 std::string
55 PathToString(const std::vector<std::string> &path)
56 {
57  if (path.empty()) {return "/";}
58  std::stringstream ss;
59  for (const auto &entry : path) {
60  ss << "/" << entry;
61  }
62 
63  return ss.str();
64 }
65 
66 uint64_t monotonic_time_s() {
67  struct timespec tp;
68  clock_gettime(CLOCK_MONOTONIC, &tp);
69  return tp.tv_sec + (tp.tv_nsec >= 500000000);
70 }
71 
72 }
73 
74 
75 XrdVomsMapfile::XrdVomsMapfile(XrdSysError *erp,
76  const std::string &mapfile)
77  : m_mapfile(mapfile), m_edest(erp)
78 {
79  struct stat statbuf;
80  if (-1 == stat(m_mapfile.c_str(), &statbuf)) {
81  m_edest->Emsg("XrdVomsMapfile", errno, "Error checking the mapfile", m_mapfile.c_str());
82  return;
83  }
84  memcpy(&m_mapfile_ctime, &statbuf.st_ctim, sizeof(decltype(m_mapfile_ctime)));
85 
86  if (!ParseMapfile(m_mapfile)) {return;}
87 
88  pthread_t tid;
89  auto rc = XrdSysThread::Run(&tid, XrdVomsMapfile::MaintenanceThread,
90  static_cast<void*>(this), 0, "VOMS Mapfile refresh");
91  if (rc) {
92  m_edest->Emsg("XrdVomsMapfile", "Failed to launch VOMS mapfile monitoring thread");
93  return;
94  }
95  m_is_valid = true;
96 }
97 
98 
100 {}
101 
102 
103 bool
104 XrdVomsMapfile::ParseMapfile(const std::string &mapfile)
105 {
106  std::ifstream fstr(mapfile);
107  if (!fstr.is_open()) {
108  m_edest->Emsg("ParseMapfile", "Failed to open file", mapfile.c_str(), strerror(errno));
109  return false;
110  }
111  std::shared_ptr<std::vector<MapfileEntry>> entries(new std::vector<MapfileEntry>());
112  for (std::string line; std::getline(fstr, line); ) {
113  MapfileEntry entry;
114  if (ParseLine(line, entry.m_path, entry.m_target) && !entry.m_path.empty()) {
115  if (m_edest->getMsgMask() & LogMask::Debug) {
116  m_edest->Log(LogMask::Debug, "ParseMapfile", PathToString(entry.m_path).c_str(), "->", entry.m_target.c_str());
117  }
118  entries->emplace_back(entry);
119  }
120  }
121  m_entries = entries;
122  return true;
123 }
124 
125 
126 bool
127 XrdVomsMapfile::ParseLine(const std::string &line, std::vector<std::string> &entry, std::string &target)
128 {
129  bool began_entry = false;
130  bool finish_entry = false;
131  bool began_target = false;
132  std::string element;
133  element.reserve(16);
134  for (size_t idx=0; idx<line.size(); idx++) {
135  auto txt = line[idx];
136  if (!began_entry && !finish_entry) {
137  if (txt == '#') {return false;}
138  else if (txt == '"') {began_entry = true;}
139  else if (!isspace(txt)) {return false;}
140  continue;
141  } else if (began_entry && !finish_entry) {
142  if (txt == '\\') {
143  if (idx + 1 == line.size()) {return false;}
144  idx++;
145  auto escaped_char = line[idx];
146  switch (escaped_char) {
147  case '\'':
148  element += "'";
149  break;
150  case '\"':
151  element += "\"";
152  break;
153  case '/':
154  element += "/";
155  break;
156  case 'f':
157  element += "\f";
158  break;
159  case 'n':
160  element += "\n";
161  break;
162  case 'r':
163  element += "\r";
164  break;
165  case 't':
166  element += "\t";
167  break;
168  default:
169  return false;
170  };
171  } else if (txt == '"') {
172  if (!element.empty()) entry.push_back(element);
173  finish_entry = true;
174  } else if (txt == '/') {
175  if (!element.empty()) entry.push_back(element);
176  element.clear();
177  } else if (isprint(txt)) {
178  element += txt;
179  } else {
180  return false;
181  }
182  } else if (!began_target) {
183  if (isspace(txt)) {continue;}
184  began_target = true;
185  }
186  if (began_target) {
187  if (isprint(txt)) {
188  target += txt;
189  } else if (isspace(txt)) {
190  return true;
191  } else {
192  return false;
193  }
194  }
195  }
196  return true;
197 }
198 
199 
200 std::string
201 XrdVomsMapfile::Map(const std::vector<std::string> &fqan)
202 {
203  decltype(m_entries) entries = m_entries;
204  if (!entries) {return "";}
205 
206  if (m_edest && (m_edest->getMsgMask() & LogMask::Debug)) {
207  m_edest->Log(LogMask::Debug, "VOMSMapfile", "Mapping VOMS FQAN", PathToString(fqan).c_str());
208  }
209 
210  for (const auto &entry : *entries) {
211  if (Compare(entry, fqan)) {
212  if (m_edest && (m_edest->getMsgMask() & LogMask::Debug)) {
213  m_edest->Log(LogMask::Debug, "VOMSMapfile", "Mapped FQAN to target", entry.m_target.c_str());
214  }
215  return entry.m_target;
216  }
217  }
218  return "";
219 }
220 
221 
222 bool
223 XrdVomsMapfile::Compare(const MapfileEntry &entry, const std::vector<std::string> &fqan)
224 {
225  if (entry.m_path.empty()) {return false;}
226 
227  // A more specific mapfile entry cannot match a generic FQAN
228  if (fqan.size() < entry.m_path.size()) {return false;}
229 
230  XrdOucString fqan_element;
231  for (size_t idx=0; idx<entry.m_path.size(); idx++) {
232  fqan_element.assign(fqan[idx].c_str(), 0);
233  const auto &path_element = entry.m_path[idx];
234  if (!fqan_element.matches(path_element.c_str())) {return false;}
235  }
236  if (fqan.size() == entry.m_path.size()) {return true;}
237  if (entry.m_path.back() == "*") {return true;}
238  return false;
239 }
240 
241 
242 std::vector<std::string>
243 XrdVomsMapfile::MakePath(const XrdOucString &group)
244 {
245  int from = 0;
246  XrdOucString entry;
247  std::vector<std::string> path;
248  path.reserve(4);
249  // The const'ness of the tokenize method as declared is incorrect; we use
250  // const_cast here to avoid fixing the XrdOucString header (which would break
251  // the ABI).
252  while ((from = const_cast<XrdOucString&>(group).tokenize(entry, from, '/')) != -1) {
253  if (entry.length() == 0) continue;
254  path.emplace_back(entry.c_str());
255  }
256  return path;
257 }
258 
259 
260 int
262 {
263  // In current use cases, the gridmap results take precedence over the voms-mapfile
264  // results. However, the grid mapfile plugins often will populate the name attribute
265  // with a reasonable default (DN or DN hash) if the mapping fails, meaning we can't
266  // simply look at entity.name; instead, we look at an extended attribute that is only
267  // set when the mapfile is used to generate the name.
268  std::string gridmap_name;
269  auto gridmap_success = entity.eaAPI->Get("gridmap.name", gridmap_name);
270  if (gridmap_success && gridmap_name == "1") {
271  return 0;
272  }
273 
274  int from_vorg = 0, from_role = 0, from_grps = 0;
275  XrdOucString vorg = entity.vorg, entry_vorg;
276  XrdOucString role = entity.role ? entity.role : "", entry_role = "NULL";
277  XrdOucString grps = entity.grps, entry_grps;
278  if (m_edest) m_edest->Log(LogMask::Debug, "VOMSMapfile", "Applying VOMS mapfile to incoming credential");
279  while (((from_vorg = vorg.tokenize(entry_vorg, from_vorg, ' ')) != -1) &&
280  ((role == "") || (from_role = role.tokenize(entry_role, from_role, ' ')) != -1) &&
281  ((from_grps = grps.tokenize(entry_grps, from_grps, ' ')) != -1))
282  {
283  auto fqan = MakePath(entry_grps);
284  if (fqan.empty()) {continue;}
285 
286  // By convention, the root group should be the same as the VO name; however,
287  // the VOMS mapfile makes this assumption. To be secure, enforce it.
288  if (strcmp(fqan[0].c_str(), entry_vorg.c_str())) {continue;}
289 
290  fqan.emplace_back(std::string("Role=") + entry_role.c_str());
291  fqan.emplace_back("Capability=NULL");
292  std::string username;
293  if (!(username = Map(fqan)).empty()) {
294  if (entity.name) {free(entity.name);}
295  entity.name = strdup(username.c_str());
296  break;
297  }
298  }
299 
300  return 0;
301 }
302 
303 
306 {
307  return mapper.get();
308 }
309 
310 
313 {
314  if (tried_configure) {
315  auto result = mapper.get();
316  if (result) {
317  result->SetErrorStream(erp);
318  }
319  return result;
320  }
321 
322  tried_configure = true;
323 
324  // Set default mask for logging.
325  if (erp) erp->setMsgMask(LogMask::Error | LogMask::Warning);
326 
327  char *config_filename = nullptr;
328  if (!XrdOucEnv::Import("XRDCONFIGFN", config_filename)) {
329  return VOMS_MAP_FAILED;
330  }
331  XrdOucEnv myEnv;
332  XrdOucStream stream(erp, getenv("XRDINSTANCE"), &myEnv, "=====> ");
333 
334  int cfg_fd;
335  if ((cfg_fd = open(config_filename, O_RDONLY, 0)) < 0) {
336  if (erp) erp->Emsg("Config", errno, "open config file", config_filename);
337  return VOMS_MAP_FAILED;
338  }
339  stream.Attach(cfg_fd);
340  char *var;
341  std::string map_filename;
342  while ((var = stream.GetMyFirstWord())) {
343  if (!strcmp(var, "voms.mapfile")) {
344  auto val = stream.GetWord();
345  if (!val || !val[0]) {
346  if (erp) erp->Emsg("Config", "VOMS mapfile not specified");
347  return VOMS_MAP_FAILED;
348  }
349  map_filename = val;
350  } else if (!strcmp(var, "voms.trace")) {
351  auto val = stream.GetWord();
352  if (!val || !val[0]) {
353  if (erp) erp->Emsg("Config", "VOMS logging level not specified");
354  return VOMS_MAP_FAILED;
355  }
356  if (erp) erp->setMsgMask(0);
357  if (erp) do {
358  if (!strcmp(val, "all")) {erp->setMsgMask(erp->getMsgMask() | LogMask::All);}
359  else if (!strcmp(val, "error")) {erp->setMsgMask(erp->getMsgMask() | LogMask::Error);}
360  else if (!strcmp(val, "warning")) {erp->setMsgMask(erp->getMsgMask() | LogMask::Warning);}
361  else if (!strcmp(val, "info")) {erp->setMsgMask(erp->getMsgMask() | LogMask::Info);}
362  else if (!strcmp(val, "debug")) {erp->setMsgMask(erp->getMsgMask() | LogMask::Debug);}
363  else if (!strcmp(val, "none")) {erp->setMsgMask(0);}
364  else {erp->Emsg("Config", "voms.trace encountered an unknown directive:", val);}
365  val = stream.GetWord();
366  } while (val);
367  }
368  }
369 
370  if (!map_filename.empty()) {
371  if (erp) erp->Emsg("Config", "Will initialize VOMS mapfile", map_filename.c_str());
372  mapper.reset(new XrdVomsMapfile(erp, map_filename));
373  if (!mapper->IsValid()) {
374  mapper.reset(nullptr);
375  return VOMS_MAP_FAILED;
376  }
377  }
378 
379  return mapper.get();
380 }
381 
382 
383 void *
384 XrdVomsMapfile::MaintenanceThread(void *myself_raw)
385 {
386  auto myself = static_cast<XrdVomsMapfile*>(myself_raw);
387 
388  auto now = monotonic_time_s();
389  auto next_update = now + m_update_interval;
390  while (true) {
391  now = monotonic_time_s();
392  auto remaining = next_update - now;
393  auto rval = sleep(remaining);
394  if (rval > 0) {
395  // Woke up early due to a signal; re-run prior logic.
396  continue;
397  }
398  next_update = monotonic_time_s() + m_update_interval;
399  struct stat statbuf;
400  if (-1 == stat(myself->m_mapfile.c_str(), &statbuf)) {
401  myself->m_edest->Emsg("XrdVomsMapfile", errno, "Error checking the mapfile",
402  myself->m_mapfile.c_str());
403  myself->m_mapfile_ctime.tv_sec = 0;
404  myself->m_mapfile_ctime.tv_nsec = 0;
405  myself->m_is_valid = false;
406  continue;
407  }
408  // Use ctime here as it is solely controlled by the OS (unlike mtime,
409  // which can be manipulated by userspace and potentially not change
410  // when updated - rsync, tar, and rpm, for example, all preserve mtime).
411  // ctime also will also be updated appropriately for overwrites/renames,
412  // allowing us to detect those changes as well.
413  //
414  if ((myself->m_mapfile_ctime.tv_sec == statbuf.st_ctim.tv_sec) &&
415  (myself->m_mapfile_ctime.tv_nsec == statbuf.st_ctim.tv_nsec))
416  {
417  myself->m_edest->Log(LogMask::Debug, "Maintenance", "Not reloading VOMS mapfile; "
418  "no changes detected.");
419  continue;
420  }
421  memcpy(&myself->m_mapfile_ctime, &statbuf.st_ctim, sizeof(decltype(statbuf.st_ctim)));
422 
423  myself->m_edest->Log(LogMask::Debug, "Maintenance", "Reloading VOMS mapfile now");
424  if ( !(myself->m_is_valid = myself->ParseMapfile(myself->m_mapfile)) ) {
425  myself->m_edest->Log(LogMask::Error, "Maintenance", "Failed to reload VOMS mapfile");
426  }
427  }
428  return nullptr;
429 }
#define open
Definition: XrdPosix.hh:76
#define stat(a, b)
Definition: XrdPosix.hh:101
bool Debug
void getline(uchar *buff, int blen)
@ Error
#define VOMS_MAP_FAILED
static bool Import(const char *var, char *&val)
Definition: XrdOucEnv.cc:204
char * GetMyFirstWord(int lowcase=0)
char * GetWord(int lowcase=0)
int Attach(int FileDescriptor, int bsz=2047)
const char * c_str() const
void assign(const char *s, int j, int k=-1)
int matches(const char *s, char wch=' *')
int length() const
int tokenize(XrdOucString &tok, int from, char del=':')
XrdSecAttr * Get(const void *sigkey)
char * vorg
Entity's virtual organization(s)
Definition: XrdSecEntity.hh:71
XrdSecEntityAttr * eaAPI
non-const API to attributes
Definition: XrdSecEntity.hh:92
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
int Emsg(const char *esfx, int ecode, const char *text1, const char *text2=0)
Definition: XrdSysError.cc:95
void setMsgMask(int mask)
Definition: XrdSysError.hh:154
int getMsgMask()
Definition: XrdSysError.hh:156
void Log(int mask, const char *esfx, const char *text1, const char *text2=0, const char *text3=0)
Definition: XrdSysError.hh:133
static int Run(pthread_t *, void *(*proc)(void *), void *arg, int opts=0, const char *desc=0)
static XrdVomsMapfile * Get()
static XrdVomsMapfile * Configure(XrdSysError *)
virtual ~XrdVomsMapfile()
int Apply(XrdSecEntity &)
@ Warning