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>
60 #include <shib/shib-threads.h>
67 #include <sys/types.h>
70 class HeaderIterator : public shibtarget::ShibINI::Iterator {
72 HeaderIterator (ShibINIPriv* ini);
75 const string* begin();
79 map<string, map<string,string> >::const_iterator iter;
83 class TagIterator : public ShibINI::Iterator {
85 TagIterator (ShibINIPriv* ini, const string& header);
88 const string* begin();
93 map<string,string>::const_iterator iter;
97 class shibtarget::ShibINIPriv {
100 ~ShibINIPriv() { delete rwlock; }
101 log4cpp::Category *log;
103 map<string, map<string, string> > table;
107 unsigned long modtime;
109 unsigned long iterators;
112 bool exists(const std::string& header);
113 bool exists(const std::string& header, const std::string& tag);
116 ShibINIPriv::ShibINIPriv()
118 string ctx = "shibtarget.ShibINI";
119 log = &(log4cpp::Category::getInstance(ctx));
120 rwlock = RWLock::create();
125 static void trimline (string& s)
127 int end = s.size() - 1, start = 0;
129 // Trim stuff on right.
130 while (end > 0 && !isgraph(s[end])) end--;
132 // Trim stuff on left.
133 while (start < end && !isgraph(s[start])) start++;
135 // Modify the string.
136 s = s.substr(start, end - start + 1);
139 static void to_lowercase (string& s)
141 for (int i = 0, sz = s.size(); i < sz; i++)
142 s[i] = tolower(s[i]);
145 ShibINI::~ShibINI() {
149 void ShibINI::init (string& f, bool case_sensitive)
151 m_priv = new ShibINIPriv();
153 m_priv->cs = case_sensitive;
154 m_priv->log->info ("initializing INI file: %s (sensitive=%s)", f.c_str(),
155 (case_sensitive ? "true" : "false"));
157 ReadLock lock(m_priv->rwlock);
162 // Must be called holding the ReadLock.
164 void ShibINI::refresh(void)
166 saml::NDC ndc("refresh");
168 // check if we need to refresh
170 struct _stat stat_buf;
171 if (_stat (m_priv->file.c_str(), &stat_buf) < 0)
173 struct stat stat_buf;
174 if (stat (m_priv->file.c_str(), &stat_buf) < 0)
176 m_priv->log->error("stat failed: %s", m_priv->file.c_str());
179 m_priv->log->info("refresh: last modtime at %d; file is %d; iters: %d",
180 m_priv->modtime, stat_buf.st_mtime, m_priv->iterators);
183 if (m_priv->modtime >= stat_buf.st_mtime || m_priv->iterators > 0)
186 // Release the read lock -- grab the write lock. Don't worry if
187 // this is non-atomic -- we'll recheck the status.
188 m_priv->rwlock->unlock();
189 m_priv->rwlock->wrlock();
191 // Recheck the modtime
192 if (m_priv->modtime >= stat_buf.st_mtime) {
193 // Yep, another thread got to it. We can exit now... Release
194 // the write lock and reaquire the read-lock.
196 m_priv->rwlock->unlock();
197 m_priv->rwlock->rdlock();
201 // Ok, we've got the write lock. Let's update our state.
203 m_priv->modtime = stat_buf.st_mtime;
205 // clear the existing maps
206 m_priv->table.clear();
208 m_priv->log->info("reading %s", m_priv->file.c_str());
213 ifstream infile (m_priv->file.c_str());
215 m_priv->log->warn("cannot open file: %s", m_priv->file.c_str());
216 m_priv->rwlock->unlock();
217 m_priv->rwlock->rdlock();
221 const int MAXLEN = 1024;
222 char linebuffer[MAXLEN];
223 string current_header;
224 bool have_header = false;
227 infile.getline (linebuffer, MAXLEN);
228 string line (linebuffer);
230 if (line[0] == '#') continue;
233 if (line.size() <= 1) continue;
235 if (line[0] == '[') {
239 m_priv->log->info("Found what appears to be a header line");
244 // find the end of the header
245 int endpos = line.find (']');
246 if (endpos == line.npos) {
248 m_priv->log->info("Weird: no end found.. punting");
250 continue; // HUH? No end?
254 current_header = line.substr (1, endpos-1);
255 trimline (current_header);
257 if (!m_priv->cs) to_lowercase (current_header);
259 m_priv->table[current_header] = map<string,string>();
262 m_priv->log->info("current header: \"%s\"", current_header.c_str());
265 } else if (have_header) {
269 m_priv->log->info("Found what appears to be a tag line");
273 int mid = line.find ('=');
275 if (mid == line.npos) {
277 m_priv->log->info("Weird: no '=' found.. punting");
279 continue; // Can't find the value's setting
282 tag = line.substr (0,mid);
283 setting = line.substr (mid+1, line.size()-mid);
288 if (!m_priv->cs) to_lowercase (tag);
290 // If it already exists, log an error and do not save it
291 if (m_priv->exists (current_header, tag))
292 m_priv->log->error("Duplicate tag found in section %s: \"%s\"",
293 current_header.c_str(), tag.c_str());
295 (m_priv->table[current_header])[tag] = setting;
298 m_priv->log->info("new tag: \"%s\" = \"%s\"",
299 tag.c_str(), setting.c_str());
304 } // until the file ends
307 // In case there are exceptions.
310 // Now release the write lock and reaquire the read lock
311 m_priv->rwlock->unlock();
312 m_priv->rwlock->rdlock();
315 const std::string ShibINI::get (const string& header, const string& tag)
317 ReadLock rwlock(m_priv->rwlock);
320 static string empty = "";
330 if (!m_priv->exists(h)) return empty;
332 map<string,string>::const_iterator i = m_priv->table[h].find(t);
333 if (i == m_priv->table[h].end())
338 bool ShibINIPriv::exists(const std::string& header)
341 if (!cs) to_lowercase (h);
343 return (table.find(h) != table.end());
346 bool ShibINI::exists(const std::string& header)
348 ReadLock rwlock(m_priv->rwlock);
351 return m_priv->exists(header);
354 bool ShibINIPriv::exists(const std::string& header, const std::string& tag)
364 if (!exists(h)) return false;
365 return (table[h].find(t) != table[h].end());
368 bool ShibINI::exists(const std::string& header, const std::string& tag)
370 ReadLock rwlock(m_priv->rwlock);
373 return m_priv->exists(header, tag);
376 bool ShibINI::get_tag (string& header, string& tag, bool try_general, string* result)
378 if (!result) return false;
380 m_priv->rwlock->rdlock();
382 m_priv->rwlock->unlock();
384 if (m_priv->exists (header, tag)) {
385 *result = get (header, tag);
388 if (try_general && exists (SHIBTARGET_GENERAL, tag)) {
389 *result = get (SHIBTARGET_GENERAL, tag);
396 void ShibINI::dump (ostream& os)
398 ReadLock rwlock(m_priv->rwlock);
401 os << "File: " << m_priv->file << "\n";
402 os << "Case-Sensitive: " << ( m_priv->cs ? "Yes\n" : "No\n" );
403 os << "File Entries:\n";
405 for (map<string, map<string, string> >::const_iterator i = m_priv->table.begin();
406 i != m_priv->table.end(); i++) {
408 os << "[" << i->first << "]\n";
410 for (map<string,string>::const_iterator j=i->second.begin();
411 j != i->second.end(); j++) {
413 os << " " << j->first << " = " << j->second << "\n";
420 ShibINI::Iterator* ShibINI::header_iterator()
422 ReadLock rwlock(m_priv->rwlock);
424 HeaderIterator* iter = new HeaderIterator(m_priv);
425 return (ShibINI::Iterator*) iter;
428 ShibINI::Iterator* ShibINI::tag_iterator(const std::string& header)
430 ReadLock rwlock(m_priv->rwlock);
432 TagIterator* iter = new TagIterator(m_priv, header);
433 return (ShibINI::Iterator*) iter;
437 // XXX: FIXME: there may be a race condition in the iterators if a
438 // caller holds an active Iterator, the underlying file changes, and
439 // then calls one of the get() routines. It's possible the iterator
440 // may screw up -- I don't know whether the iterator actually depends
441 // on the underlying infrastructure or not.
444 HeaderIterator::HeaderIterator (ShibINIPriv* inip)
446 saml::NDC ndc("HeaderIterator");
449 ini->rwlock->rdlock();
451 ini->log->debug("iterators: %d", ini->iterators);
454 HeaderIterator::~HeaderIterator ()
456 saml::NDC ndc("~HeaderIterator");
458 ini->rwlock->unlock();
459 ini->log->debug("iterators: %d", ini->iterators);
462 const string* HeaderIterator::begin ()
464 iter = ini->table.begin();
465 if (iter == ini->table.end()) {
473 const string* HeaderIterator::next ()
478 if (iter == ini->table.end()) {
485 TagIterator::TagIterator (ShibINIPriv* inip, const string& headerp)
488 saml::NDC ndc("TagIterator");
491 ini->rwlock->rdlock();
493 ini->log->debug("iterators: %d", ini->iterators);
496 TagIterator::~TagIterator ()
498 saml::NDC ndc("~TagIterator");
500 ini->rwlock->unlock();
501 ini->log->debug("iterators: %d", ini->iterators);
504 const string* TagIterator::begin ()
506 iter = ini->table[header].begin();
507 if (iter == ini->table[header].end()) {
515 const string* TagIterator::next ()
520 if (iter == ini->table[header].end()) {
527 bool ShibINI::boolean(string& value)
529 const char* v = value.c_str();
530 #ifdef HAVE_STRCASECMP
531 if (!strncasecmp (v, "on", 2) || !strncasecmp (v, "true", 4) || !strncmp(v, "1", 1))
534 if (!strnicmp (v, "on", 2) || !strnicmp (v, "true", 4) || !strncmp(v, "1", 1))