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 bool XrdVomsMapfile::tried_configure = false;
46 std::unique_ptr<XrdVomsMapfile> XrdVomsMapfile::mapper;
47 
48 namespace {
49 
50 std::string
51 PathToString(const std::vector<std::string> &path)
52 {
53  if (path.empty()) {return "/";}
54  std::stringstream ss;
55  for (const auto &entry : path) {
56  ss << "/" << entry;
57  }
58 
59  return ss.str();
60 }
61 
62 uint64_t monotonic_time_s() {
63  struct timespec tp;
64  clock_gettime(CLOCK_MONOTONIC, &tp);
65  return tp.tv_sec + (tp.tv_nsec >= 500000000);
66 }
67 
68 }
69 
70 
71 XrdVomsMapfile::XrdVomsMapfile(XrdSysError *erp,
72  const std::string &mapfile)
73  : m_mapfile(mapfile), m_edest(erp)
74 {
75  struct stat statbuf;
76  if (-1 == stat(m_mapfile.c_str(), &statbuf)) {
77  m_edest->Emsg("XrdVomsMapfile", errno, "Error checking the mapfile", m_mapfile.c_str());
78  return;
79  }
80  memcpy(&m_mapfile_ctime, &statbuf.st_ctim, sizeof(decltype(m_mapfile_ctime)));
81 
82  if (!ParseMapfile(m_mapfile)) {return;}
83 
84  pthread_t tid;
85  auto rc = XrdSysThread::Run(&tid, XrdVomsMapfile::MaintenanceThread,
86  static_cast<void*>(this), 0, "VOMS Mapfile refresh");
87  if (rc) {
88  m_edest->Emsg("XrdVomsMapfile", "Failed to launch VOMS mapfile monitoring thread");
89  return;
90  }
91  m_is_valid = true;
92 }
93 
94 
96 {}
97 
98 
99 bool
100 XrdVomsMapfile::ParseMapfile(const std::string &mapfile)
101 {
102  std::ifstream fstr(mapfile);
103  if (!fstr.is_open()) {
104  m_edest->Emsg("ParseMapfile", "Failed to open file", mapfile.c_str(), strerror(errno));
105  return false;
106  }
107  std::shared_ptr<std::vector<MapfileEntry>> entries(new std::vector<MapfileEntry>());
108  for (std::string line; std::getline(fstr, line); ) {
109  MapfileEntry entry;
110  if (ParseLine(line, entry.m_path, entry.m_target) && !entry.m_path.empty()) {
111  if (m_edest->getMsgMask() & LogMask::Debug) {
112  m_edest->Log(LogMask::Debug, "ParseMapfile", PathToString(entry.m_path).c_str(), "->", entry.m_target.c_str());
113  }
114  entries->emplace_back(entry);
115  }
116  }
117  m_entries = entries;
118  return true;
119 }
120 
121 
122 bool
123 XrdVomsMapfile::ParseLine(const std::string &line, std::vector<std::string> &entry, std::string &target)
124 {
125  bool began_entry = false;
126  bool finish_entry = false;
127  bool began_target = false;
128  std::string element;
129  element.reserve(16);
130  for (size_t idx=0; idx<line.size(); idx++) {
131  auto txt = line[idx];
132  if (!began_entry && !finish_entry) {
133  if (txt == '#') {return false;}
134  else if (txt == '"') {began_entry = true;}
135  else if (!isspace(txt)) {return false;}
136  continue;
137  } else if (began_entry && !finish_entry) {
138  if (txt == '\\') {
139  if (idx + 1 == line.size()) {return false;}
140  idx++;
141  auto escaped_char = line[idx];
142  switch (escaped_char) {
143  case '\'':
144  element += "'";
145  break;
146  case '\"':
147  element += "\"";
148  break;
149  case '/':
150  element += "/";
151  break;
152  case 'f':
153  element += "\f";
154  break;
155  case 'n':
156  element += "\n";
157  break;
158  case 'r':
159  element += "\r";
160  break;
161  case 't':
162  element += "\t";
163  break;
164  default:
165  return false;
166  };
167  } else if (txt == '"') {
168  if (!element.empty()) entry.push_back(element);
169  finish_entry = true;
170  } else if (txt == '/') {
171  if (!element.empty()) entry.push_back(element);
172  element.clear();
173  } else if (isprint(txt)) {
174  element += txt;
175  } else {
176  return false;
177  }
178  } else if (!began_target) {
179  if (isspace(txt)) {continue;}
180  began_target = true;
181  }
182  if (began_target) {
183  if (isprint(txt)) {
184  target += txt;
185  } else if (isspace(txt)) {
186  return true;
187  } else {
188  return false;
189  }
190  }
191  }
192  return true;
193 }
194 
195 
196 std::string
197 XrdVomsMapfile::Map(const std::vector<std::string> &fqan)
198 {
199  decltype(m_entries) entries = m_entries;
200  if (!entries) {return "";}
201 
202  if (m_edest && (m_edest->getMsgMask() & LogMask::Debug)) {
203  m_edest->Log(LogMask::Debug, "VOMSMapfile", "Mapping VOMS FQAN", PathToString(fqan).c_str());
204  }
205 
206  for (const auto &entry : *entries) {
207  if (Compare(entry, fqan)) {
208  if (m_edest && (m_edest->getMsgMask() & LogMask::Debug)) {
209  m_edest->Log(LogMask::Debug, "VOMSMapfile", "Mapped FQAN to target", entry.m_target.c_str());
210  }
211  return entry.m_target;
212  }
213  }
214  return "";
215 }
216 
217 
218 bool
219 XrdVomsMapfile::Compare(const MapfileEntry &entry, const std::vector<std::string> &fqan)
220 {
221  if (entry.m_path.empty()) {return false;}
222 
223  // A more specific mapfile entry cannot match a generic FQAN
224  if (fqan.size() < entry.m_path.size()) {return false;}
225 
226  XrdOucString fqan_element;
227  for (size_t idx=0; idx<entry.m_path.size(); idx++) {
228  fqan_element.assign(fqan[idx].c_str(), 0);
229  const auto &path_element = entry.m_path[idx];
230  if (!fqan_element.matches(path_element.c_str())) {return false;}
231  }
232  if (fqan.size() == entry.m_path.size()) {return true;}
233  if (entry.m_path.back() == "*") {return true;}
234  return false;
235 }
236 
237 
238 std::vector<std::string>
239 XrdVomsMapfile::MakePath(const XrdOucString &group)
240 {
241  int from = 0;
242  XrdOucString entry;
243  std::vector<std::string> path;
244  path.reserve(4);
245  // The const'ness of the tokenize method as declared is incorrect; we use
246  // const_cast here to avoid fixing the XrdOucString header (which would break
247  // the ABI).
248  while ((from = const_cast<XrdOucString&>(group).tokenize(entry, from, '/')) != -1) {
249  if (entry.length() == 0) continue;
250  path.emplace_back(entry.c_str());
251  }
252  return path;
253 }
254 
255 
256 int
258 {
259  // In current use cases, the gridmap results take precedence over the voms-mapfile
260  // results. However, the grid mapfile plugins often will populate the name attribute
261  // with a reasonable default (DN or DN hash) if the mapping fails, meaning we can't
262  // simply look at entity.name; instead, we look at an extended attribute that is only
263  // set when the mapfile is used to generate the name.
264  std::string gridmap_name;
265  auto gridmap_success = entity.eaAPI->Get("gridmap.name", gridmap_name);
266  if (gridmap_success && gridmap_name == "1") {
267  return 0;
268  }
269 
270  int from_vorg = 0, from_role = 0, from_grps = 0;
271  XrdOucString vorg = entity.vorg, entry_vorg;
272  XrdOucString role = entity.role ? entity.role : "", entry_role = "NULL";
273  XrdOucString grps = entity.grps, entry_grps;
274  if (m_edest) m_edest->Log(LogMask::Debug, "VOMSMapfile", "Applying VOMS mapfile to incoming credential");
275  while (((from_vorg = vorg.tokenize(entry_vorg, from_vorg, ' ')) != -1) &&
276  ((role == "") || (from_role = role.tokenize(entry_role, from_role, ' ')) != -1) &&
277  ((from_grps = grps.tokenize(entry_grps, from_grps, ' ')) != -1))
278  {
279  auto fqan = MakePath(entry_grps);
280  if (fqan.empty()) {continue;}
281 
282  // By convention, the root group should be the same as the VO name; however,
283  // the VOMS mapfile makes this assumption. To be secure, enforce it.
284  if (strcmp(fqan[0].c_str(), entry_vorg.c_str())) {continue;}
285 
286  fqan.emplace_back(std::string("Role=") + entry_role.c_str());
287  fqan.emplace_back("Capability=NULL");
288  std::string username;
289  if (!(username = Map(fqan)).empty()) {
290  if (entity.name) {free(entity.name);}
291  entity.name = strdup(username.c_str());
292  break;
293  }
294  }
295 
296  return 0;
297 }
298 
299 
302 {
303  return mapper.get();
304 }
305 
306 
309 {
310  if (tried_configure) {
311  auto result = mapper.get();
312  if (result) {
313  result->SetErrorStream(erp);
314  }
315  return result;
316  }
317 
318  tried_configure = true;
319 
320  // Set default mask for logging.
321  if (erp) erp->setMsgMask(LogMask::Error | LogMask::Warning);
322 
323  char *config_filename = nullptr;
324  if (!XrdOucEnv::Import("XRDCONFIGFN", config_filename)) {
325  return VOMS_MAP_FAILED;
326  }
327  XrdOucEnv myEnv;
328  XrdOucStream stream(erp, getenv("XRDINSTANCE"), &myEnv, "=====> ");
329 
330  int cfg_fd;
331  if ((cfg_fd = open(config_filename, O_RDONLY, 0)) < 0) {
332  if (erp) erp->Emsg("Config", errno, "open config file", config_filename);
333  return VOMS_MAP_FAILED;
334  }
335  stream.Attach(cfg_fd);
336  char *var;
337  std::string map_filename;
338  while ((var = stream.GetMyFirstWord())) {
339  if (!strcmp(var, "voms.mapfile")) {
340  auto val = stream.GetWord();
341  if (!val || !val[0]) {
342  if (erp) erp->Emsg("Config", "VOMS mapfile not specified");
343  return VOMS_MAP_FAILED;
344  }
345  map_filename = val;
346  } else if (!strcmp(var, "voms.trace")) {
347  auto val = stream.GetWord();
348  if (!val || !val[0]) {
349  if (erp) erp->Emsg("Config", "VOMS logging level not specified");
350  return VOMS_MAP_FAILED;
351  }
352  if (erp) erp->setMsgMask(0);
353  if (erp) do {
354  if (!strcmp(val, "all")) {erp->setMsgMask(erp->getMsgMask() | LogMask::All);}
355  else if (!strcmp(val, "error")) {erp->setMsgMask(erp->getMsgMask() | LogMask::Error);}
356  else if (!strcmp(val, "warning")) {erp->setMsgMask(erp->getMsgMask() | LogMask::Warning);}
357  else if (!strcmp(val, "info")) {erp->setMsgMask(erp->getMsgMask() | LogMask::Info);}
358  else if (!strcmp(val, "debug")) {erp->setMsgMask(erp->getMsgMask() | LogMask::Debug);}
359  else if (!strcmp(val, "none")) {erp->setMsgMask(0);}
360  else {erp->Emsg("Config", "voms.trace encountered an unknown directive:", val);}
361  val = stream.GetWord();
362  } while (val);
363  }
364  }
365 
366  if (!map_filename.empty()) {
367  if (erp) erp->Emsg("Config", "Will initialize VOMS mapfile", map_filename.c_str());
368  mapper.reset(new XrdVomsMapfile(erp, map_filename));
369  if (!mapper->IsValid()) {
370  mapper.reset(nullptr);
371  return VOMS_MAP_FAILED;
372  }
373  }
374 
375  return mapper.get();
376 }
377 
378 
379 void *
380 XrdVomsMapfile::MaintenanceThread(void *myself_raw)
381 {
382  auto myself = static_cast<XrdVomsMapfile*>(myself_raw);
383 
384  auto now = monotonic_time_s();
385  auto next_update = now + m_update_interval;
386  while (true) {
387  now = monotonic_time_s();
388  auto remaining = next_update - now;
389  auto rval = sleep(remaining);
390  if (rval > 0) {
391  // Woke up early due to a signal; re-run prior logic.
392  continue;
393  }
394  next_update = monotonic_time_s() + m_update_interval;
395  struct stat statbuf;
396  if (-1 == stat(myself->m_mapfile.c_str(), &statbuf)) {
397  myself->m_edest->Emsg("XrdVomsMapfile", errno, "Error checking the mapfile",
398  myself->m_mapfile.c_str());
399  myself->m_mapfile_ctime.tv_sec = 0;
400  myself->m_mapfile_ctime.tv_nsec = 0;
401  myself->m_is_valid = false;
402  continue;
403  }
404  // Use ctime here as it is solely controlled by the OS (unlike mtime,
405  // which can be manipulated by userspace and potentially not change
406  // when updated - rsync, tar, and rpm, for example, all preserve mtime).
407  // ctime also will also be updated appropriately for overwrites/renames,
408  // allowing us to detect those changes as well.
409  //
410  if ((myself->m_mapfile_ctime.tv_sec == statbuf.st_ctim.tv_sec) &&
411  (myself->m_mapfile_ctime.tv_nsec == statbuf.st_ctim.tv_nsec))
412  {
413  myself->m_edest->Log(LogMask::Debug, "Maintenance", "Not reloading VOMS mapfile; "
414  "no changes detected.");
415  continue;
416  }
417  memcpy(&myself->m_mapfile_ctime, &statbuf.st_ctim, sizeof(decltype(statbuf.st_ctim)));
418 
419  myself->m_edest->Log(LogMask::Debug, "Maintenance", "Reloading VOMS mapfile now");
420  if ( !(myself->m_is_valid = myself->ParseMapfile(myself->m_mapfile)) ) {
421  myself->m_edest->Log(LogMask::Error, "Maintenance", "Failed to reload VOMS mapfile");
422  }
423  }
424  return nullptr;
425 }
@ Info
@ Warning
int stat(const char *path, struct stat *buf)
int open(const char *path, int oflag,...)
void getline(uchar *buff, int blen)
#define VOMS_MAP_FAILED
static bool Import(const char *var, char *&val)
Definition: XrdOucEnv.cc:222
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 &)