1 //--------------------- Configuration Parsing ----------------------------
2 #include <stdio.h>
3 #include <ctype.h>
4 #include <string>
5 #include <sys/stat.h>           // Stat()
6 #include "c-client-header.h"           // for MAILTMPLEN
7 #include "configuration.h"
8 #include "options.h"
9 #include "store.h"
10 #include "channel.h"
11 
12 extern options_t options;
13 
14 //////////////////////////////////////////////////////////////////////////
15 //
16 struct Token {
17 //
18 // A token is a [space|newline|tab] delimited chain of characters which
19 // represents a syntactic element from the configuration file
20 //
21 //////////////////////////////////////////////////////////////////////////
22   string buf;
23   int eof;
24   int line;
25 };
26 
27 
28 //////////////////////////////////////////////////////////////////////////
29 //
die_with_fatal_parse_error(Token * t,char * errorMessage,const char * insertIntoMessage=NULL)30 void die_with_fatal_parse_error( Token* t,
31                                  char * errorMessage,
32                                  const char * insertIntoMessage = NULL)
33 //
34 // Fatal error while parsing the config file
35 // Display error message and quit
36 //
37 // errorMessage       must contain an error message to be displayed
38 // insertIntoMessage  is optional. errorMessage can act as a sprintf format
39 //                    string in which case insertIntoMessage will be used
40 //                    as replacement for "%s" inside errorMessage
41 //
42 //////////////////////////////////////////////////////////////////////////
43 {
44   fprintf(stderr, "Error: ");
45   fprintf(stderr, errorMessage, insertIntoMessage ? insertIntoMessage : "");
46   fprintf(stderr, "\n");
47   if (t->eof) {
48     fprintf(stderr, "       Unexpected EOF while parsing configuration file");
49     fprintf(stderr, "       While parsing line %d:\n", t->line);
50   }
51   else {
52     fprintf(stderr, "       While parsing line %d at token \"%s\"\n",
53             t->line, t->buf.c_str());
54   }
55   fprintf(stderr,   "       Quitting!\n");
56 
57   exit(1);
58 }
59 
60 //////////////////////////////////////////////////////////////////////////
61 //
get_token(FILE * f,Token * t)62 void get_token( FILE* f, Token* t )
63 //
64 // Read a token from the config file
65 //
66 //////////////////////////////////////////////////////////////////////////
67 {
68   int c;
69   t->buf = "";
70   t->eof = 0;
71   while (1) {
72     if (t->buf.size() > MAILTMPLEN) {
73       // Token too long
74       die_with_fatal_parse_error(t, "Line too long");
75     }
76     c = getc(f);
77 
78     // Ignore comments
79     if ( t->buf.size() == 0 && c=='#') {
80       while( c != '\n' && c !=EOF) {
81         c = getc(f);
82       }
83     }
84 
85     // Skip newline
86     if (c=='\n')
87       t->line++;
88 
89     // We're done reading the config file
90     if (c==EOF) {
91       t->eof = 1;
92       break;
93 
94     // We've reached a token boundary
95     }
96     else if (isspace(c)) {
97       // The token is non empty so we've acquired something and can return
98       if (t->buf.size()) {
99         break;
100       // Ignore spaces
101       }
102       else {
103         continue;
104       }
105     // Continue on next line...
106     }
107     else if (c=='\\') {
108       t->buf += (char)getc(f);
109     // Add to token
110     }
111     else {
112       t->buf += (char)c;
113     }
114   }
115   if (options.debug_config) printf("\t%s\n",t->buf.c_str());
116   return;
117 }
118 
119 //////////////////////////////////////////////////////////////////////////
120 //
parse_config(FILE * f,map<string,ConfigItem> & confmap)121 void parse_config(FILE* f, map<string, ConfigItem>& confmap)
122 //
123 // Parse config file
124 //
125 //////////////////////////////////////////////////////////////////////////
126 {
127   Token token;
128   Token *t;
129 
130   if (options.debug_config) printf(" Parsing config...\n\n");
131 
132   t = &token;
133   t->line = 0;
134 
135   // Read items from file
136   while (1) {
137     ConfigItem config_item;
138     string name;
139 
140     // Acquire one item
141     get_token(f, t);
142 
143     // End of config file reached
144     if (t->eof)
145       break;
146 
147     // Parse store
148     if (t->buf == "store") {
149       Store* store = new Store();
150       // Read name (store name)
151       get_token(f, t);
152       if (t->eof) {
153         die_with_fatal_parse_error( t, "Store name missing");
154       }
155       name = t->buf;
156       store->name = t->buf;
157       // Read "{"
158       get_token(f, t);
159       if (t->eof || t->buf != "{") {
160         die_with_fatal_parse_error( t, "Expected \"{\" while parsing store");
161       }
162       // Read store configuration (name/value pairs)
163       while (1) {
164         get_token(f, t);
165         // End of store config
166         if (t->buf == "}")
167           break;
168         else if (t->eof)
169           die_with_fatal_parse_error( t,
170                          "Expected \"{\" while parsing store: unclosed store");
171         else if (t->buf == "server") {
172           get_token(f, t);
173           store->server = t->buf;
174         }
175         else if (t->buf == "prefix") {
176           get_token(f, t);
177           store->prefix = t->buf;
178         }
179         else if (t->buf == "ref") {
180           get_token(f, t);
181           store->ref = t->buf;
182         }
183         else if (t->buf == "pat") {
184           get_token(f, t);
185           store->pat = t->buf;
186         }
187         else if (t->buf == "passwd") {
188           get_token(f, t);
189           store->set_passwd(t->buf);
190         }
191         else
192           die_with_fatal_parse_error(t, "Unknown store field");
193       }
194       if (store->server == "") {
195         store->isremote = 0;
196       }
197       else {
198         store->isremote = 1;
199       }
200       config_item.store    = store;
201       config_item.is_store = true;
202     }
203     // Parse a channel
204     else if (t->buf == "channel") {
205       Channel* channel = new Channel();
206       // Read name (channel name)
207       get_token(f, t);
208       if (t->eof) {
209         die_with_fatal_parse_error( t, "Channel name missing");
210       }
211       name = t->buf;
212       channel->name = t->buf;
213       // Read stores
214       get_token(f, t);
215       if (t->eof) {
216         die_with_fatal_parse_error( t,
217                         "Expected a store name while parsing channel");
218       }
219       channel->store_a.name = t->buf;
220       get_token(f, t);
221       if (t->eof) {
222         die_with_fatal_parse_error( t,
223                         "Expected a store name while parsing channel");
224       }
225       channel->store_b.name = t->buf;
226       // Read "{"
227       get_token(f, t);
228       if (t->eof || t->buf != "{") {
229         die_with_fatal_parse_error(t, "Expected \"{\" while parsing channel");
230       }
231       // Read channel config (name/value pairs)
232       while (1) {
233         get_token(f, t);
234         if (t->buf == "}")
235           break;
236         else if (t->eof)
237           die_with_fatal_parse_error(t, "Unclosed channel");
238         else if (t->buf == "msinfo") {
239           get_token(f, t);
240           channel->msinfo = t->buf;
241         }
242         else if (t->buf == "passwd") {
243           get_token(f, t);
244           channel->set_passwd(t->buf);
245         }
246 	else if (t->buf == "sizelimit") {
247           get_token(f, t);
248 	  channel->set_sizelimit(t->buf);
249 	}
250         else
251           die_with_fatal_parse_error(t, "Unknown channel field");
252       }
253       if (channel->msinfo == "") {
254         die_with_fatal_parse_error( t, "%s: missing msinfo",
255                                     channel->name.c_str());
256       }
257       config_item.channel  = channel;
258       config_item.is_store = false;
259     }
260     else
261       die_with_fatal_parse_error(t, "unknow configuration element");
262 
263     if (confmap.count(name)) {
264       die_with_fatal_parse_error( t,
265                       "Tag (store or channel name) used twice: %s",
266                       name.c_str());
267     }
268     confmap.insert( make_pair(name, config_item) );
269   }
270 
271   if (options.debug_config) printf( " End parsing config. Config is OK\n\n" );
272 
273   return;
274 }
275 
276 //////////////////////////////////////////////////////////////////////////
277 //
read_configuration(const string & config_file_name,map<string,ConfigItem> & confmap)278 bool read_configuration( const string& config_file_name,
279                                map<string, ConfigItem>& confmap)
280 //
281 // Read and parse config file
282 //
283 // Return true on success
284 //
285 //////////////////////////////////////////////////////////////////////////
286 {
287   FILE* config;
288   string config_file = config_file_name;
289 
290   if (config_file == "") {
291     char *home;
292     home = getenv( "HOME" );
293     if (home) {
294       config_file = string( home ) + "/.mailsync";
295     }
296     else {
297       fprintf( stderr, "Error: Can't get home directory. Use `-f file'.\n" );
298       return false;
299     }
300   }
301   {
302     struct stat st;
303     stat( config_file.c_str(), &st );
304     if ( ! S_ISREG( st.st_mode )
305          || !( config = fopen( config_file.c_str(), "r") ) )
306     {
307       fprintf( stderr,
308                "Error: Can't open config file %s\n", config_file.c_str());
309       return false;
310     }
311   }
312   parse_config( config, confmap );
313   return true;
314 }
315 
316 //////////////////////////////////////////////////////////////////////////
317 //
setup_channel_stores_and_mode(const string & config_file,const vector<string> & chan_stor_names,Channel & channel)318 enum operation_mode_t setup_channel_stores_and_mode(
319                                     const string& config_file,
320                                     const vector<string>& chan_stor_names,
321                                     Channel& channel)
322 //
323 // Parse chan_stor_names for the desired stores or channel and setup
324 // store_a, store_b and channel accordingly
325 //
326 // return mode
327 //
328 //////////////////////////////////////////////////////////////////////////
329 {
330   vector<Store> stores_to_treat;
331   vector<Channel> channels_to_treat;
332 
333   operation_mode_t operation_mode = mode_unknown;
334 
335   map<string, ConfigItem> configured_items;
336 
337   if ( ! read_configuration( config_file, configured_items))
338     return mode_unknown;
339 
340   // make sure the channel and store names correspond to channel and
341   // store names in the config
342   for ( unsigned i = 0; i < chan_stor_names.size(); i++)
343   {
344     if ( configured_items.count(chan_stor_names[i]) == 0 )
345     {
346       fprintf( stderr,
347                "Error: A channel or store named \"%s\" has not"
348                "been configured\n", chan_stor_names[i].c_str());
349       return mode_unknown;
350     }
351 
352     ConfigItem* config_item = &configured_items[chan_stor_names[i]];
353     if ( config_item->is_store
354          && config_item->store->name == chan_stor_names[i] )
355     {
356       stores_to_treat.push_back( *config_item->store);
357     }
358     else if ( ! config_item->is_store
359               && config_item->channel->name == chan_stor_names[i])
360     {
361       channels_to_treat.push_back( *config_item->channel);
362     }
363   }
364 
365   // mode_sync
366   if ( channels_to_treat.size() == 1 && stores_to_treat.size() == 0 )
367   {
368     channel = channels_to_treat[0];
369     if ( ! configured_items[channel.store_a.name].is_store
370          || ! configured_items[channel.store_b.name].is_store )
371     {
372       fprintf( stderr,
373                "Error: Malconfigured channel %s\n",
374                channel.name.c_str());
375       if( configured_items[ channel.store_a.name ].is_store)
376       {
377         fprintf( stderr,
378                  "The configuration doesn't contain a store named \"%s\"\n",
379                  channel.store_a.name.c_str());
380       }
381       else
382       {
383         fprintf( stderr,
384                  "The configuration doesn't contain a store named \"%s\"\n",
385                  channel.store_b.name.c_str());
386       }
387       return mode_unknown;
388     }
389     channel.store_a = *(configured_items[channel.store_a.name].store);
390     channel.store_b = *(configured_items[channel.store_b.name].store);
391 
392     operation_mode = mode_sync;
393   }
394 
395   // mode_diff
396   else if (channels_to_treat.size() == 1 && stores_to_treat.size() == 1)
397   {
398     options.expunge_duplicates = 0;
399     channel = channels_to_treat[0];
400     if ( channel.store_a.name == stores_to_treat[0].name) {
401       channel.store_b = *(configured_items[channel.store_a.name]).store;
402     }
403     else if ( channel.store_b.name == stores_to_treat[0].name) {
404       channel.store_b = *(configured_items[channel.store_b.name]).store;
405     }
406     else {
407       fprintf( stderr,
408                "Diff mode: channel %s doesn't contain store %s.\n",
409                channel.name.c_str(), stores_to_treat[0].name.c_str() );
410       return mode_unknown;
411     }
412     channel.store_a = stores_to_treat[0];
413 
414     operation_mode = mode_diff;
415   }
416   // mode_list
417   else if (channels_to_treat.size() == 0 && stores_to_treat.size() == 1)
418   {
419     channel.store_a = stores_to_treat[0];
420 
421     operation_mode = mode_list;
422   }
423   else
424   {
425     fprintf( stderr,
426              "Don't know what to do with %d channels and %d stores.\n",
427              channels_to_treat.size(),
428              stores_to_treat.size());
429     return mode_unknown;
430   }
431 
432   // Give feedback on the mode we're in
433   switch( operation_mode ) {
434     case mode_sync:
435       printf( "Synchronizing stores \"%s\" <-> \"%s\"...\n",
436               channel.store_a.name.c_str(),
437               channel.store_b.name.c_str());
438       break;
439     case mode_diff:
440       printf( "Comparing store \"%s\" <-> \"%s\"...\n",
441               channel.store_a.name.c_str(),
442               channel.store_b.name.c_str());
443       break;
444     case mode_list:
445       printf( "Listing store \"%s\"\n",
446               channel.store_a.name.c_str() );
447       break;
448     default:
449       printf( "Panic! Unknown mode - something unexpected happened\n");
450   }
451 
452   return operation_mode;
453 }
454