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