Reorganized preprocessor declarations for Windows.
[shibboleth/cpp-sp.git] / shib-target / shib-ini.cpp
1 /*
2  * The Shibboleth License, Version 1.
3  * Copyright (c) 2002
4  * University Corporation for Advanced Internet Development, Inc.
5  * All rights reserved
6  *
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are met:
10  *
11  * Redistributions of source code must retain the above copyright notice, this
12  * list of conditions and the following disclaimer.
13  *
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.
22  *
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
28  *
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.
33  *
34  *
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.
48  */
49
50 /*
51  * shib-ini.h -- INI file handling
52  *
53  * Created By:  Derek Atkins <derek@ihtfp.com>
54  *
55  * $Id$
56  */
57
58 #include "internal.h"
59
60 #include <shib/shib-threads.h>
61
62 #include <sstream>
63 #include <iostream>
64 #include <fstream>
65 #include <ctype.h>
66
67 #include <sys/types.h>
68 #include <sys/stat.h>
69
70 class HeaderIterator : public shibtarget::ShibINI::Iterator {
71 public:
72   HeaderIterator (ShibINIPriv* ini);
73   ~HeaderIterator ();
74
75   const string* begin();
76   const string* next();
77 private:
78   ShibINIPriv* ini;
79   map<string, map<string,string> >::const_iterator iter;
80   bool valid;
81 };
82
83 class TagIterator : public ShibINI::Iterator {
84 public:
85   TagIterator (ShibINIPriv* ini, const string& header);
86   ~TagIterator ();
87
88   const string* begin();
89   const string* next();
90 private:
91   ShibINIPriv* ini;
92   string header;
93   map<string,string>::const_iterator iter;
94   bool valid;
95 };
96
97 class shibtarget::ShibINIPriv {
98 public:
99   ShibINIPriv();
100   ~ShibINIPriv() { delete rwlock; }
101   log4cpp::Category *log;
102
103   map<string, map<string, string> > table;
104   string file;
105   bool cs;
106
107   unsigned long modtime;
108
109   unsigned long iterators;
110   RWLock *rwlock;
111
112   bool exists(const std::string& header);
113   bool exists(const std::string& header, const std::string& tag);
114 };
115
116 ShibINIPriv::ShibINIPriv()
117 {
118   string ctx = "shibtarget.ShibINI";
119   log = &(log4cpp::Category::getInstance(ctx));
120   rwlock = RWLock::create();
121   modtime = 0;
122   iterators = 0;
123 }
124
125 static void trimline (string& s)
126 {
127   int end = s.size() - 1, start = 0;
128
129   // Trim stuff on right.
130   while (end > 0 && !isgraph(s[end])) end--;
131
132   // Trim stuff on left.
133   while (start < end && !isgraph(s[start])) start++;
134
135   // Modify the string.
136   s = s.substr(start, end - start + 1);
137 }
138
139 static void to_lowercase (string& s)
140 {
141   for (int i = 0, sz = s.size(); i < sz; i++)
142     s[i] = tolower(s[i]);
143 }
144
145 ShibINI::~ShibINI() {
146   delete m_priv;
147 }
148
149 void ShibINI::init (string& f, bool case_sensitive)
150 {
151   m_priv = new ShibINIPriv();
152   m_priv->file = f;
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"));
156
157   ReadLock lock(m_priv->rwlock);
158   refresh();
159 }
160
161 //
162 // Must be called holding the ReadLock.
163 //
164 void ShibINI::refresh(void)
165 {
166   saml::NDC ndc("refresh");
167
168   // check if we need to refresh
169 #ifdef _WIN32
170   struct _stat stat_buf;
171   if (_stat (m_priv->file.c_str(), &stat_buf) < 0)
172 #else
173   struct stat stat_buf;
174   if (stat (m_priv->file.c_str(), &stat_buf) < 0)
175 #endif
176     m_priv->log->error("stat failed: %s", m_priv->file.c_str());
177
178 #ifdef DEBUG
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);
181 #endif
182
183   if (m_priv->modtime >= stat_buf.st_mtime || m_priv->iterators > 0)
184     return;
185
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();
190
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.
195
196     m_priv->rwlock->unlock();
197     m_priv->rwlock->rdlock();
198     return;
199   }
200
201   // Ok, we've got the write lock.  Let's update our state.
202
203   m_priv->modtime = stat_buf.st_mtime;
204
205   // clear the existing maps
206   m_priv->table.clear();
207
208   m_priv->log->info("reading %s", m_priv->file.c_str());
209   
210   // read the file
211   try
212   {
213     ifstream infile (m_priv->file.c_str());
214     if (!infile) {
215       m_priv->log->warn("cannot open file: %s", m_priv->file.c_str());
216       m_priv->rwlock->unlock();
217       m_priv->rwlock->rdlock();
218       return;
219     }
220
221     const int MAXLEN = 1024;
222     char linebuffer[MAXLEN];
223     string current_header;
224     bool have_header = false;
225
226     while (infile) {
227       infile.getline (linebuffer, MAXLEN);
228       string line (linebuffer);
229
230       if (line[0] == '#') continue;
231
232       trimline (line);
233       if (line.size() <= 1) continue;
234
235       if (line[0] == '[') {
236         // this is a header
237
238 #ifdef DEBUG
239         m_priv->log->info("Found what appears to be a header line");
240 #endif
241
242         have_header = false;
243
244         // find the end of the header
245         int endpos = line.find (']');
246         if (endpos == line.npos) {
247 #ifdef DEBUG
248           m_priv->log->info("Weird: no end found.. punting");
249 #endif
250           continue; // HUH?  No end?
251         }
252
253         // found it
254         current_header = line.substr (1, endpos-1);
255         trimline (current_header);
256
257         if (!m_priv->cs) to_lowercase (current_header);
258
259         m_priv->table[current_header] = map<string,string>();
260         have_header = true;
261 #ifdef DEBUG
262         m_priv->log->info("current header: \"%s\"", current_header.c_str());
263 #endif
264
265       } else if (have_header) {
266         // this is a tag
267
268 #ifdef DEBUG
269         m_priv->log->info("Found what appears to be a tag line");
270 #endif
271
272         string tag, setting;
273         int mid = line.find ('=');
274
275         if (mid == line.npos) {
276 #ifdef DEBUG
277           m_priv->log->info("Weird: no '=' found.. punting");
278 #endif
279           continue; // Can't find the value's setting
280         }
281
282         tag = line.substr (0,mid);
283         setting = line.substr (mid+1, line.size()-mid);
284
285         trimline (tag);
286         trimline (setting);
287
288         if (!m_priv->cs) to_lowercase (tag);
289
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());
294         else
295           (m_priv->table[current_header])[tag] = setting;
296
297 #ifdef DEBUG
298         m_priv->log->info("new tag: \"%s\" = \"%s\"",
299                           tag.c_str(), setting.c_str());
300 #endif
301
302       }
303
304     } // until the file ends
305
306   } catch (...) {
307     // In case there are exceptions.
308   }
309
310   // Now release the write lock and reaquire the read lock
311   m_priv->rwlock->unlock();
312   m_priv->rwlock->rdlock();
313 }
314
315 const std::string ShibINI::get (const string& header, const string& tag)
316 {
317   ReadLock rwlock(m_priv->rwlock);
318   refresh();
319
320   static string empty = "";
321
322   string h = header;
323   string t = tag;
324
325   if (!m_priv->cs) {
326     to_lowercase (h);
327     to_lowercase (t);
328   }
329
330   if (!m_priv->exists(h)) return empty;
331
332   map<string,string>::const_iterator i = m_priv->table[h].find(t);
333   if (i == m_priv->table[h].end())
334     return empty;
335   return i->second;
336 }
337
338 bool ShibINIPriv::exists(const std::string& header)
339 {
340   string h = header;
341   if (!cs) to_lowercase (h);
342
343   return (table.find(h) != table.end());
344 }
345
346 bool ShibINI::exists(const std::string& header)
347 {
348   ReadLock rwlock(m_priv->rwlock);
349   refresh();
350
351   return m_priv->exists(header);
352 }
353
354 bool ShibINIPriv::exists(const std::string& header, const std::string& tag)
355 {
356   string h = header;
357   string t = tag;
358
359   if (!cs) {
360     to_lowercase (h);
361     to_lowercase (t);
362   }
363
364   if (!exists(h)) return false;
365   return (table[h].find(t) != table[h].end());
366 }
367
368 bool ShibINI::exists(const std::string& header, const std::string& tag)
369 {
370   ReadLock rwlock(m_priv->rwlock);
371   refresh();
372
373   return m_priv->exists(header, tag);
374 }
375
376 bool ShibINI::get_tag (string& header, string& tag, bool try_general, string* result)
377 {
378   if (!result) return false;
379
380   m_priv->rwlock->rdlock();
381   refresh();
382   m_priv->rwlock->unlock();
383
384   if (m_priv->exists (header, tag)) {
385     *result = get (header, tag);
386     return true;
387   }
388   if (try_general && exists (SHIBTARGET_GENERAL, tag)) {
389     *result = get (SHIBTARGET_GENERAL, tag);
390     return true;
391   }
392   return false;
393 }
394
395
396 void ShibINI::dump (ostream& os)
397 {
398   ReadLock rwlock(m_priv->rwlock);
399   refresh();
400
401   os << "File: " << m_priv->file << "\n";
402   os << "Case-Sensitive: " << ( m_priv->cs ? "Yes\n" : "No\n" );
403   os << "File Entries:\n";
404
405   for (map<string, map<string, string> >::const_iterator i = m_priv->table.begin();
406        i != m_priv->table.end(); i++) {
407
408     os << "[" << i->first << "]\n";
409
410     for (map<string,string>::const_iterator j=i->second.begin();
411          j != i->second.end(); j++) {
412
413       os << "  " << j->first << " = " << j->second << "\n";
414     }
415   }
416
417   os << "END\n";
418 }
419
420 ShibINI::Iterator* ShibINI::header_iterator()
421 {
422   ReadLock rwlock(m_priv->rwlock);
423   refresh();
424   HeaderIterator* iter = new HeaderIterator(m_priv);
425   return (ShibINI::Iterator*) iter;
426 }
427
428 ShibINI::Iterator* ShibINI::tag_iterator(const std::string& header)
429 {
430   ReadLock rwlock(m_priv->rwlock);
431   refresh();
432   TagIterator* iter = new TagIterator(m_priv, header);
433   return (ShibINI::Iterator*) iter;
434 }
435
436 //
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.
442 //
443
444 HeaderIterator::HeaderIterator (ShibINIPriv* inip)
445 {
446   saml::NDC ndc("HeaderIterator");
447   ini = inip;
448   valid = false;
449   ini->rwlock->rdlock();
450   ini->iterators++;
451   ini->log->debug("iterators: %d", ini->iterators);
452 }
453
454 HeaderIterator::~HeaderIterator ()
455 {
456   saml::NDC ndc("~HeaderIterator");
457   ini->iterators--;
458   ini->rwlock->unlock();
459   ini->log->debug("iterators: %d", ini->iterators);
460 }
461
462 const string* HeaderIterator::begin ()
463 {
464   iter = ini->table.begin();
465   if (iter == ini->table.end()) {
466     valid = false;
467     return 0;
468   }
469   valid = true;
470   return &iter->first;
471 }
472
473 const string* HeaderIterator::next ()
474 {
475   if (!valid)
476     return 0;
477   iter++;
478   if (iter == ini->table.end()) {
479     valid = false;
480     return 0;
481   }
482   return &iter->first;
483 }
484
485 TagIterator::TagIterator (ShibINIPriv* inip, const string& headerp)
486   : header(headerp)
487 {
488   saml::NDC ndc("TagIterator");
489   ini = inip;
490   valid = false;
491   ini->rwlock->rdlock();
492   ini->iterators++;
493   ini->log->debug("iterators: %d", ini->iterators);
494 }
495
496 TagIterator::~TagIterator ()
497 {
498   saml::NDC ndc("~TagIterator");
499   ini->iterators--;
500   ini->rwlock->unlock();
501   ini->log->debug("iterators: %d", ini->iterators);
502 }
503
504 const string* TagIterator::begin ()
505 {
506   iter = ini->table[header].begin();
507   if (iter == ini->table[header].end()) {
508     valid = false;
509     return 0;
510   }
511   valid = true;
512   return &iter->first;
513 }
514
515 const string* TagIterator::next ()
516 {
517   if (!valid)
518     return 0;
519   iter++;
520   if (iter == ini->table[header].end()) {
521     valid = false;
522     return 0;
523   }
524   return &iter->first;
525 }
526
527 bool ShibINI::boolean(string& value)
528 {
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))
532     return true;
533 #else
534   if (!strnicmp (v, "on", 2) || !strnicmp (v, "true", 4) || !strncmp(v, "1", 1))
535     return true;
536 #endif
537   return false;
538 }
539