Added "1" for boolean tag values.
[shibboleth/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 #include "shib-target.h"
10 #include <sstream>
11 #include <iostream>
12 #include <fstream>
13 #include <ctype.h>
14
15 #include <sys/types.h>
16 #include <sys/stat.h>
17
18 #include <log4cpp/Category.hh>
19
20 using namespace std;
21 using namespace shibtarget;
22
23 class HeaderIterator : public shibtarget::ShibINI::Iterator {
24 public:
25   HeaderIterator (ShibINIPriv* ini);
26   ~HeaderIterator () { }
27
28   const string* begin();
29   const string* next();
30 private:
31   ShibINIPriv* ini;
32   map<string, map<string,string> >::const_iterator iter;
33   bool valid;
34 };
35
36 class TagIterator : public ShibINI::Iterator {
37 public:
38   TagIterator (ShibINIPriv* ini, const string& header);
39   ~TagIterator () { }
40
41   const string* begin();
42   const string* next();
43 private:
44   ShibINIPriv* ini;
45   const string& header;
46   map<string,string>::const_iterator iter;
47   bool valid;
48 };
49
50 class shibtarget::ShibINIPriv {
51 public:
52   ShibINIPriv();
53   ~ShibINIPriv() { }
54   log4cpp::Category *log;
55
56   map<string, map<string, string> > table;
57   string file;
58   bool cs;
59
60   unsigned long modtime;
61 };
62
63 ShibINIPriv::ShibINIPriv()
64 {
65   string ctx = "shibtarget.ShibINI";
66   log = &(log4cpp::Category::getInstance(ctx));
67 }
68
69 static void trimline (string& s)
70 {
71   int end = s.size() - 1, start = 0;
72
73   // Trim stuff on right.
74   while (end > 0 && !isgraph(s[end])) end--;
75
76   // Trim stuff on left.
77   while (start < end && !isgraph(s[start])) start++;
78
79   // Modify the string.
80   s = s.substr(start, end - start + 1);
81 }
82
83 static void to_lowercase (string& s)
84 {
85   for (int i = 0, sz = s.size(); i < sz; i++)
86     s[i] = tolower(s[i]);
87 }
88
89 ShibINI::~ShibINI() {
90   delete m_priv;
91 }
92
93 void ShibINI::init (string& f, bool case_sensitive)
94 {
95   m_priv = new ShibINIPriv();
96   m_priv->file = f;
97   m_priv->cs = case_sensitive;
98   m_priv->log->info ("initializing INI file: %s (sensitive=%s)", f.c_str(),
99                      (case_sensitive ? "true" : "false"));
100   refresh();
101 }
102
103 void ShibINI::refresh(void)
104 {
105   saml::NDC ndc("refresh");
106
107   // check if we need to refresh
108 #ifdef _WIN32
109   struct _stat stat_buf;
110   if (_stat (m_priv->file.c_str(), &stat_buf) < 0)
111 #else
112   struct stat stat_buf;
113   if (stat (m_priv->file.c_str(), &stat_buf) < 0)
114 #endif
115     m_priv->log->error("stat failed: %s", m_priv->file.c_str());
116
117   if (m_priv->modtime == stat_buf.st_mtime)
118     return;
119
120   m_priv->modtime = stat_buf.st_mtime;
121
122   // clear the existing maps
123   m_priv->table.clear();
124
125   m_priv->log->info("reading %s", m_priv->file.c_str());
126   
127   // read the file
128   try
129   {
130     ifstream infile (m_priv->file.c_str());
131     if (!infile) {
132       m_priv->log->warn("cannot open file: %s", m_priv->file.c_str());
133       return;
134     }
135
136     const int MAXLEN = 1024;
137     char linebuffer[MAXLEN];
138     string current_header;
139     bool have_header = false;
140
141     while (infile) {
142       infile.getline (linebuffer, MAXLEN);
143       string line (linebuffer);
144
145       if (line[0] == '#') continue;
146
147       trimline (line);
148       if (line.size() <= 1) continue;
149
150       if (line[0] == '[') {
151         // this is a header
152
153         m_priv->log->debug("Found what appears to be a header line");
154
155         have_header = false;
156
157         // find the end of the header
158         int endpos = line.find (']');
159         if (endpos == line.npos) {
160           m_priv->log->debug("Weird: no end found.. punting");
161           continue; // HUH?  No end?
162         }
163
164         // found it
165         current_header = line.substr (1, endpos-1);
166         trimline (current_header);
167
168         if (!m_priv->cs) to_lowercase (current_header);
169
170         m_priv->table[current_header] = map<string,string>();
171         have_header = true;
172         m_priv->log->debug("current header: \"%s\"", current_header.c_str());
173
174       } else if (have_header) {
175         // this is a tag
176
177         m_priv->log->debug("Found what appears to be a tag line");
178
179         string tag, setting;
180         int mid = line.find ('=');
181
182         if (mid == line.npos) {
183           m_priv->log->debug("Weird: no '=' found.. punting");
184           continue; // Can't find the value's setting
185         }
186
187         tag = line.substr (0,mid);
188         setting = line.substr (mid+1, line.size()-mid);
189
190         trimline (tag);
191         trimline (setting);
192
193         if (!m_priv->cs) to_lowercase (tag);
194
195         // If it already exists, log an error and do not save it
196         if (exists (current_header, tag))
197           m_priv->log->error("Duplicate tag found in section %s: \"%s\"",
198                              current_header.c_str(), tag.c_str());
199         else
200           (m_priv->table[current_header])[tag] = setting;
201
202         m_priv->log->debug("new tag: \"%s\" = \"%s\"",
203                           tag.c_str(), setting.c_str());
204
205       }
206
207     } // until the file ends
208
209   } catch (...) {
210     // In case there are exceptions.
211   }
212 }
213
214 const std::string& ShibINI::get (const string& header, const string& tag)
215 {
216   refresh();
217
218   static string empty = "";
219
220   string h = header;
221   string t = tag;
222
223   if (!m_priv->cs) {
224     to_lowercase (h);
225     to_lowercase (t);
226   }
227
228   if (!exists(h)) return empty;
229
230   map<string,string>::const_iterator i = m_priv->table[h].find(t);
231   if (i == m_priv->table[h].end())
232     return empty;
233   return i->second;
234 }
235
236 bool ShibINI::exists(const std::string& header)
237 {
238   refresh();
239
240   string h = header;
241   if (!m_priv->cs) to_lowercase (h);
242
243   return (m_priv->table.find(h) != m_priv->table.end());
244 }
245
246 bool ShibINI::exists(const std::string& header, const std::string& tag)
247 {
248   refresh();
249
250   string h = header;
251   string t = tag;
252
253   if (!m_priv->cs) {
254     to_lowercase (h);
255     to_lowercase (t);
256   }
257
258   if (!exists(h)) return false;
259   return (m_priv->table[h].find(t) != m_priv->table[h].end());
260 }
261
262 bool ShibINI::get_tag (string& header, string& tag, bool try_general, string* result)
263 {
264   if (!result) return false;
265
266   refresh();
267
268   if (exists (header, tag)) {
269     *result = get (header, tag);
270     return true;
271   }
272   if (try_general && exists (SHIBTARGET_GENERAL, tag)) {
273     *result = get (SHIBTARGET_GENERAL, tag);
274     return true;
275   }
276   return false;
277 }
278
279
280 void ShibINI::dump (ostream& os)
281 {
282   refresh();
283
284   os << "File: " << m_priv->file << "\n";
285   os << "Case-Sensitive: " << ( m_priv->cs ? "Yes\n" : "No\n" );
286   os << "File Entries:\n";
287
288   for (map<string, map<string, string> >::const_iterator i = m_priv->table.begin();
289        i != m_priv->table.end(); i++) {
290
291     os << "[" << i->first << "]\n";
292
293     for (map<string,string>::const_iterator j=i->second.begin();
294          j != i->second.end(); j++) {
295
296       os << "  " << j->first << " = " << j->second << "\n";
297     }
298   }
299
300   os << "END\n";
301 }
302
303 ShibINI::Iterator* ShibINI::header_iterator()
304 {
305   refresh();
306   HeaderIterator* iter = new HeaderIterator(m_priv);
307   return (ShibINI::Iterator*) iter;
308 }
309
310 ShibINI::Iterator* ShibINI::tag_iterator(const std::string& header)
311 {
312   refresh();
313   TagIterator* iter = new TagIterator(m_priv, header);
314   return (ShibINI::Iterator*) iter;
315 }
316
317 HeaderIterator::HeaderIterator (ShibINIPriv* inip)
318 {
319   ini = inip;
320   valid = false;
321 }
322
323 const string* HeaderIterator::begin ()
324 {
325   iter = ini->table.begin();
326   if (iter == ini->table.end()) {
327     valid = false;
328     return 0;
329   }
330   valid = true;
331   return &iter->first;
332 }
333
334 const string* HeaderIterator::next ()
335 {
336   if (!valid)
337     return 0;
338   iter++;
339   if (iter == ini->table.end()) {
340     valid = false;
341     return 0;
342   }
343   return &iter->first;
344 }
345
346 TagIterator::TagIterator (ShibINIPriv* inip, const string& headerp)
347   : header(headerp)
348 {
349   ini = inip;
350   valid = false;
351 }
352
353 const string* TagIterator::begin ()
354 {
355   iter = ini->table[header].begin();
356   if (iter == ini->table[header].end()) {
357     valid = false;
358     return 0;
359   }
360   valid = true;
361   return &iter->first;
362 }
363
364 const string* TagIterator::next ()
365 {
366   if (!valid)
367     return 0;
368   iter++;
369   if (iter == ini->table[header].end()) {
370     valid = false;
371     return 0;
372   }
373   return &iter->first;
374 }
375
376 bool ShibINI::boolean(string& value)
377 {
378   const char* v = value.c_str();
379   if (!strncasecmp (v, "on", 2) || !strncasecmp (v, "true", 4) || !strncmp(v, "1", 1))
380     return true;
381   return false;
382 }