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
45bool XrdVomsMapfile::tried_configure = false;
46std::unique_ptr<XrdVomsMapfile> XrdVomsMapfile::mapper;
47
48namespace {
49
50std::string
51PathToString(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
62uint64_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
71XrdVomsMapfile::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
99bool
100XrdVomsMapfile::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
122bool
123XrdVomsMapfile::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
196std::string
197XrdVomsMapfile::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
218bool
219XrdVomsMapfile::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
238std::vector<std::string>
239XrdVomsMapfile::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
256int
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
379void *
380XrdVomsMapfile::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}
#define open
Definition XrdPosix.hh:71
#define stat(a, b)
Definition XrdPosix.hh:96
#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)
void Log(int mask, const char *esfx, const char *text1, const char *text2=0, const char *text3=0)
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 &)