d86c0424a4920bcd712c5d72f8822ef4e195a133
[notmuch] / notmuch-config.c
1 /* notmuch - Not much of an email program, (just index and search)
2  *
3  * Copyright © 2009 Carl Worth
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see http://www.gnu.org/licenses/ .
17  *
18  * Author: Carl Worth <cworth@cworth.org>
19  */
20
21 #include "notmuch-client.h"
22
23 #include <pwd.h>
24 #include <netdb.h>
25
26 static const char toplevel_config_comment[] =
27     " .notmuch-config - Configuration file for the notmuch mail system\n"
28     "\n"
29     " For more information about notmuch, see http://notmuchmail.org";
30
31 static const char database_config_comment[] =
32     " Database configuration\n"
33     "\n"
34     " The only value supported here is 'path' which should be the top-level\n"
35     " directory where your mail currently exists and to where mail will be\n"
36     " delivered in the future. Files should be individual email messages.\n"
37     " Notmuch will store its database within a sub-directory of the path\n"
38     " configured here named \".notmuch\".\n";
39
40 static const char new_config_comment[] =
41     " Configuration for \"notmuch new\"\n"
42     "\n"
43     " The following options are supported here:\n"
44     "\n"
45     "\ttags     A list (separated by ';') of the tags that will be\n"
46     "\t added to all messages incorporated by \"notmuch new\".\n";
47
48 static const char user_config_comment[] =
49     " User configuration\n"
50     "\n"
51     " Here is where you can let notmuch know how you would like to be\n"
52     " addressed. Valid settings are\n"
53     "\n"
54     "\tname             Your full name.\n"
55     "\tprimary_email    Your primary email address.\n"
56     "\tother_email      A list (separated by ';') of other email addresses\n"
57     "\t         at which you receive email.\n"
58     "\n"
59     " Notmuch will use the various email addresses configured here when\n"
60     " formatting replies. It will avoid including your own addresses in the\n"
61     " recipient list of replies, and will set the From address based on the\n"
62     " address to which the original email was addressed.\n";
63
64 static const char maildir_config_comment[] =
65     " Maildir compatibility configuration\n"
66     "\n"
67     " The following option is supported here:\n"
68     "\n"
69     "\tsynchronize_flags      Valid values are true and false.\n"
70     "\n"
71     "\tIf true, then the following maildir flags (in message filenames)\n"
72     "\twill be syncrhonized with the corresponding notmuch tags:\n"
73     "\n"
74     "\t\tFlag   Tag\n"
75     "\t\t----   -------\n"
76     "\t\tD      draft\n"
77     "\t\tF      flagged\n"
78     "\t\tP      passed\n"
79     "\t\tR      replied\n"
80     "\t\tS      unread (added when 'S' flag is not present)\n"
81     "\n"
82     "\tThe \"notmuch new\" command will notice flag changes in filenames\n"
83     "\tand update tags, while the \"notmuch tag\" and \"notmuch restore\"\n"
84     "\tcommands will notice tag changes and update flags in filenames\n";
85
86 struct _notmuch_config {
87     char *filename;
88     GKeyFile *key_file;
89
90     char *database_path;
91     char *user_name;
92     char *user_primary_email;
93     const char **user_other_email;
94     size_t user_other_email_length;
95     const char **new_tags;
96     size_t new_tags_length;
97     notmuch_bool_t maildir_synchronize_flags;
98 };
99
100 static int
101 notmuch_config_destructor (notmuch_config_t *config)
102 {
103     if (config->key_file)
104         g_key_file_free (config->key_file);
105
106     return 0;
107 }
108
109 static char *
110 get_name_from_passwd_file (void *ctx)
111 {
112     long pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
113     char *pw_buf = talloc_zero_size (ctx, pw_buf_size);
114     struct passwd passwd, *ignored;
115     char *name;
116     int e;
117
118     if (pw_buf_size == -1) pw_buf_size = 64;
119
120     while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
121                             pw_buf_size, &ignored)) == ERANGE) {
122         pw_buf_size = pw_buf_size * 2;
123         pw_buf = talloc_zero_size(ctx, pw_buf_size);
124     }
125
126     if (e == 0) {
127         char *comma = strchr (passwd.pw_gecos, ',');
128         if (comma)
129             name = talloc_strndup (ctx, passwd.pw_gecos,
130                                    comma - passwd.pw_gecos);
131         else
132             name = talloc_strdup (ctx, passwd.pw_gecos);
133     } else {
134         name = talloc_strdup (ctx, "");
135     }
136
137     talloc_free (pw_buf);
138
139     return name;
140 }
141
142 static char *
143 get_username_from_passwd_file (void *ctx)
144 {
145     long pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
146     char *pw_buf = talloc_zero_size (ctx, pw_buf_size);
147     struct passwd passwd, *ignored;
148     char *name;
149     int e;
150
151     if (pw_buf_size == -1) pw_buf_size = 64;
152     while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
153                             pw_buf_size, &ignored)) == ERANGE) {
154         pw_buf_size = pw_buf_size * 2;
155         pw_buf = talloc_zero_size(ctx, pw_buf_size);
156     }
157
158     if (e == 0)
159         name = talloc_strdup (ctx, passwd.pw_name);
160     else
161         name = talloc_strdup (ctx, "");
162
163     talloc_free (pw_buf);
164
165     return name;
166 }
167
168 /* Open the named notmuch configuration file. If the filename is NULL,
169  * the value of the environment variable $NOTMUCH_CONFIG will be used.
170  * If $NOTMUCH_CONFIG is unset, the default configuration file
171  * ($HOME/.notmuch-config) will be used.
172  *
173  * If any error occurs, (out of memory, or a permission-denied error,
174  * etc.), this function will print a message to stderr and return
175  * NULL.
176  *
177  * FILE NOT FOUND: When the specified configuration file (whether from
178  * 'filename' or the $NOTMUCH_CONFIG environment variable) does not
179  * exist, the behavior of this function depends on the 'is_new_ret'
180  * variable.
181  *
182  *      If is_new_ret is NULL, then a "file not found" message will be
183  *      printed to stderr and NULL will be returned.
184
185  *      If is_new_ret is non-NULL then a default configuration will be
186  *      returned and *is_new_ret will be set to 1 on return so that
187  *      the caller can recognize this case.
188  *
189  *      These default configuration settings are determined as
190  *      follows:
191  *
192  *              database_path:          $HOME/mail
193  *
194  *              user_name:              From /etc/passwd
195  *
196  *              user_primary_mail:      $EMAIL variable if set, otherwise
197  *                                      constructed from the username and
198  *                                      hostname of the current machine.
199  *
200  *              user_other_email:       Not set.
201  *
202  *      The default configuration also contains comments to guide the
203  *      user in editing the file directly.
204  */
205 notmuch_config_t *
206 notmuch_config_open (void *ctx,
207                      const char *filename,
208                      notmuch_bool_t *is_new_ret)
209 {
210     GError *error = NULL;
211     int is_new = 0;
212     size_t tmp;
213     char *notmuch_config_env = NULL;
214     int file_had_database_group;
215     int file_had_new_group;
216     int file_had_user_group;
217     int file_had_maildir_group;
218
219     if (is_new_ret)
220         *is_new_ret = 0;
221
222     notmuch_config_t *config = talloc (ctx, notmuch_config_t);
223     if (config == NULL) {
224         fprintf (stderr, "Out of memory.\n");
225         return NULL;
226     }
227     
228     talloc_set_destructor (config, notmuch_config_destructor);
229
230     if (filename) {
231         config->filename = talloc_strdup (config, filename);
232     } else if ((notmuch_config_env = getenv ("NOTMUCH_CONFIG"))) {
233         config->filename = talloc_strdup (config, notmuch_config_env);
234     } else {
235         config->filename = talloc_asprintf (config, "%s/.notmuch-config",
236                                             getenv ("HOME"));
237     }
238
239     config->key_file = g_key_file_new ();
240
241     config->database_path = NULL;
242     config->user_name = NULL;
243     config->user_primary_email = NULL;
244     config->user_other_email = NULL;
245     config->user_other_email_length = 0;
246     config->new_tags = NULL;
247     config->new_tags_length = 0;
248     config->maildir_synchronize_flags = TRUE;
249
250     if (! g_key_file_load_from_file (config->key_file,
251                                      config->filename,
252                                      G_KEY_FILE_KEEP_COMMENTS,
253                                      &error))
254     {
255         /* If the caller passed a non-NULL value for is_new_ret, then
256          * the caller is prepared for a default configuration file in
257          * the case of FILE NOT FOUND. Otherwise, any read failure is
258          * an error.
259          */
260         if (is_new_ret &&
261             error->domain == G_FILE_ERROR &&
262             error->code == G_FILE_ERROR_NOENT)
263         {
264             g_error_free (error);
265             is_new = 1;
266         }
267         else
268         {
269             fprintf (stderr, "Error reading configuration file %s: %s\n",
270                      config->filename, error->message);
271             talloc_free (config);
272             g_error_free (error);
273             return NULL;
274         }
275     }
276
277     /* Whenever we know of configuration sections that don't appear in
278      * the configuration file, we add some comments to help the user
279      * understand what can be done.
280      *
281      * It would be convenient to just add those comments now, but
282      * apparently g_key_file will clear any comments when keys are
283      * added later that create the groups. So we have to check for the
284      * groups now, but add the comments only after setting all of our
285      * values.
286      */
287     file_had_database_group = g_key_file_has_group (config->key_file,
288                                                     "database");
289     file_had_new_group = g_key_file_has_group (config->key_file, "new");
290     file_had_user_group = g_key_file_has_group (config->key_file, "user");
291     file_had_maildir_group = g_key_file_has_group (config->key_file, "maildir");
292
293
294     if (notmuch_config_get_database_path (config) == NULL) {
295         char *path = talloc_asprintf (config, "%s/mail",
296                                       getenv ("HOME"));
297         notmuch_config_set_database_path (config, path);
298         talloc_free (path);
299     }
300
301     if (notmuch_config_get_user_name (config) == NULL) {
302         char *name = get_name_from_passwd_file (config);
303         notmuch_config_set_user_name (config, name);
304         talloc_free (name);
305     }
306
307     if (notmuch_config_get_user_primary_email (config) == NULL) {
308         char *email = getenv ("EMAIL");
309         if (email) {
310             notmuch_config_set_user_primary_email (config, email);
311         } else {
312             char hostname[256];
313             struct hostent *hostent;
314             const char *domainname;
315
316             char *username = get_username_from_passwd_file (config);
317
318             gethostname (hostname, 256);
319             hostname[255] = '\0';
320
321             hostent = gethostbyname (hostname);
322             if (hostent && (domainname = strchr (hostent->h_name, '.')))
323                 domainname += 1;
324             else
325                 domainname = "(none)";
326
327             email = talloc_asprintf (config, "%s@%s.%s",
328                                      username, hostname, domainname);
329
330             notmuch_config_set_user_primary_email (config, email);
331
332             talloc_free (username);
333             talloc_free (email);
334         }
335     }
336
337     if (notmuch_config_get_new_tags (config, &tmp) == NULL) {
338         const char *tags[] = { "unread", "inbox" };
339         notmuch_config_set_new_tags (config, tags, 2);
340     }
341
342     error = NULL;
343     config->maildir_synchronize_flags =
344         g_key_file_get_boolean (config->key_file,
345                                 "maildir", "synchronize_flags", &error);
346     if (error) {
347         notmuch_config_set_maildir_synchronize_flags (config, TRUE);
348         g_error_free (error);
349     }
350
351     /* Whenever we know of configuration sections that don't appear in
352      * the configuration file, we add some comments to help the user
353      * understand what can be done. */
354     if (is_new)
355     {
356         g_key_file_set_comment (config->key_file, NULL, NULL,
357                                 toplevel_config_comment, NULL);
358     }
359
360     if (! file_had_database_group)
361     {
362         g_key_file_set_comment (config->key_file, "database", NULL,
363                                 database_config_comment, NULL);
364     }
365
366     if (! file_had_new_group)
367     {
368         g_key_file_set_comment (config->key_file, "new", NULL,
369                                 new_config_comment, NULL);
370     }
371
372     if (! file_had_user_group)
373     {
374         g_key_file_set_comment (config->key_file, "user", NULL,
375                                 user_config_comment, NULL);
376     }
377
378     if (! file_had_maildir_group)
379     {
380         g_key_file_set_comment (config->key_file, "maildir", NULL,
381                                 maildir_config_comment, NULL);
382     }
383
384     if (is_new_ret)
385         *is_new_ret = is_new;
386
387     return config;
388 }
389
390 /* Close the given notmuch_config_t object, freeing all resources.
391  * 
392  * Note: Any changes made to the configuration are *not* saved by this
393  * function. To save changes, call notmuch_config_save before
394  * notmuch_config_close.
395 */
396 void
397 notmuch_config_close (notmuch_config_t *config)
398 {
399     talloc_free (config);
400 }
401
402 /* Save any changes made to the notmuch configuration.
403  *
404  * Any comments originally in the file will be preserved.
405  *
406  * Returns 0 if successful, and 1 in case of any error, (after
407  * printing a description of the error to stderr).
408  */
409 int
410 notmuch_config_save (notmuch_config_t *config)
411 {
412     size_t length;
413     char *data;
414     GError *error = NULL;
415
416     data = g_key_file_to_data (config->key_file, &length, NULL);
417     if (data == NULL) {
418         fprintf (stderr, "Out of memory.\n");
419         return 1;
420     }
421
422     if (! g_file_set_contents (config->filename, data, length, &error)) {
423         fprintf (stderr, "Error saving configuration to %s: %s\n",
424                  config->filename, error->message);
425         g_error_free (error);
426         g_free (data);
427         return 1;
428     }
429
430     g_free (data);
431     return 0;
432 }
433
434 const char *
435 notmuch_config_get_database_path (notmuch_config_t *config)
436 {
437     char *path;
438
439     if (config->database_path == NULL) {
440         path = g_key_file_get_string (config->key_file,
441                                       "database", "path", NULL);
442         if (path) {
443             config->database_path = talloc_strdup (config, path);
444             free (path);
445         }
446     }
447
448     return config->database_path;
449 }
450
451 void
452 notmuch_config_set_database_path (notmuch_config_t *config,
453                                   const char *database_path)
454 {
455     g_key_file_set_string (config->key_file,
456                            "database", "path", database_path);
457
458     talloc_free (config->database_path);
459     config->database_path = NULL;
460 }
461
462 const char *
463 notmuch_config_get_user_name (notmuch_config_t *config)
464 {
465     char *name;
466
467     if (config->user_name == NULL) {
468         name = g_key_file_get_string (config->key_file,
469                                       "user", "name", NULL);
470         if (name) {
471             config->user_name = talloc_strdup (config, name);
472             free (name);
473         }
474     }
475
476     return config->user_name;
477 }
478
479 void
480 notmuch_config_set_user_name (notmuch_config_t *config,
481                               const char *user_name)
482 {
483     g_key_file_set_string (config->key_file,
484                            "user", "name", user_name);
485
486     talloc_free (config->user_name);
487     config->user_name = NULL;
488 }
489
490 const char *
491 notmuch_config_get_user_primary_email (notmuch_config_t *config)
492 {
493     char *email;
494
495     if (config->user_primary_email == NULL) {
496         email = g_key_file_get_string (config->key_file,
497                                        "user", "primary_email", NULL);
498         if (email) {
499             config->user_primary_email = talloc_strdup (config, email);
500             free (email);
501         }
502     }
503
504     return config->user_primary_email;
505 }
506
507 void
508 notmuch_config_set_user_primary_email (notmuch_config_t *config,
509                                        const char *primary_email)
510 {
511     g_key_file_set_string (config->key_file,
512                            "user", "primary_email", primary_email);
513
514     talloc_free (config->user_primary_email);
515     config->user_primary_email = NULL;
516 }
517
518 const char **
519 notmuch_config_get_user_other_email (notmuch_config_t *config,
520                                      size_t *length)
521 {
522     char **emails;
523     size_t emails_length;
524     unsigned int i;
525
526     if (config->user_other_email == NULL) {
527         emails = g_key_file_get_string_list (config->key_file,
528                                              "user", "other_email",
529                                              &emails_length, NULL);
530         if (emails) {
531             config->user_other_email = talloc_size (config,
532                                                     sizeof (char *) *
533                                                     (emails_length + 1));
534             for (i = 0; i < emails_length; i++)
535                 config->user_other_email[i] = talloc_strdup (config->user_other_email,
536                                                              emails[i]);
537             config->user_other_email[i] = NULL;
538
539             g_strfreev (emails);
540
541             config->user_other_email_length = emails_length;
542         }
543     }
544
545     *length = config->user_other_email_length;
546     return config->user_other_email;
547 }
548
549 void
550 notmuch_config_set_user_other_email (notmuch_config_t *config,
551                                      const char *other_email[],
552                                      size_t length)
553 {
554     g_key_file_set_string_list (config->key_file,
555                                 "user", "other_email",
556                                 other_email, length);
557
558     talloc_free (config->user_other_email);
559     config->user_other_email = NULL;
560 }
561
562 const char **
563 notmuch_config_get_new_tags (notmuch_config_t *config,
564                              size_t *length)
565 {
566     char **tags;
567     size_t tags_length;
568     unsigned int i;
569
570     if (config->new_tags == NULL) {
571         tags = g_key_file_get_string_list (config->key_file,
572                                            "new", "tags",
573                                            &tags_length, NULL);
574         if (tags) {
575             config->new_tags = talloc_size (config,
576                                             sizeof (char *) *
577                                             (tags_length + 1));
578             for (i = 0; i < tags_length; i++)
579                 config->new_tags[i] = talloc_strdup (config->new_tags,
580                                                      tags[i]);
581             config->new_tags[i] = NULL;
582
583             g_strfreev (tags);
584
585             config->new_tags_length = tags_length;
586         }
587     }
588
589     *length = config->new_tags_length;
590     return config->new_tags;
591 }
592
593 void
594 notmuch_config_set_new_tags (notmuch_config_t *config,
595                              const char *new_tags[],
596                              size_t length)
597 {
598     g_key_file_set_string_list (config->key_file,
599                                 "new", "tags",
600                                 new_tags, length);
601
602     talloc_free (config->new_tags);
603     config->new_tags = NULL;
604 }
605
606 /* Given a configuration item of the form <group>.<key> return the
607  * component group and key. If any error occurs, print a message on
608  * stderr and return 1. Otherwise, return 0.
609  *
610  * Note: This function modifies the original 'item' string.
611  */
612 static int
613 _item_split (char *item, char **group, char **key)
614 {
615     char *period;
616
617     *group = item;
618
619     period = index (item, '.');
620     if (period == NULL || *(period+1) == '\0') {
621         fprintf (stderr,
622                  "Invalid configuration name: %s\n"
623                  "(Should be of the form <section>.<item>)\n", item);
624         return 1;
625     }
626
627     *period = '\0';
628     *key = period + 1;
629
630     return 0;
631 }
632
633 static int
634 notmuch_config_command_get (void *ctx, char *item)
635 {
636     notmuch_config_t *config;
637
638     config = notmuch_config_open (ctx, NULL, NULL);
639     if (config == NULL)
640         return 1;
641
642     if (strcmp(item, "database.path") == 0) {
643         printf ("%s\n", notmuch_config_get_database_path (config));
644     } else if (strcmp(item, "user.name") == 0) {
645         printf ("%s\n", notmuch_config_get_user_name (config));
646     } else if (strcmp(item, "user.primary_email") == 0) {
647         printf ("%s\n", notmuch_config_get_user_primary_email (config));
648     } else if (strcmp(item, "user.other_email") == 0) {
649         const char **other_email;
650         size_t i, length;
651         
652         other_email = notmuch_config_get_user_other_email (config, &length);
653         for (i = 0; i < length; i++)
654             printf ("%s\n", other_email[i]);
655     } else if (strcmp(item, "new.tags") == 0) {
656         const char **tags;
657         size_t i, length;
658
659         tags = notmuch_config_get_new_tags (config, &length);
660         for (i = 0; i < length; i++)
661             printf ("%s\n", tags[i]);
662     } else {
663         char **value;
664         size_t i, length;
665         char *group, *key;
666
667         if (_item_split (item, &group, &key))
668             return 1;
669
670         value = g_key_file_get_string_list (config->key_file,
671                                             group, key,
672                                             &length, NULL);
673         if (value == NULL) {
674             fprintf (stderr, "Unknown configuration item: %s.%s\n",
675                      group, key);
676             return 1;
677         }
678
679         for (i = 0; i < length; i++)
680             printf ("%s\n", value[i]);
681
682         free (value);
683     }
684
685     notmuch_config_close (config);
686
687     return 0;
688 }
689
690 static int
691 notmuch_config_command_set (void *ctx, char *item, int argc, char *argv[])
692 {
693     notmuch_config_t *config;
694     char *group, *key;
695     int ret;
696
697     if (_item_split (item, &group, &key))
698         return 1;
699
700     config = notmuch_config_open (ctx, NULL, NULL);
701     if (config == NULL)
702         return 1;
703
704     /* With only the name of an item, we clear it from the
705      * configuration file.
706      *
707      * With a single value, we set it as a string.
708      *
709      * With multiple values, we set them as a string list.
710      */
711     switch (argc) {
712     case 0:
713         g_key_file_remove_key (config->key_file, group, key, NULL);
714         break;
715     case 1:
716         g_key_file_set_string (config->key_file, group, key, argv[0]);
717         break;
718     default:
719         g_key_file_set_string_list (config->key_file, group, key,
720                                     (const gchar **) argv, argc);
721         break;
722     }
723
724     ret = notmuch_config_save (config);
725     notmuch_config_close (config);
726
727     return ret;
728 }
729
730 int
731 notmuch_config_command (void *ctx, int argc, char *argv[])
732 {
733     if (argc < 2) {
734         fprintf (stderr, "Error: notmuch config requires at least two arguments.\n");
735         return 1;
736     }
737
738     if (strcmp (argv[0], "get") == 0)
739         return notmuch_config_command_get (ctx, argv[1]);
740     else if (strcmp (argv[0], "set") == 0)
741         return notmuch_config_command_set (ctx, argv[1], argc - 2, argv + 2);
742
743     fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
744              argv[0]);
745     return 1;
746 }
747
748 notmuch_bool_t
749 notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
750 {
751     return config->maildir_synchronize_flags;
752 }
753
754 void
755 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
756                                               notmuch_bool_t synchronize_flags)
757 {
758     g_key_file_set_boolean (config->key_file,
759                             "maildir", "synchronize_flags", synchronize_flags);
760     config->maildir_synchronize_flags = synchronize_flags;
761 }