2 * The Shibboleth License, Version 1.
4 * University Corporation for Advanced Internet Development, Inc.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are met:
11 * Redistributions of source code must retain the above copyright notice, this
12 * list of conditions and the following disclaimer.
14 * Redistributions in binary form must reproduce the above copyright notice,
15 * this list of conditions and the following disclaimer in the documentation
16 * and/or other materials provided with the distribution, if any, must include
17 * the following acknowledgment: "This product includes software developed by
18 * the University Corporation for Advanced Internet Development
19 * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement
20 * may appear in the software itself, if and wherever such third-party
21 * acknowledgments normally appear.
23 * Neither the name of Shibboleth nor the names of its contributors, nor
24 * Internet2, nor the University Corporation for Advanced Internet Development,
25 * Inc., nor UCAID may be used to endorse or promote products derived from this
26 * software without specific prior written permission. For written permission,
27 * please contact shibboleth@shibboleth.org
29 * Products derived from this software may not be called Shibboleth, Internet2,
30 * UCAID, or the University Corporation for Advanced Internet Development, nor
31 * may Shibboleth appear in their name, without prior written permission of the
32 * University Corporation for Advanced Internet Development.
35 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
36 * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
38 * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK
39 * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE.
40 * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
41 * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT,
42 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
43 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
44 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
47 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
51 * shib-ini.h -- INI file handling
53 * Created By: Derek Atkins <derek@ihtfp.com>
58 // eventually we might be able to support autoconf via cygwin...
59 #if defined (_MSC_VER) || defined(__BORLANDC__)
60 # include "config_win32.h"
66 # define SHIBTARGET_EXPORTS __declspec(dllexport)
69 #include "shib-target.h"
70 #include <shib/shib-threads.h>
77 #include <sys/types.h>
80 #include <log4cpp/Category.hh>
83 using namespace shibboleth;
84 using namespace shibtarget;
86 class HeaderIterator : public shibtarget::ShibINI::Iterator {
88 HeaderIterator (ShibINIPriv* ini);
91 const string* begin();
95 map<string, map<string,string> >::const_iterator iter;
99 class TagIterator : public ShibINI::Iterator {
101 TagIterator (ShibINIPriv* ini, const string& header);
104 const string* begin();
105 const string* next();
109 map<string,string>::const_iterator iter;
113 class shibtarget::ShibINIPriv {
116 ~ShibINIPriv() { delete rwlock; }
117 log4cpp::Category *log;
119 map<string, map<string, string> > table;
123 unsigned long modtime;
125 unsigned long iterators;
128 bool exists(const std::string& header);
129 bool exists(const std::string& header, const std::string& tag);
132 ShibINIPriv::ShibINIPriv()
134 string ctx = "shibtarget.ShibINI";
135 log = &(log4cpp::Category::getInstance(ctx));
136 rwlock = RWLock::create();
141 static void trimline (string& s)
143 int end = s.size() - 1, start = 0;
145 // Trim stuff on right.
146 while (end > 0 && !isgraph(s[end])) end--;
148 // Trim stuff on left.
149 while (start < end && !isgraph(s[start])) start++;
151 // Modify the string.
152 s = s.substr(start, end - start + 1);
155 static void to_lowercase (string& s)
157 for (int i = 0, sz = s.size(); i < sz; i++)
158 s[i] = tolower(s[i]);
161 ShibINI::~ShibINI() {
165 void ShibINI::init (string& f, bool case_sensitive)
167 m_priv = new ShibINIPriv();
169 m_priv->cs = case_sensitive;
170 m_priv->log->info ("initializing INI file: %s (sensitive=%s)", f.c_str(),
171 (case_sensitive ? "true" : "false"));
173 ReadLock lock(m_priv->rwlock);
178 // Must be called holding the ReadLock.
180 void ShibINI::refresh(void)
182 saml::NDC ndc("refresh");
184 // check if we need to refresh
186 struct _stat stat_buf;
187 if (_stat (m_priv->file.c_str(), &stat_buf) < 0)
189 struct stat stat_buf;
190 if (stat (m_priv->file.c_str(), &stat_buf) < 0)
192 m_priv->log->error("stat failed: %s", m_priv->file.c_str());
195 m_priv->log->info("refresh: last modtime at %d; file is %d; iters: %d",
196 m_priv->modtime, stat_buf.st_mtime, m_priv->iterators);
199 if (m_priv->modtime >= stat_buf.st_mtime || m_priv->iterators > 0)
202 // Release the read lock -- grab the write lock. Don't worry if
203 // this is non-atomic -- we'll recheck the status.
204 m_priv->rwlock->unlock();
205 m_priv->rwlock->wrlock();
207 // Recheck the modtime
208 if (m_priv->modtime >= stat_buf.st_mtime) {
209 // Yep, another thread got to it. We can exit now... Release
210 // the write lock and reaquire the read-lock.
212 m_priv->rwlock->unlock();
213 m_priv->rwlock->rdlock();
217 // Ok, we've got the write lock. Let's update our state.
219 m_priv->modtime = stat_buf.st_mtime;
221 // clear the existing maps
222 m_priv->table.clear();
224 m_priv->log->info("reading %s", m_priv->file.c_str());
229 ifstream infile (m_priv->file.c_str());
231 m_priv->log->warn("cannot open file: %s", m_priv->file.c_str());
232 m_priv->rwlock->unlock();
233 m_priv->rwlock->rdlock();
237 const int MAXLEN = 1024;
238 char linebuffer[MAXLEN];
239 string current_header;
240 bool have_header = false;
243 infile.getline (linebuffer, MAXLEN);
244 string line (linebuffer);
246 if (line[0] == '#') continue;
249 if (line.size() <= 1) continue;
251 if (line[0] == '[') {
255 m_priv->log->info("Found what appears to be a header line");
260 // find the end of the header
261 int endpos = line.find (']');
262 if (endpos == line.npos) {
264 m_priv->log->info("Weird: no end found.. punting");
266 continue; // HUH? No end?
270 current_header = line.substr (1, endpos-1);
271 trimline (current_header);
273 if (!m_priv->cs) to_lowercase (current_header);
275 m_priv->table[current_header] = map<string,string>();
278 m_priv->log->info("current header: \"%s\"", current_header.c_str());
281 } else if (have_header) {
285 m_priv->log->info("Found what appears to be a tag line");
289 int mid = line.find ('=');
291 if (mid == line.npos) {
293 m_priv->log->info("Weird: no '=' found.. punting");
295 continue; // Can't find the value's setting
298 tag = line.substr (0,mid);
299 setting = line.substr (mid+1, line.size()-mid);
304 if (!m_priv->cs) to_lowercase (tag);
306 // If it already exists, log an error and do not save it
307 if (m_priv->exists (current_header, tag))
308 m_priv->log->error("Duplicate tag found in section %s: \"%s\"",
309 current_header.c_str(), tag.c_str());
311 (m_priv->table[current_header])[tag] = setting;
314 m_priv->log->info("new tag: \"%s\" = \"%s\"",
315 tag.c_str(), setting.c_str());
320 } // until the file ends
323 // In case there are exceptions.
326 // Now release the write lock and reaquire the read lock
327 m_priv->rwlock->unlock();
328 m_priv->rwlock->rdlock();
331 const std::string ShibINI::get (const string& header, const string& tag)
333 ReadLock rwlock(m_priv->rwlock);
336 static string empty = "";
346 if (!m_priv->exists(h)) return empty;
348 map<string,string>::const_iterator i = m_priv->table[h].find(t);
349 if (i == m_priv->table[h].end())
354 bool ShibINIPriv::exists(const std::string& header)
357 if (!cs) to_lowercase (h);
359 return (table.find(h) != table.end());
362 bool ShibINI::exists(const std::string& header)
364 ReadLock rwlock(m_priv->rwlock);
367 return m_priv->exists(header);
370 bool ShibINIPriv::exists(const std::string& header, const std::string& tag)
380 if (!exists(h)) return false;
381 return (table[h].find(t) != table[h].end());
384 bool ShibINI::exists(const std::string& header, const std::string& tag)
386 ReadLock rwlock(m_priv->rwlock);
389 return m_priv->exists(header, tag);
392 bool ShibINI::get_tag (string& header, string& tag, bool try_general, string* result)
394 if (!result) return false;
396 m_priv->rwlock->rdlock();
398 m_priv->rwlock->unlock();
400 if (m_priv->exists (header, tag)) {
401 *result = get (header, tag);
404 if (try_general && exists (SHIBTARGET_GENERAL, tag)) {
405 *result = get (SHIBTARGET_GENERAL, tag);
412 void ShibINI::dump (ostream& os)
414 ReadLock rwlock(m_priv->rwlock);
417 os << "File: " << m_priv->file << "\n";
418 os << "Case-Sensitive: " << ( m_priv->cs ? "Yes\n" : "No\n" );
419 os << "File Entries:\n";
421 for (map<string, map<string, string> >::const_iterator i = m_priv->table.begin();
422 i != m_priv->table.end(); i++) {
424 os << "[" << i->first << "]\n";
426 for (map<string,string>::const_iterator j=i->second.begin();
427 j != i->second.end(); j++) {
429 os << " " << j->first << " = " << j->second << "\n";
436 ShibINI::Iterator* ShibINI::header_iterator()
438 ReadLock rwlock(m_priv->rwlock);
440 HeaderIterator* iter = new HeaderIterator(m_priv);
441 return (ShibINI::Iterator*) iter;
444 ShibINI::Iterator* ShibINI::tag_iterator(const std::string& header)
446 ReadLock rwlock(m_priv->rwlock);
448 TagIterator* iter = new TagIterator(m_priv, header);
449 return (ShibINI::Iterator*) iter;
453 // XXX: FIXME: there may be a race condition in the iterators if a
454 // caller holds an active Iterator, the underlying file changes, and
455 // then calls one of the get() routines. It's possible the iterator
456 // may screw up -- I don't know whether the iterator actually depends
457 // on the underlying infrastructure or not.
460 HeaderIterator::HeaderIterator (ShibINIPriv* inip)
462 saml::NDC ndc("HeaderIterator");
465 ini->rwlock->rdlock();
467 ini->log->debug("iterators: %d", ini->iterators);
470 HeaderIterator::~HeaderIterator ()
472 saml::NDC ndc("~HeaderIterator");
474 ini->rwlock->unlock();
475 ini->log->debug("iterators: %d", ini->iterators);
478 const string* HeaderIterator::begin ()
480 iter = ini->table.begin();
481 if (iter == ini->table.end()) {
489 const string* HeaderIterator::next ()
494 if (iter == ini->table.end()) {
501 TagIterator::TagIterator (ShibINIPriv* inip, const string& headerp)
504 saml::NDC ndc("TagIterator");
507 ini->rwlock->rdlock();
509 ini->log->debug("iterators: %d", ini->iterators);
512 TagIterator::~TagIterator ()
514 saml::NDC ndc("~TagIterator");
516 ini->rwlock->unlock();
517 ini->log->debug("iterators: %d", ini->iterators);
520 const string* TagIterator::begin ()
522 iter = ini->table[header].begin();
523 if (iter == ini->table[header].end()) {
531 const string* TagIterator::next ()
536 if (iter == ini->table[header].end()) {
543 bool ShibINI::boolean(string& value)
545 const char* v = value.c_str();
546 #ifdef HAVE_STRCASECMP
547 if (!strncasecmp (v, "on", 2) || !strncasecmp (v, "true", 4) || !strncmp(v, "1", 1))
550 if (!strnicmp (v, "on", 2) || !strnicmp (v, "true", 4) || !strncmp(v, "1", 1))
556 ShibINI::Iterator::~Iterator() {}