XRootD
Loading...
Searching...
No Matches
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"
33#include "XrdSys/XrdSysError.hh"
34#include "XrdSys/XrdSysFD.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
49bool XrdVomsMapfile::tried_configure = false;
50std::unique_ptr<XrdVomsMapfile> XrdVomsMapfile::mapper;
51
52namespace {
53
54std::string
55PathToString(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
66uint64_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
75XrdVomsMapfile::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
101
102
103bool
104XrdVomsMapfile::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
126bool
127XrdVomsMapfile::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
200std::string
201XrdVomsMapfile::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
222bool
223XrdVomsMapfile::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
242std::vector<std::string>
243XrdVomsMapfile::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
260int
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
383void *
384XrdVomsMapfile::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
if(Avsz)
#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)
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=':')
const char * c_str() const
XrdSecAttr * Get(const void *sigkey)
char * vorg
Entity's virtual organization(s)
XrdSecEntityAttr * eaAPI
non-const API to attributes
char * grps
Entity's group name(s)
char * name
Entity's name.
char * role
Entity's role(s)
int Emsg(const char *esfx, int ecode, const char *text1, const char *text2=0)
void setMsgMask(int mask)
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 &)