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