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