Added the --output and --quiet options.
[mymail.git] / mymail.c
1
2 /*
3  *  Copyright (c) 2013 Francois Fleuret
4  *  Written by Francois Fleuret <francois@fleuret.org>
5  *
6  *  This file is part of mymail.
7  *
8  *  mymail is free software: you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License version 3 as
10  *  published by the Free Software Foundation.
11  *
12  *  mymail is distributed in the hope that it will be useful, but
13  *  WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  *  General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with mymail.  If not, see <http://www.gnu.org/licenses/>.
19  *
20  */
21
22 /*
23
24   This command is a dumb mail indexer. It can either (1) scan
25   directories containing mbox files, and create a db file containing
26   for each mail a list of fields computed from the header, or (2)
27   read such a db file and get all the mails matching regexp-defined
28   conditions on the fields.
29
30   It is low-tech, simple, light and fast.
31
32 */
33
34 #define _GNU_SOURCE
35
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <locale.h>
42 #include <getopt.h>
43 #include <limits.h>
44 #include <dirent.h>
45 #include <regex.h>
46
47 #define MYMAIL_DB_MAGIC_TOKEN "mymail_index_file"
48 #define VERSION "0.9.1"
49
50 #define MAX_NB_SEARCH_CONDITIONS 10
51
52 #define BUFFER_SIZE 65536
53
54 char *db_filename;
55 char *db_filename_regexp_string;
56 char *db_root_path;
57 char *db_filename_list;
58 char output_filename[PATH_MAX + 1];
59
60 int paranoid;
61 int action_index;
62 int quiet;
63
64 /********************************************************************/
65
66 enum {
67   ID_MAIL = 0,
68   ID_FROM,
69   ID_TO,
70   ID_SUBJECT,
71   ID_DATE,
72   ID_PARTICIPANT,
73   ID_BODY,
74   MAX_ID
75 };
76
77 static char *field_names[] = {
78   "mail",
79   "from",
80   "to",
81   "subject",
82   "date",
83   "part",
84   "body"
85 };
86
87 /********************************************************************/
88
89 struct search_condition {
90   int field_id;
91   int negation;
92   regex_t regexp;
93 };
94
95 /********************************************************************/
96
97 struct parsable_field {
98   int id;
99   char *regexp_string;
100   regex_t regexp;
101 };
102
103 static struct parsable_field fields_to_parse[] = {
104   {
105     ID_FROM,
106     "^\\(From \\|[Ff][Rr][Oo][Mm]:\\|[R][r][E][e][P][p][L][l][Y][y]-[T][t][O][o]:\\)",
107     { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
108   },
109
110   {
111     ID_TO,
112     "^\\([Tt][Oo]\\|[Cc][Cc]\\|[Bb][Cc][Cc]\\): ",
113     { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
114   },
115
116   {
117     ID_SUBJECT,
118     "^[Ss][Uu][Bb][Jj][Ee][Cc][Tt]: ",
119     { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
120   },
121
122   {
123     ID_DATE,
124     "^[Dd][Aa][Tt][Ee]: ",
125     { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
126   },
127
128 };
129
130 /********************************************************************/
131
132 int xor(int a, int b) {
133   return (a && !b) || (!a && b);
134 }
135
136 char *segment_next_field(char *current) {
137   while(*current && *current != ' ') current++;
138   *current = '\0'; current++;
139   while(*current && *current == ' ') current++;
140   return current;
141 }
142
143 void remove_eof(char *c) {
144   while(*c && *c != '\n' && *c != '\r') c++;
145   *c = '\0';
146 }
147
148 /********************************************************************/
149
150 /* malloc with error checking.  */
151
152 void *safe_malloc(size_t n) {
153   void *p = malloc(n);
154   if(!p && n != 0) {
155     fprintf(stderr,
156             "mymail: cannot allocate memory: %s\n", strerror(errno));
157     exit(EXIT_FAILURE);
158   }
159   return p;
160 }
161
162 /*********************************************************************/
163
164 void print_version(FILE *out) {
165   fprintf(out, "mymail version %s (%s)\n", VERSION, UNAME);
166 }
167
168 void print_usage(FILE *out) {
169   print_version(out);
170   fprintf(out, "Written by Francois Fleuret <francois@fleuret.org>.\n");
171   fprintf(out, "\n");
172   fprintf(out, "Usage: mymail [options] [<mbox dir1> [<mbox dir2> ...]|<db file1> [<db file2> ...]]\n");
173   fprintf(out, "\n");
174   fprintf(out, " -h, --help\n");
175   fprintf(out, "         show this help\n");
176   fprintf(out, " -v, --version\n");
177   fprintf(out, "         print the version number\n");
178   fprintf(out, " -q, --quiet\n");
179   fprintf(out, "         do not print information during search\n");
180   fprintf(out, " -p <db filename pattern>, --db-pattern <db filename pattern>\n");
181   fprintf(out, "         set the db filename pattern for recursive search\n");
182   fprintf(out, " -r <db root path>, --db-root <db root path>\n");
183   fprintf(out, "         set the db root path for recursive search\n");
184   fprintf(out, " -l <db filename list>, --db-list <db filename list>\n");
185   fprintf(out, "         set the semicolon-separated list of db files for search\n");
186   fprintf(out, " -s <search pattern>, --search <search pattern>\n");
187   fprintf(out, "         search for matching mails in the db file\n");
188   fprintf(out, " -d <db filename>, --db-file <db filename>\n");
189   fprintf(out, "         set the db filename for indexing\n");
190   fprintf(out, " -i, --index\n");
191   fprintf(out, "         index mails\n");
192   fprintf(out, " -o <output filename>, --output <output filename>\n");
193   fprintf(out, "         set the result file, use stdout if unset\n");
194 }
195
196 /*********************************************************************/
197
198 int ignore_entry(const char *name) {
199   return
200     /* strcmp(name, ".") == 0 || */
201     /* strcmp(name, "..") == 0 || */
202     (name[0] == '.' && name[1] != '/');
203 }
204
205 int mbox_line_match_search(struct search_condition *condition,
206                            int mbox_id, char *mbox_value) {
207   return
208     (condition->field_id == mbox_id ||
209      (condition->field_id == ID_PARTICIPANT && (mbox_id == ID_FROM || mbox_id == ID_TO)))
210     &&
211     regexec(&condition->regexp, mbox_value, 0, 0, 0) == 0;
212 }
213
214 void search_in_db(FILE *db_file,
215                   int nb_search_conditions,
216                   struct search_condition *search_conditions,
217                   FILE *output_file) {
218
219   int hits[MAX_NB_SEARCH_CONDITIONS];
220   char raw_db_line[BUFFER_SIZE];
221   char raw_mbox_line[BUFFER_SIZE];
222   char current_mail_filename[PATH_MAX + 1];
223   unsigned long int current_position_in_mail;
224   char *mbox_name, *mbox_value;
225   int mbox_id;
226   int already_written, m, n;
227   int last_mbox_line_was_empty;
228   int nb_body_conditions, nb_fulfilled_body_conditions;
229
230   current_position_in_mail = 0;
231   already_written = 0;
232
233   for(n = 0; n < nb_search_conditions; n++) { hits[n] = 0; }
234
235   nb_body_conditions = 0;
236   for(n = 0; n < nb_search_conditions; n++) {
237     if(search_conditions[n].field_id == ID_BODY) {
238       nb_body_conditions++;
239     }
240   }
241
242   strcpy(current_mail_filename, "");
243
244   while(fgets(raw_db_line, BUFFER_SIZE, db_file)) {
245     mbox_name = raw_db_line;
246     mbox_value = segment_next_field(raw_db_line);
247
248     if(strcmp("mail", mbox_name) == 0) {
249       char *position_in_file_string;
250       char *mail_filename;
251
252       if(current_mail_filename[0]) {
253
254         /* We first check all conditions but the body ones */
255
256         for(n = 0; n < nb_search_conditions &&
257               ((search_conditions[n].field_id == ID_BODY) ||
258                xor(hits[n], search_conditions[n].negation)); n++);
259
260         if(n == nb_search_conditions) {
261
262           /* all conditions but the body ones are fine, check the body
263              ones */
264
265           nb_fulfilled_body_conditions = 0;
266
267           if(nb_body_conditions > 0) {
268             FILE *mail_file;
269             int header;
270
271             header = 1;
272             mail_file = fopen(current_mail_filename, "r");
273
274             if(!mail_file) {
275               fprintf(stderr,
276                       "mymail: Cannot open mbox '%s' for body scan.\n",
277                       current_mail_filename);
278               exit(EXIT_FAILURE);
279             }
280
281             fseek(mail_file, current_position_in_mail, SEEK_SET);
282
283             if(fgets(raw_mbox_line, BUFFER_SIZE, mail_file)) {
284               while(nb_fulfilled_body_conditions < nb_body_conditions) {
285                 last_mbox_line_was_empty = (raw_mbox_line[0] == '\n');
286
287                 if(last_mbox_line_was_empty) { header = 0; }
288
289                 if(!header) {
290                   for(n = 0; n < nb_search_conditions; n++) {
291                     if(search_conditions[n].field_id == ID_BODY && !hits[n]) {
292                       hits[n] =
293                         (regexec(&search_conditions[n].regexp, raw_mbox_line, 0, 0, 0) == 0);
294                       if(hits[n]) {
295                         nb_fulfilled_body_conditions++;
296                       }
297                     }
298                   }
299                 }
300
301                 if(!fgets(raw_mbox_line, BUFFER_SIZE, mail_file) ||
302                    (last_mbox_line_was_empty && strncmp(raw_mbox_line, "From ", 5) == 0))
303                   break;
304               }
305             }
306
307             fclose(mail_file);
308           }
309
310           if(nb_body_conditions == nb_fulfilled_body_conditions) {
311             FILE *mail_file;
312
313             mail_file = fopen(current_mail_filename, "r");
314
315             if(!mail_file) {
316               fprintf(stderr,
317                       "mymail: Cannot open mbox '%s' for mail extraction.\n",
318                       current_mail_filename);
319               exit(EXIT_FAILURE);
320             }
321
322             fseek(mail_file, current_position_in_mail, SEEK_SET);
323
324             if(fgets(raw_mbox_line, BUFFER_SIZE, mail_file)) {
325               last_mbox_line_was_empty = 1;
326               fprintf(output_file, "%s", raw_mbox_line);
327               while(1) {
328                 if(!fgets(raw_mbox_line, BUFFER_SIZE, mail_file) ||
329                    (last_mbox_line_was_empty && strncmp(raw_mbox_line, "From ", 5) == 0))
330                   break;
331                 last_mbox_line_was_empty = (raw_mbox_line[0] == '\n');
332                 fprintf(output_file, "%s", raw_mbox_line);
333               }
334             }
335
336             fclose(mail_file);
337           }
338         }
339       }
340
341       for(n = 0; n < nb_search_conditions; n++) { hits[n] = 0; }
342
343       position_in_file_string = mbox_value;
344       mail_filename = segment_next_field(mbox_value);
345       current_position_in_mail = atol(position_in_file_string);
346       strcpy(current_mail_filename, mail_filename);
347
348       remove_eof(current_mail_filename);
349       already_written = 0;
350     }
351
352     else {
353       mbox_id = -1;
354       for(m = 0; (m < MAX_ID) && mbox_id == -1; m++) {
355         if(strncmp(field_names[m], mbox_name, strlen(mbox_name)) == 0) {
356           mbox_id = m;
357         }
358       }
359       for(n = 0; n < nb_search_conditions; n++) {
360         hits[n] |= mbox_line_match_search(&search_conditions[n],
361                                           mbox_id, mbox_value);
362       }
363     }
364   }
365 }
366
367 void recursive_search_in_db(const char *entry_name, regex_t *db_filename_regexp,
368                             int nb_search_conditions,
369                             struct search_condition *search_conditions,
370                             FILE *output_file) {
371   DIR *dir;
372   struct dirent *dir_e;
373   struct stat sb;
374   char raw_db_line[BUFFER_SIZE];
375   char subname[PATH_MAX + 1];
376
377   if(lstat(entry_name, &sb) != 0) {
378     fprintf(stderr,
379             "mymail: Cannot stat \"%s\": %s\n",
380             entry_name,
381             strerror(errno));
382     exit(EXIT_FAILURE);
383   }
384
385   dir = opendir(entry_name);
386
387   if(dir) {
388     while((dir_e = readdir(dir))) {
389       if(!ignore_entry(dir_e->d_name)) {
390         snprintf(subname, PATH_MAX, "%s/%s", entry_name, dir_e->d_name);
391         recursive_search_in_db(subname, db_filename_regexp,
392                                nb_search_conditions, search_conditions,
393                                output_file);
394       }
395     }
396     closedir(dir);
397   }
398
399   else {
400     const char *s = entry_name, *filename = entry_name;
401     while(*s) { if(*s == '/') { filename = s+1; } s++; }
402
403     if(regexec(db_filename_regexp, filename, 0, 0, 0) == 0) {
404       FILE *db_file = fopen(entry_name, "r");
405
406       if(!quiet) {
407         printf("Searching in '%s' ... ", entry_name);
408       }
409
410       if(!db_file) {
411         fprintf(stderr,
412                 "mymail: Cannot open \"%s\" for reading: %s\n",
413                 db_filename,
414                 strerror(errno));
415         exit(EXIT_FAILURE);
416       }
417
418       if(fgets(raw_db_line, BUFFER_SIZE, db_file)) {
419         if(strncmp(raw_db_line, MYMAIL_DB_MAGIC_TOKEN, strlen(MYMAIL_DB_MAGIC_TOKEN))) {
420           fprintf(stderr,
421                   "mymail: Header line in '%s' does not match the mymail db format.\n",
422                   entry_name);
423           exit(EXIT_FAILURE);
424         }
425       } else {
426         fprintf(stderr,
427                 "mymail: Cannot read the header line in '%s'.\n",
428                 entry_name);
429         exit(EXIT_FAILURE);
430       }
431
432       search_in_db(db_file, nb_search_conditions, search_conditions, output_file);
433
434       fclose(db_file);
435
436       if(!quiet) {
437         printf("done.\n");
438       }
439     }
440   }
441 }
442
443 /*********************************************************************/
444
445 void index_one_mbox_line(int nb_fields_to_parse, struct parsable_field *fields_to_parse,
446                          char *raw_mbox_line, FILE *db_file) {
447   regmatch_t matches;
448   int f;
449   for(f = 0; f < nb_fields_to_parse; f++) {
450     if(regexec(&fields_to_parse[f].regexp, raw_mbox_line, 1, &matches, 0) == 0) {
451       fprintf(db_file, "%s %s\n",
452               field_names[fields_to_parse[f].id],
453               raw_mbox_line + matches.rm_eo);
454     }
455   }
456 }
457
458 void index_mbox(const char *mbox_filename,
459                 int nb_fields_to_parse, struct parsable_field *fields_to_parse,
460                 FILE *db_file) {
461   char raw_mbox_line[BUFFER_SIZE], full_line[BUFFER_SIZE];
462   char *end_of_full_line;
463   FILE *file;
464   int in_header, new_header, last_mbox_line_was_empty;
465   unsigned long int position_in_file;
466
467   file = fopen(mbox_filename, "r");
468
469   if(!file) {
470     fprintf(stderr, "mymail: Cannot open '%s'.\n", mbox_filename);
471     if(paranoid) { exit(EXIT_FAILURE); }
472     return;
473   }
474
475   in_header = 0;
476   new_header = 0;
477
478   position_in_file = 0;
479   end_of_full_line = 0;
480   full_line[0] = '\0';
481   last_mbox_line_was_empty = 1;
482
483   while(fgets(raw_mbox_line, BUFFER_SIZE, file)) {
484     if(last_mbox_line_was_empty && strncmp(raw_mbox_line, "From ", 5) == 0) {
485       if(in_header) {
486         fprintf(stderr,
487                 "Got a ^\"From \" in the header in %s:%lu.\n",
488                 mbox_filename, position_in_file);
489         fprintf(stderr, "%s", raw_mbox_line);
490         if(paranoid) { exit(EXIT_FAILURE); }
491       }
492       in_header = 1;
493       new_header = 1;
494     } else if(raw_mbox_line[0] == '\n') {
495       if(in_header) { in_header = 0; }
496     }
497
498     last_mbox_line_was_empty = (raw_mbox_line[0] == '\n');
499
500     if(in_header) {
501       if(new_header) {
502         fprintf(db_file, "mail %lu %s\n", position_in_file, mbox_filename);
503         new_header = 0;
504       }
505
506       if(raw_mbox_line[0] == ' ' || raw_mbox_line[0] == '\t') {
507         char *start = raw_mbox_line;
508         while(*start == ' ' || *start == '\t') start++;
509         *(end_of_full_line++) = ' ';
510         strcpy(end_of_full_line, start);
511         while(*end_of_full_line && *end_of_full_line != '\n') {
512           end_of_full_line++;
513         }
514         *end_of_full_line = '\0';
515       }
516
517       else {
518         /*
519           if(!((raw_mbox_line[0] >= 'a' && raw_mbox_line[0] <= 'z') ||
520           (raw_mbox_line[0] >= 'A' && raw_mbox_line[0] <= 'Z'))) {
521           fprintf(stderr,
522           "Header line syntax error %s:%lu.\n",
523           mbox_filename, position_in_file);
524           fprintf(stderr, "%s", raw_mbox_line);
525           }
526         */
527
528         if(full_line[0]) {
529           index_one_mbox_line(nb_fields_to_parse, fields_to_parse, full_line, db_file);
530         }
531
532         end_of_full_line = full_line;
533         strcpy(end_of_full_line, raw_mbox_line);
534         while(*end_of_full_line && *end_of_full_line != '\n') {
535           end_of_full_line++;
536         }
537         *end_of_full_line = '\0';
538       }
539
540     }
541
542     position_in_file += strlen(raw_mbox_line);
543   }
544
545   fclose(file);
546 }
547
548 void recursive_index_mbox(FILE *db_file,
549                           const char *entry_name,
550                           int nb_fields_to_parse, struct parsable_field *fields_to_parse) {
551   DIR *dir;
552   struct dirent *dir_e;
553   struct stat sb;
554   char subname[PATH_MAX + 1];
555
556   if(lstat(entry_name, &sb) != 0) {
557     fprintf(stderr,
558             "mymail: Cannot stat \"%s\": %s\n",
559             entry_name,
560             strerror(errno));
561     exit(EXIT_FAILURE);
562   }
563
564   dir = opendir(entry_name);
565
566   if(dir) {
567     while((dir_e = readdir(dir))) {
568       if(!ignore_entry(dir_e->d_name)) {
569         snprintf(subname, PATH_MAX, "%s/%s", entry_name, dir_e->d_name);
570         recursive_index_mbox(db_file, subname, nb_fields_to_parse, fields_to_parse);
571       }
572     }
573     closedir(dir);
574   } else {
575     index_mbox(entry_name, nb_fields_to_parse, fields_to_parse, db_file);
576   }
577 }
578
579 /*********************************************************************/
580
581 /* For long options that have no equivalent short option, use a
582    non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
583 enum {
584   OPT_BASH_MODE = CHAR_MAX + 1
585 };
586
587 static struct option long_options[] = {
588   { "help", no_argument, 0, 'h' },
589   { "version", no_argument, 0, 'v' },
590   { "quiet", no_argument, 0, 'q' },
591   { "db-file", 1, 0, 'd' },
592   { "db-pattern", 1, 0, 'p' },
593   { "db-root", 1, 0, 'r' },
594   { "db-list", 1, 0, 'l' },
595   { "search", 1, 0, 's' },
596   { "index", 0, 0, 'i' },
597   { "output", 1, 0, 'o' },
598   { 0, 0, 0, 0 }
599 };
600
601 /*********************************************************************/
602
603 int main(int argc, char **argv) {
604   int error = 0, show_help = 0;
605   const int nb_fields_to_parse = sizeof(fields_to_parse) / sizeof(struct parsable_field);
606   char c;
607   int f;
608   int nb_search_conditions;
609   char *search_condition_strings[MAX_NB_SEARCH_CONDITIONS];
610   FILE *output_file;
611
612   paranoid = 0;
613   action_index = 0;
614   db_filename = 0;
615   db_root_path = 0;
616   db_filename_list = 0;
617   quiet = 0;
618
619   setlocale(LC_ALL, "");
620
621   nb_search_conditions = 0;
622
623   while ((c = getopt_long(argc, argv, "hvqip:s:d:r:l:o:",
624                           long_options, NULL)) != -1) {
625
626     switch(c) {
627
628     case 'h':
629       show_help = 1;
630       break;
631
632     case 'v':
633       print_version(stdout);
634       break;
635
636     case 'q':
637       quiet = 1;
638       break;
639
640     case 'i':
641       action_index = 1;
642       break;
643
644     case 'd':
645       db_filename = strdup(optarg);
646       break;
647
648     case 'p':
649       db_filename_regexp_string = strdup(optarg);
650       break;
651
652     case 'o':
653       strncpy(output_filename, optarg, PATH_MAX);
654       break;
655
656     case 'r':
657       db_root_path = strdup(optarg);
658       break;
659
660     case 'l':
661       db_filename_list = strdup(optarg);
662       break;
663
664     case 's':
665       if(nb_search_conditions == MAX_NB_SEARCH_CONDITIONS) {
666         fprintf(stderr, "mymail: Too many search patterns.\n");
667         exit(EXIT_FAILURE);
668       }
669       search_condition_strings[nb_search_conditions++] = strdup(optarg);
670       break;
671
672     default:
673       error = 1;
674       break;
675     }
676   }
677
678   if(!db_filename) {
679     char *default_db_filename = getenv("MYMAIL_DB_FILE");
680
681     if(!default_db_filename) {
682       default_db_filename = "mymail.db";
683     }
684
685     db_filename = strdup(default_db_filename);
686   }
687
688   if(!db_filename_regexp_string) {
689     char *default_db_filename_regexp_string = getenv("MYMAIL_DB_PATTERN");
690
691     if(!default_db_filename_regexp_string) {
692       default_db_filename_regexp_string = "^mymail.db$";
693     }
694
695     db_filename_regexp_string = strdup(default_db_filename_regexp_string);
696   }
697
698   if(!db_root_path) {
699     char *default_db_root_path = getenv("MYMAIL_DB_ROOT");
700
701     if(default_db_root_path) {
702       db_root_path = strdup(default_db_root_path);
703     }
704   }
705
706   if(!db_filename_list) {
707     char *default_db_filename_list = getenv("MYMAIL_DB_LIST");
708
709     if(default_db_filename_list) {
710       db_filename_list = strdup(default_db_filename_list);
711     }
712   }
713
714   if(output_filename[0]) {
715     output_file = fopen(output_filename, "w");
716
717     if(!output_file) {
718       fprintf(stderr,
719               "mymail: Cannot open result file \"%s\" for writing: %s\n",
720               output_filename,
721               strerror(errno));
722       exit(EXIT_FAILURE);
723     }
724   } else {
725     output_file = stdout;
726     quiet = 1;
727   }
728
729   if(error) {
730     print_usage(stderr);
731     exit(EXIT_FAILURE);
732   }
733
734   if(show_help) {
735     print_usage(stdout);
736     exit(EXIT_SUCCESS);
737   }
738
739   if(action_index) {
740     FILE *db_file;
741
742     db_file = fopen(db_filename, "w");
743
744     if(!db_file) {
745       fprintf(stderr,
746               "mymail: Cannot open \"%s\" for writing: %s\n",
747               db_filename,
748               strerror(errno));
749       exit(EXIT_FAILURE);
750     }
751
752     for(f = 0; f < nb_fields_to_parse; f++) {
753       if(regcomp(&fields_to_parse[f].regexp,
754                  fields_to_parse[f].regexp_string,
755                  REG_ICASE)) {
756         fprintf(stderr,
757                 "mymail: Syntax error in regexp \"%s\" for field \"%s\".\n",
758                 fields_to_parse[f].regexp_string,
759                 field_names[fields_to_parse[f].id]);
760         exit(EXIT_FAILURE);
761       }
762     }
763
764     fprintf(db_file, "%s version_%s raw\n", MYMAIL_DB_MAGIC_TOKEN, VERSION);
765
766     while(optind < argc) {
767       recursive_index_mbox(db_file,
768                            argv[optind],
769                            nb_fields_to_parse, fields_to_parse);
770       optind++;
771     }
772
773     fclose(db_file);
774
775     for(f = 0; f < nb_fields_to_parse; f++) {
776       regfree(&fields_to_parse[f].regexp);
777     }
778   }
779
780   else {
781
782     if(nb_search_conditions > 0) {
783       struct search_condition search_conditions[MAX_NB_SEARCH_CONDITIONS];
784       char *search_field, *search_regexp_string;
785       int m, n;
786
787       for(n = 0; n < nb_search_conditions; n++) {
788         search_field = search_condition_strings[n];
789         search_regexp_string = segment_next_field(search_condition_strings[n]);
790
791         if(search_field[0] == '!') {
792           search_field++;
793           search_conditions[n].negation = 1;
794         } else {
795           search_conditions[n].negation = 0;
796         }
797
798         search_conditions[n].field_id = -1;
799         for(m = 0; (m < MAX_ID) && search_conditions[n].field_id == -1; m++) {
800           if(strncmp(field_names[m], search_field, strlen(search_field)) == 0) {
801             search_conditions[n].field_id = m;
802           }
803         }
804
805         if(search_conditions[n].field_id == -1) {
806           fprintf(stderr,
807                   "mymail: Syntax error in field name \"%s\".\n",
808                   search_field);
809           exit(EXIT_FAILURE);
810         }
811
812         if(regcomp(&search_conditions[n].regexp,
813                    search_regexp_string,
814                    REG_ICASE)) {
815           fprintf(stderr,
816                   "mymail: Syntax error in regexp \"%s\" for field \"%s\".\n",
817                   search_regexp_string,
818                   field_names[search_conditions[n].field_id]);
819           exit(EXIT_FAILURE);
820         }
821       }
822
823       /* Recursive search if db_root_path is set */
824
825       if(db_root_path) {
826         regex_t db_filename_regexp;
827         if(regcomp(&db_filename_regexp,
828                    db_filename_regexp_string,
829                    0)) {
830           fprintf(stderr,
831                   "mymail: Syntax error in regexp \"%s\".\n",
832                   db_filename_regexp_string);
833           exit(EXIT_FAILURE);
834         }
835
836         recursive_search_in_db(db_root_path, &db_filename_regexp,
837                                nb_search_conditions, search_conditions,
838                                output_file);
839
840         regfree(&db_filename_regexp);
841       }
842
843       /* Search in all db files listed in db_filename_list */
844
845       if(db_filename_list) {
846         char db_filename[PATH_MAX + 1];
847         char *s, *t;
848         FILE *db_file;
849
850         s = db_filename_list;
851
852         while(*s) {
853           t = db_filename;
854           while(*s == ';') { s++; }
855           while(*s && *s != ';') { *t++ = *s++; }
856           *t++ = '\0';
857
858           if(db_filename[0]) {
859             db_file = fopen(db_filename, "r");
860
861             if(!db_file) {
862               fprintf(stderr,
863                       "mymail: Cannot open \"%s\" for reading: %s\n",
864                       argv[optind],
865                       strerror(errno));
866               exit(EXIT_FAILURE);
867             }
868
869             search_in_db(db_file, nb_search_conditions, search_conditions, output_file);
870
871             fclose(db_file);
872           }
873         }
874       }
875
876       /* Search in all db files listed in the command arguments */
877
878       while(optind < argc) {
879         FILE *db_file = fopen(argv[optind], "r");
880
881         if(!db_file) {
882           fprintf(stderr,
883                   "mymail: Cannot open \"%s\" for reading: %s\n",
884                   argv[optind],
885                   strerror(errno));
886           exit(EXIT_FAILURE);
887         }
888
889         search_in_db(db_file, nb_search_conditions, search_conditions, output_file);
890
891         fclose(db_file);
892         optind++;
893       }
894
895       for(n = 0; n < nb_search_conditions; n++) {
896         regfree(&search_conditions[n].regexp);
897         free(search_condition_strings[n]);
898       }
899     }
900   }
901
902   if(output_file != stdout) {
903     fclose(output_file);
904   }
905
906   free(db_filename);
907   free(db_filename_regexp_string);
908   free(db_root_path);
909   free(db_filename_list);
910
911   exit(EXIT_SUCCESS);
912 }