shib-ccache.cpp:
[shibboleth/cpp-sp.git] / shib-target / shib-ini.cpp
1 /*
2  * shib-ini.h -- INI file handling
3  *
4  * Created By:  Derek Atkins <derek@ihtfp.com>
5  *
6  * $Id$
7  */
8
9 // eventually we might be able to support autoconf via cygwin...
10 #if defined (_MSC_VER) || defined(__BORLANDC__)
11 # include "config_win32.h"
12 #else
13 # include "config.h"
14 #endif
15
16 #include "shib-target.h"
17 #include "shib-threads.h"
18
19 #include <sstream>
20 #include <iostream>
21 #include <fstream>
22 #include <ctype.h>
23
24 #include <sys/types.h>
25 #include <sys/stat.h>
26
27 #include <log4cpp/Category.hh>
28
29 using namespace std;
30 using namespace shibtarget;
31
32 class HeaderIterator : public shibtarget::ShibINI::Iterator {
33 public:
34   HeaderIterator (ShibINIPriv* ini);
35   ~HeaderIterator ();
36
37   const string* begin();
38   const string* next();
39 private:
40   ShibINIPriv* ini;
41   map<string, map<string,string> >::const_iterator iter;
42   bool valid;
43 };
44
45 class TagIterator : public ShibINI::Iterator {
46 public:
47   TagIterator (ShibINIPriv* ini, const string& header);
48   ~TagIterator ();
49
50   const string* begin();
51   const string* next();
52 private:
53   ShibINIPriv* ini;
54   string header;
55   map<string,string>::const_iterator iter;
56   bool valid;
57 };
58
59 class shibtarget::ShibINIPriv {
60 public:
61   ShibINIPriv();
62   ~ShibINIPriv() { delete rwlock; }
63   log4cpp::Category *log;
64
65   map<string, map<string, string> > table;
66   string file;
67   bool cs;
68
69   unsigned long modtime;
70
71   unsigned long iterators;
72   RWLock *rwlock;
73
74   bool exists(const std::string& header);
75   bool exists(const std::string& header, const std::string& tag);
76 };
77
78 ShibINIPriv::ShibINIPriv()
79 {
80   string ctx = "shibtarget.ShibINI";
81   log = &(log4cpp::Category::getInstance(ctx));
82   rwlock = RWLock::create();
83   iterators = 0;
84 }
85
86 static void trimline (string& s)
87 {
88   int end = s.size() - 1, start = 0;
89
90   // Trim stuff on right.
91   while (end > 0 && !isgraph(s[end])) end--;
92
93   // Trim stuff on left.
94   while (start < end && !isgraph(s[start])) start++;
95
96   // Modify the string.
97   s = s.substr(start, end - start + 1);
98 }
99
100 static void to_lowercase (string& s)
101 {
102   for (int i = 0, sz = s.size(); i < sz; i++)
103     s[i] = tolower(s[i]);
104 }
105
106 ShibINI::~ShibINI() {
107   delete m_priv;
108 }
109
110 void ShibINI::init (string& f, bool case_sensitive)
111 {
112   m_priv = new ShibINIPriv();
113   m_priv->file = f;
114   m_priv->cs = case_sensitive;
115   m_priv->log->info ("initializing INI file: %s (sensitive=%s)", f.c_str(),
116                      (case_sensitive ? "true" : "false"));
117
118   ReadLock lock(m_priv->rwlock);
119   refresh();
120 }
121
122 //
123 // Must be called holding the ReadLock.
124 //
125 void ShibINI::refresh(void)
126 {
127   saml::NDC ndc("refresh");
128
129   // check if we need to refresh
130 #ifdef _WIN32
131   struct _stat stat_buf;
132   if (_stat (m_priv->file.c_str(), &stat_buf) < 0)
133 #else
134   struct stat stat_buf;
135   if (stat (m_priv->file.c_str(), &stat_buf) < 0)
136 #endif
137     m_priv->log->error("stat failed: %s", m_priv->file.c_str());
138
139   if (m_priv->modtime >= stat_buf.st_mtime || m_priv->iterators > 0)
140     return;
141
142   // Release the read lock -- grab the write lock.  Don't worry if
143   // this is non-atomic -- we'll recheck the status.
144   m_priv->rwlock->unlock();
145   m_priv->rwlock->wrlock();
146
147   // Recheck the modtime
148   if (m_priv->modtime >= stat_buf.st_mtime) {
149     // Yep, another thread got to it.  We can exit now...  Release
150     // the write lock and reaquire the read-lock.
151
152     m_priv->rwlock->unlock();
153     m_priv->rwlock->rdlock();
154     return;
155   }
156
157   // Ok, we've got the write lock.  Let's update our state.
158
159   m_priv->modtime = stat_buf.st_mtime;
160
161   // clear the existing maps
162   m_priv->table.clear();
163
164   m_priv->log->info("reading %s", m_priv->file.c_str());
165   
166   // read the file
167   try
168   {
169     ifstream infile (m_priv->file.c_str());
170     if (!infile) {
171       m_priv->log->warn("cannot open file: %s", m_priv->file.c_str());
172       m_priv->rwlock->unlock();
173       m_priv->rwlock->rdlock();
174       return;
175     }
176
177     const int MAXLEN = 1024;
178     char linebuffer[MAXLEN];
179     string current_header;
180     bool have_header = false;
181
182     while (infile) {
183       infile.getline (linebuffer, MAXLEN);
184       string line (linebuffer);
185
186       if (line[0] == '#') continue;
187
188       trimline (line);
189       if (line.size() <= 1) continue;
190
191       if (line[0] == '[') {
192         // this is a header
193
194         m_priv->log->debug("Found what appears to be a header line");
195
196         have_header = false;
197
198         // find the end of the header
199         int endpos = line.find (']');
200         if (endpos == line.npos) {
201           m_priv->log->debug("Weird: no end found.. punting");
202           continue; // HUH?  No end?
203         }
204
205         // found it
206         current_header = line.substr (1, endpos-1);
207         trimline (current_header);
208
209         if (!m_priv->cs) to_lowercase (current_header);
210
211         m_priv->table[current_header] = map<string,string>();
212         have_header = true;
213         m_priv->log->debug("current header: \"%s\"", current_header.c_str());
214
215       } else if (have_header) {
216         // this is a tag
217
218         m_priv->log->debug("Found what appears to be a tag line");
219
220         string tag, setting;
221         int mid = line.find ('=');
222
223         if (mid == line.npos) {
224           m_priv->log->debug("Weird: no '=' found.. punting");
225           continue; // Can't find the value's setting
226         }
227
228         tag = line.substr (0,mid);
229         setting = line.substr (mid+1, line.size()-mid);
230
231         trimline (tag);
232         trimline (setting);
233
234         if (!m_priv->cs) to_lowercase (tag);
235
236         // If it already exists, log an error and do not save it
237         if (m_priv->exists (current_header, tag))
238           m_priv->log->error("Duplicate tag found in section %s: \"%s\"",
239                              current_header.c_str(), tag.c_str());
240         else
241           (m_priv->table[current_header])[tag] = setting;
242
243         m_priv->log->debug("new tag: \"%s\" = \"%s\"",
244                           tag.c_str(), setting.c_str());
245
246       }
247
248     } // until the file ends
249
250   } catch (...) {
251     // In case there are exceptions.
252   }
253
254   // Now release the write lock and reaquire the read lock
255   m_priv->rwlock->unlock();
256   m_priv->rwlock->rdlock();
257 }
258
259 const std::string ShibINI::get (const string& header, const string& tag)
260 {
261   ReadLock rwlock(m_priv->rwlock);
262   refresh();
263
264   static string empty = "";
265
266   string h = header;
267   string t = tag;
268
269   if (!m_priv->cs) {
270     to_lowercase (h);
271     to_lowercase (t);
272   }
273
274   if (!exists(h)) return empty;
275
276   map<string,string>::const_iterator i = m_priv->table[h].find(t);
277   if (i == m_priv->table[h].end())
278     return empty;
279   return i->second;
280 }
281
282 bool ShibINIPriv::exists(const std::string& header)
283 {
284   string h = header;
285   if (!cs) to_lowercase (h);
286
287   return (table.find(h) != table.end());
288 }
289
290 bool ShibINI::exists(const std::string& header)
291 {
292   ReadLock rwlock(m_priv->rwlock);
293   refresh();
294
295   return m_priv->exists(header);
296 }
297
298 bool ShibINIPriv::exists(const std::string& header, const std::string& tag)
299 {
300   string h = header;
301   string t = tag;
302
303   if (!cs) {
304     to_lowercase (h);
305     to_lowercase (t);
306   }
307
308   if (!exists(h)) return false;
309   return (table[h].find(t) != table[h].end());
310 }
311
312 bool ShibINI::exists(const std::string& header, const std::string& tag)
313 {
314   ReadLock rwlock(m_priv->rwlock);
315   refresh();
316
317   return m_priv->exists(header, tag);
318 }
319
320 bool ShibINI::get_tag (string& header, string& tag, bool try_general, string* result)
321 {
322   if (!result) return false;
323
324   ReadLock rwlock(m_priv->rwlock);
325   refresh();
326
327   if (exists (header, tag)) {
328     *result = get (header, tag);
329     return true;
330   }
331   if (try_general && exists (SHIBTARGET_GENERAL, tag)) {
332     *result = get (SHIBTARGET_GENERAL, tag);
333     return true;
334   }
335   return false;
336 }
337
338
339 void ShibINI::dump (ostream& os)
340 {
341   ReadLock rwlock(m_priv->rwlock);
342   refresh();
343
344   os << "File: " << m_priv->file << "\n";
345   os << "Case-Sensitive: " << ( m_priv->cs ? "Yes\n" : "No\n" );
346   os << "File Entries:\n";
347
348   for (map<string, map<string, string> >::const_iterator i = m_priv->table.begin();
349        i != m_priv->table.end(); i++) {
350
351     os << "[" << i->first << "]\n";
352
353     for (map<string,string>::const_iterator j=i->second.begin();
354          j != i->second.end(); j++) {
355
356       os << "  " << j->first << " = " << j->second << "\n";
357     }
358   }
359
360   os << "END\n";
361 }
362
363 ShibINI::Iterator* ShibINI::header_iterator()
364 {
365   ReadLock rwlock(m_priv->rwlock);
366   refresh();
367   HeaderIterator* iter = new HeaderIterator(m_priv);
368   return (ShibINI::Iterator*) iter;
369 }
370
371 ShibINI::Iterator* ShibINI::tag_iterator(const std::string& header)
372 {
373   ReadLock rwlock(m_priv->rwlock);
374   refresh();
375   TagIterator* iter = new TagIterator(m_priv, header);
376   return (ShibINI::Iterator*) iter;
377 }
378
379 //
380 // XXX: FIXME: there may be a race condition in the iterators if a
381 // caller holds an active Iterator, the underlying file changes, and
382 // then calls one of the get() routines.  It's possible the iterator
383 // may screw up -- I don't know whether the iterator actually depends
384 // on the underlying infrastructure or not.
385 //
386
387 HeaderIterator::HeaderIterator (ShibINIPriv* inip)
388 {
389   ini = inip;
390   valid = false;
391   ini->rwlock->rdlock();
392   ini->iterators++;
393 }
394
395 HeaderIterator::~HeaderIterator ()
396 {
397   ini->iterators--;
398   ini->rwlock->unlock();
399 }
400
401 const string* HeaderIterator::begin ()
402 {
403   iter = ini->table.begin();
404   if (iter == ini->table.end()) {
405     valid = false;
406     return 0;
407   }
408   valid = true;
409   return &iter->first;
410 }
411
412 const string* HeaderIterator::next ()
413 {
414   if (!valid)
415     return 0;
416   iter++;
417   if (iter == ini->table.end()) {
418     valid = false;
419     return 0;
420   }
421   return &iter->first;
422 }
423
424 TagIterator::TagIterator (ShibINIPriv* inip, const string& headerp)
425   : header(headerp)
426 {
427   ini = inip;
428   valid = false;
429   ini->rwlock->rdlock();
430   ini->iterators++;
431 }
432
433 TagIterator::~TagIterator ()
434 {
435   ini->iterators--;
436   ini->rwlock->unlock();
437 }
438
439 const string* TagIterator::begin ()
440 {
441   iter = ini->table[header].begin();
442   if (iter == ini->table[header].end()) {
443     valid = false;
444     return 0;
445   }
446   valid = true;
447   return &iter->first;
448 }
449
450 const string* TagIterator::next ()
451 {
452   if (!valid)
453     return 0;
454   iter++;
455   if (iter == ini->table[header].end()) {
456     valid = false;
457     return 0;
458   }
459   return &iter->first;
460 }
461
462 bool ShibINI::boolean(string& value)
463 {
464   const char* v = value.c_str();
465 #ifdef HAVE_STRCASECMP
466   if (!strncasecmp (v, "on", 2) || !strncasecmp (v, "true", 4) || !strncmp(v, "1", 1))
467     return true;
468 #else
469   if (!strnicmp (v, "on", 2) || !strnicmp (v, "true", 4) || !strncmp(v, "1", 1))
470     return true;
471 #endif
472   return false;
473 }