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 https://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 #include "path-util.h"
28 #include "unicode-util.h"
29
30 static const char toplevel_config_comment[] =
31 " .notmuch-config - Configuration file for the notmuch mail system\n"
32 "\n"
33 " For more information about notmuch, see https://notmuchmail.org";
34
35 static const struct config_group {
36 const char *group_name;
37 const char *comment;
38 } group_comment_table [] = {
39 {
40 "database",
41 " Database configuration\n"
42 "\n"
43 " The only value supported here is 'path' which should be the top-level\n"
44 " directory where your mail currently exists and to where mail will be\n"
45 " delivered in the future. Files should be individual email messages.\n"
46 " Notmuch will store its database within a sub-directory of the path\n"
47 " configured here named \".notmuch\".\n"
48 },
49 {
50 "user",
51 " User configuration\n"
52 "\n"
53 " Here is where you can let notmuch know how you would like to be\n"
54 " addressed. Valid settings are\n"
55 "\n"
56 "\tname Your full name.\n"
57 "\tprimary_email Your primary email address.\n"
58 "\tother_email A list (separated by ';') of other email addresses\n"
59 "\t at which you receive email.\n"
60 "\n"
61 " Notmuch will use the various email addresses configured here when\n"
62 " formatting replies. It will avoid including your own addresses in the\n"
63 " recipient list of replies, and will set the From address based on the\n"
64 " address to which the original email was addressed.\n"
65 },
66 {
67 "new",
68 " Configuration for \"notmuch new\"\n"
69 "\n"
70 " The following options are supported here:\n"
71 "\n"
72 "\ttags A list (separated by ';') of the tags that will be\n"
73 "\t added to all messages incorporated by \"notmuch new\".\n"
74 "\n"
75 "\tignore A list (separated by ';') of file and directory names\n"
76 "\t that will not be searched for messages by \"notmuch new\".\n"
77 "\n"
78 "\t NOTE: *Every* file/directory that goes by one of those\n"
79 "\t names will be ignored, independent of its depth/location\n"
80 "\t in the mail store.\n"
81 },
82 {
83 "search",
84 " Search configuration\n"
85 "\n"
86 " The following option is supported here:\n"
87 "\n"
88 "\texclude_tags\n"
89 "\t\tA ;-separated list of tags that will be excluded from\n"
90 "\t\tsearch results by default. Using an excluded tag in a\n"
91 "\t\tquery will override that exclusion.\n"
92 },
93 {
94 "maildir",
95 " Maildir compatibility configuration\n"
96 "\n"
97 " The following option is supported here:\n"
98 "\n"
99 "\tsynchronize_flags Valid values are true and false.\n"
100 "\n"
101 "\tIf true, then the following maildir flags (in message filenames)\n"
102 "\twill be synchronized with the corresponding notmuch tags:\n"
103 "\n"
104 "\t\tFlag Tag\n"
105 "\t\t---- -------\n"
106 "\t\tD draft\n"
107 "\t\tF flagged\n"
108 "\t\tP passed\n"
109 "\t\tR replied\n"
110 "\t\tS unread (added when 'S' flag is not present)\n"
111 "\n"
112 "\tThe \"notmuch new\" command will notice flag changes in filenames\n"
113 "\tand update tags, while the \"notmuch tag\" and \"notmuch restore\"\n"
114 "\tcommands will notice tag changes and update flags in filenames\n"
115 },
116 };
117
118 struct _notmuch_conffile {
119 char *filename;
120 GKeyFile *key_file;
121 bool is_new;
122 };
123
124 static int
notmuch_conffile_destructor(notmuch_conffile_t * config)125 notmuch_conffile_destructor (notmuch_conffile_t *config)
126 {
127 if (config->key_file)
128 g_key_file_free (config->key_file);
129
130 return 0;
131 }
132
133 static bool
get_config_from_file(notmuch_conffile_t * config,bool create_new)134 get_config_from_file (notmuch_conffile_t *config, bool create_new)
135 {
136 #define BUF_SIZE 4096
137 char *config_str = NULL;
138 int config_len = 0;
139 int config_bufsize = BUF_SIZE;
140 size_t len;
141 GError *error = NULL;
142 bool ret = false;
143
144 FILE *fp = fopen (config->filename, "r");
145 if (fp == NULL) {
146 if (errno == ENOENT) {
147 /* If create_new is true, then the caller is prepared for a
148 * default configuration file in the case of FILE NOT FOUND.
149 */
150 if (create_new) {
151 config->is_new = true;
152 ret = true;
153 } else {
154 fprintf (stderr, "Configuration file %s not found.\n"
155 "Try running 'notmuch setup' to create a configuration.\n",
156 config->filename);
157 }
158 } else {
159 fprintf (stderr, "Error opening config file '%s': %s\n",
160 config->filename, strerror (errno));
161 }
162 goto out;
163 }
164
165 config_str = talloc_zero_array (config, char, config_bufsize);
166 if (config_str == NULL) {
167 fprintf (stderr, "Error reading '%s': Out of memory\n", config->filename);
168 goto out;
169 }
170
171 while ((len = fread (config_str + config_len, 1,
172 config_bufsize - config_len, fp)) > 0) {
173 config_len += len;
174 if (config_len == config_bufsize) {
175 config_bufsize += BUF_SIZE;
176 config_str = talloc_realloc (config, config_str, char, config_bufsize);
177 if (config_str == NULL) {
178 fprintf (stderr, "Error reading '%s': Failed to reallocate memory\n",
179 config->filename);
180 goto out;
181 }
182 }
183 }
184
185 if (ferror (fp)) {
186 fprintf (stderr, "Error reading '%s': I/O error\n", config->filename);
187 goto out;
188 }
189
190 if (g_key_file_load_from_data (config->key_file, config_str, config_len,
191 G_KEY_FILE_KEEP_COMMENTS, &error)) {
192 ret = true;
193 goto out;
194 }
195
196 fprintf (stderr, "Error parsing config file '%s': %s\n",
197 config->filename, error->message);
198
199 g_error_free (error);
200
201 out:
202 if (fp)
203 fclose (fp);
204
205 if (config_str)
206 talloc_free (config_str);
207
208 return ret;
209 }
210
211 /* Open the named notmuch configuration file. If the filename is NULL,
212 * the value of the environment variable $NOTMUCH_CONFIG will be used.
213 * If $NOTMUCH_CONFIG is unset, the default configuration file
214 * ($HOME/.notmuch-config) will be used.
215 *
216 * If any error occurs, (out of memory, or a permission-denied error,
217 * etc.), this function will print a message to stderr and return
218 * NULL.
219 *
220 * FILE NOT FOUND: When the specified configuration file (whether from
221 * 'filename' or the $NOTMUCH_CONFIG environment variable) does not
222 * exist, the behavior of this function depends on the 'is_new_ret'
223 * variable.
224 *
225 * If is_new_ret is NULL, then a "file not found" message will be
226 * printed to stderr and NULL will be returned.
227 *
228 * If is_new_ret is non-NULL then a default configuration will be
229 * returned and *is_new_ret will be set to 1 on return so that
230 * the caller can recognize this case.
231 *
232 * These default configuration settings are determined as
233 * follows:
234 *
235 * database_path: $MAILDIR, otherwise $HOME/mail
236 *
237 * user_name: $NAME variable if set, otherwise
238 * read from /etc/passwd
239 *
240 * user_primary_mail: $EMAIL variable if set, otherwise
241 * constructed from the username and
242 * hostname of the current machine.
243 *
244 * user_other_email: Not set.
245 *
246 * The default configuration also contains comments to guide the
247 * user in editing the file directly.
248 */
249 notmuch_conffile_t *
notmuch_conffile_open(notmuch_database_t * notmuch,const char * filename,bool create)250 notmuch_conffile_open (notmuch_database_t *notmuch,
251 const char *filename,
252 bool create)
253 {
254 char *notmuch_config_env = NULL;
255
256 notmuch_conffile_t *config = talloc_zero (notmuch, notmuch_conffile_t);
257
258 if (config == NULL) {
259 fprintf (stderr, "Out of memory.\n");
260 return NULL;
261 }
262
263 talloc_set_destructor (config, notmuch_conffile_destructor);
264
265 if (filename) {
266 config->filename = talloc_strdup (config, filename);
267 } else if ((notmuch_config_env = getenv ("NOTMUCH_CONFIG"))) {
268 config->filename = talloc_strdup (config, notmuch_config_env);
269 } else {
270 config->filename = talloc_asprintf (config, "%s/.notmuch-config",
271 getenv ("HOME"));
272 }
273
274 config->key_file = g_key_file_new ();
275
276 if (! get_config_from_file (config, create)) {
277 talloc_free (config);
278 return NULL;
279 }
280
281 if (config->is_new)
282 g_key_file_set_comment (config->key_file, NULL, NULL,
283 toplevel_config_comment, NULL);
284
285 for (size_t i = 0; i < ARRAY_SIZE (group_comment_table); i++) {
286 const char *name = group_comment_table[i].group_name;
287 if (! g_key_file_has_group (config->key_file, name)) {
288 /* Force group to exist before adding comment */
289 g_key_file_set_value (config->key_file, name, "dummy_key", "dummy_val");
290 g_key_file_remove_key (config->key_file, name, "dummy_key", NULL);
291 g_key_file_set_comment (config->key_file, name, NULL,
292 group_comment_table[i].comment, NULL);
293 }
294 }
295 return config;
296 }
297
298 /* Close the given notmuch_conffile_t object, freeing all resources.
299 *
300 * Note: Any changes made to the configuration are *not* saved by this
301 * function. To save changes, call notmuch_conffile_save before
302 * notmuch_conffile_close.
303 */
304 void
notmuch_conffile_close(notmuch_conffile_t * config)305 notmuch_conffile_close (notmuch_conffile_t *config)
306 {
307 talloc_free (config);
308 }
309
310 /* Save any changes made to the notmuch configuration.
311 *
312 * Any comments originally in the file will be preserved.
313 *
314 * Returns 0 if successful, and 1 in case of any error, (after
315 * printing a description of the error to stderr).
316 */
317 int
notmuch_conffile_save(notmuch_conffile_t * config)318 notmuch_conffile_save (notmuch_conffile_t *config)
319 {
320 size_t length;
321 char *data, *filename;
322 GError *error = NULL;
323
324 data = g_key_file_to_data (config->key_file, &length, NULL);
325 if (data == NULL) {
326 fprintf (stderr, "Out of memory.\n");
327 return 1;
328 }
329
330 /* Try not to overwrite symlinks. */
331 filename = notmuch_canonicalize_file_name (config->filename);
332 if (! filename) {
333 if (errno == ENOENT) {
334 filename = strdup (config->filename);
335 if (! filename) {
336 fprintf (stderr, "Out of memory.\n");
337 g_free (data);
338 return 1;
339 }
340 } else {
341 fprintf (stderr, "Error canonicalizing %s: %s\n", config->filename,
342 strerror (errno));
343 g_free (data);
344 return 1;
345 }
346 }
347
348 if (! g_file_set_contents (filename, data, length, &error)) {
349 if (strcmp (filename, config->filename) != 0) {
350 fprintf (stderr, "Error saving configuration to %s (-> %s): %s\n",
351 config->filename, filename, error->message);
352 } else {
353 fprintf (stderr, "Error saving configuration to %s: %s\n",
354 filename, error->message);
355 }
356 g_error_free (error);
357 free (filename);
358 g_free (data);
359 return 1;
360 }
361
362 free (filename);
363 g_free (data);
364 return 0;
365 }
366
367 bool
notmuch_conffile_is_new(notmuch_conffile_t * config)368 notmuch_conffile_is_new (notmuch_conffile_t *config)
369 {
370 return config->is_new;
371 }
372
373 static void
_config_set(notmuch_conffile_t * config,const char * group,const char * key,const char * value)374 _config_set (notmuch_conffile_t *config,
375 const char *group, const char *key, const char *value)
376 {
377 g_key_file_set_string (config->key_file, group, key, value);
378 }
379
380 static void
_config_set_list(notmuch_conffile_t * config,const char * group,const char * key,const char * list[],size_t length)381 _config_set_list (notmuch_conffile_t *config,
382 const char *group, const char *key,
383 const char *list[],
384 size_t length)
385 {
386 g_key_file_set_string_list (config->key_file, group, key, list, length);
387 }
388
389 void
notmuch_conffile_set_database_path(notmuch_conffile_t * config,const char * database_path)390 notmuch_conffile_set_database_path (notmuch_conffile_t *config,
391 const char *database_path)
392 {
393 _config_set (config, "database", "path", database_path);
394 }
395
396 void
notmuch_conffile_set_user_name(notmuch_conffile_t * config,const char * user_name)397 notmuch_conffile_set_user_name (notmuch_conffile_t *config,
398 const char *user_name)
399 {
400 _config_set (config, "user", "name", user_name);
401 }
402
403 void
notmuch_conffile_set_user_primary_email(notmuch_conffile_t * config,const char * primary_email)404 notmuch_conffile_set_user_primary_email (notmuch_conffile_t *config,
405 const char *primary_email)
406 {
407 _config_set (config, "user", "primary_email", primary_email);
408 }
409
410 void
notmuch_conffile_set_user_other_email(notmuch_conffile_t * config,const char * list[],size_t length)411 notmuch_conffile_set_user_other_email (notmuch_conffile_t *config,
412 const char *list[],
413 size_t length)
414 {
415 _config_set_list (config, "user", "other_email", list, length);
416 }
417
418 void
notmuch_conffile_set_new_tags(notmuch_conffile_t * config,const char * list[],size_t length)419 notmuch_conffile_set_new_tags (notmuch_conffile_t *config,
420 const char *list[],
421 size_t length)
422 {
423 _config_set_list (config, "new", "tags", list, length);
424 }
425
426 void
notmuch_conffile_set_new_ignore(notmuch_conffile_t * config,const char * list[],size_t length)427 notmuch_conffile_set_new_ignore (notmuch_conffile_t *config,
428 const char *list[],
429 size_t length)
430 {
431 _config_set_list (config, "new", "ignore", list, length);
432 }
433
434 void
notmuch_conffile_set_search_exclude_tags(notmuch_conffile_t * config,const char * list[],size_t length)435 notmuch_conffile_set_search_exclude_tags (notmuch_conffile_t *config,
436 const char *list[],
437 size_t length)
438 {
439 _config_set_list (config, "search", "exclude_tags", list, length);
440 }
441
442
443 /* Given a configuration item of the form <group>.<key> return the
444 * component group and key. If any error occurs, print a message on
445 * stderr and return 1. Otherwise, return 0.
446 *
447 * Note: This function modifies the original 'item' string.
448 */
449 static int
_item_split(char * item,char ** group,char ** key)450 _item_split (char *item, char **group, char **key)
451 {
452 char *period;
453
454 *group = item;
455
456 period = strchr (item, '.');
457 if (period == NULL || *(period + 1) == '\0') {
458 fprintf (stderr,
459 "Invalid configuration name: %s\n"
460 "(Should be of the form <section>.<item>)\n", item);
461 return 1;
462 }
463
464 *period = '\0';
465 *key = period + 1;
466
467 return 0;
468 }
469
470 /* These are more properly called Xapian fields, but the user facing
471 * docs call them prefixes, so make the error message match */
472 static bool
validate_field_name(const char * str)473 validate_field_name (const char *str)
474 {
475 const char *key;
476
477 if (! g_utf8_validate (str, -1, NULL)) {
478 fprintf (stderr, "Invalid utf8: %s\n", str);
479 return false;
480 }
481
482 key = g_utf8_strrchr (str, -1, '.');
483 if (! key ) {
484 INTERNAL_ERROR ("Impossible code path on input: %s\n", str);
485 }
486
487 key++;
488
489 if (! *key) {
490 fprintf (stderr, "Empty prefix name: %s\n", str);
491 return false;
492 }
493
494 if (! unicode_word_utf8 (key)) {
495 fprintf (stderr, "Non-word character in prefix name: %s\n", key);
496 return false;
497 }
498
499 if (key[0] >= 'a' && key[0] <= 'z') {
500 fprintf (stderr, "Prefix names starting with lower case letters are reserved: %s\n", key);
501 return false;
502 }
503
504 return true;
505 }
506
507 #define BUILT_WITH_PREFIX "built_with."
508
509 typedef struct config_key {
510 const char *name;
511 bool prefix;
512 bool (*validate)(const char *);
513 } config_key_info_t;
514
515 static const struct config_key
516 config_key_table[] = {
517 { "index.decrypt", false, NULL },
518 { "index.header.", true, validate_field_name },
519 { "query.", true, NULL },
520 { "squery.", true, validate_field_name },
521 };
522
523 static const config_key_info_t *
_config_key_info(const char * item)524 _config_key_info (const char *item)
525 {
526 for (size_t i = 0; i < ARRAY_SIZE (config_key_table); i++) {
527 if (config_key_table[i].prefix &&
528 strncmp (item, config_key_table[i].name,
529 strlen (config_key_table[i].name)) == 0)
530 return config_key_table + i;
531 if (strcmp (item, config_key_table[i].name) == 0)
532 return config_key_table + i;
533 }
534 return NULL;
535 }
536
537 static int
notmuch_config_command_get(notmuch_database_t * notmuch,char * item)538 notmuch_config_command_get (notmuch_database_t *notmuch, char *item)
539 {
540 notmuch_config_values_t *list;
541
542 if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
543 if (notmuch_built_with (item + strlen (BUILT_WITH_PREFIX)))
544 puts ("true");
545 else
546 puts ("false");
547 } else {
548 for (list = notmuch_config_get_values_string (notmuch, item);
549 notmuch_config_values_valid (list);
550 notmuch_config_values_move_to_next (list)) {
551 const char *val = notmuch_config_values_get (list);
552 puts (val);
553 }
554 }
555 return EXIT_SUCCESS;
556 }
557
558 static int
_set_db_config(notmuch_database_t * notmuch,const char * key,int argc,char ** argv)559 _set_db_config (notmuch_database_t *notmuch, const char *key, int argc, char **argv)
560 {
561 const char *val = "";
562
563 if (argc > 1) {
564 /* XXX handle lists? */
565 fprintf (stderr, "notmuch config set: at most one value expected for %s\n", key);
566 return EXIT_FAILURE;
567 }
568
569 if (argc > 0) {
570 val = argv[0];
571 }
572
573 if (print_status_database ("notmuch config", notmuch,
574 notmuch_database_reopen (notmuch,
575 NOTMUCH_DATABASE_MODE_READ_WRITE)))
576 return EXIT_FAILURE;
577
578 if (print_status_database ("notmuch config", notmuch,
579 notmuch_database_set_config (notmuch, key, val)))
580 return EXIT_FAILURE;
581
582 if (print_status_database ("notmuch config", notmuch,
583 notmuch_database_close (notmuch)))
584 return EXIT_FAILURE;
585
586 return EXIT_SUCCESS;
587 }
588
589 static int
notmuch_config_command_set(notmuch_database_t * notmuch,int argc,char * argv[])590 notmuch_config_command_set (notmuch_database_t *notmuch,
591 int argc, char *argv[])
592 {
593 char *group, *key;
594 const config_key_info_t *key_info;
595 notmuch_conffile_t *config;
596 bool update_database = false;
597 int opt_index, ret;
598 char *item;
599
600 notmuch_opt_desc_t options[] = {
601 { .opt_bool = &update_database, .name = "database" },
602 { }
603 };
604
605 opt_index = parse_arguments (argc, argv, options, 1);
606 if (opt_index < 0)
607 return EXIT_FAILURE;
608
609 argc -= opt_index;
610 argv += opt_index;
611
612 if (argc < 1) {
613 fprintf (stderr, "Error: notmuch config set requires at least "
614 "one argument.\n");
615 return EXIT_FAILURE;
616 }
617
618 item = argv[0];
619 argv++;
620 argc--;
621
622 if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
623 fprintf (stderr, "Error: read only option: %s\n", item);
624 return 1;
625 }
626
627 key_info = _config_key_info (item);
628 if (key_info && key_info->validate && (! key_info->validate (item)))
629 return 1;
630
631 if (update_database) {
632 return _set_db_config (notmuch, item, argc, argv);
633 }
634
635 if (_item_split (item, &group, &key))
636 return 1;
637
638 config = notmuch_conffile_open (notmuch,
639 notmuch_config_path (notmuch), false);
640 if (! config)
641 return 1;
642
643 /* With only the name of an item, we clear it from the
644 * configuration file.
645 *
646 * With a single value, we set it as a string.
647 *
648 * With multiple values, we set them as a string list.
649 */
650 switch (argc) {
651 case 0:
652 g_key_file_remove_key (config->key_file, group, key, NULL);
653 break;
654 case 1:
655 g_key_file_set_string (config->key_file, group, key, argv[0]);
656 break;
657 default:
658 g_key_file_set_string_list (config->key_file, group, key,
659 (const gchar **) argv, argc);
660 break;
661 }
662
663 ret = notmuch_conffile_save (config);
664
665 notmuch_conffile_close (config);
666
667 return ret;
668 }
669
670 static
671 void
_notmuch_config_list_built_with()672 _notmuch_config_list_built_with ()
673 {
674 printf ("%scompact=%s\n",
675 BUILT_WITH_PREFIX,
676 notmuch_built_with ("compact") ? "true" : "false");
677 printf ("%sfield_processor=%s\n",
678 BUILT_WITH_PREFIX,
679 notmuch_built_with ("field_processor") ? "true" : "false");
680 printf ("%sretry_lock=%s\n",
681 BUILT_WITH_PREFIX,
682 notmuch_built_with ("retry_lock") ? "true" : "false");
683 printf ("%ssexpr_query=%s\n",
684 BUILT_WITH_PREFIX,
685 notmuch_built_with ("sexpr_query") ? "true" : "false");
686 }
687
688 static int
notmuch_config_command_list(notmuch_database_t * notmuch)689 notmuch_config_command_list (notmuch_database_t *notmuch)
690 {
691 notmuch_config_pairs_t *list;
692
693 _notmuch_config_list_built_with ();
694 for (list = notmuch_config_get_pairs (notmuch, "");
695 notmuch_config_pairs_valid (list);
696 notmuch_config_pairs_move_to_next (list)) {
697 const char *value = notmuch_config_pairs_value (list);
698 if (value)
699 printf ("%s=%s\n", notmuch_config_pairs_key (list), value);
700 }
701 notmuch_config_pairs_destroy (list);
702 return EXIT_SUCCESS;
703 }
704
705 int
notmuch_config_command(notmuch_database_t * notmuch,int argc,char * argv[])706 notmuch_config_command (notmuch_database_t *notmuch, int argc, char *argv[])
707 {
708 int ret;
709 int opt_index;
710
711 opt_index = notmuch_minimal_options ("config", argc, argv);
712 if (opt_index < 0)
713 return EXIT_FAILURE;
714
715 /* skip at least subcommand argument */
716 argc -= opt_index;
717 argv += opt_index;
718
719 if (argc < 1) {
720 fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
721 return EXIT_FAILURE;
722 }
723
724 if (strcmp (argv[0], "get") == 0) {
725 if (argc != 2) {
726 fprintf (stderr, "Error: notmuch config get requires exactly "
727 "one argument.\n");
728 return EXIT_FAILURE;
729 }
730 ret = notmuch_config_command_get (notmuch, argv[1]);
731 } else if (strcmp (argv[0], "set") == 0) {
732 ret = notmuch_config_command_set (notmuch, argc, argv);
733 } else if (strcmp (argv[0], "list") == 0) {
734 ret = notmuch_config_command_list (notmuch);
735 } else {
736 fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
737 argv[0]);
738 return EXIT_FAILURE;
739 }
740
741 return ret ? EXIT_FAILURE : EXIT_SUCCESS;
742
743 }
744
745 void
notmuch_conffile_set_maildir_synchronize_flags(notmuch_conffile_t * config,bool synchronize_flags)746 notmuch_conffile_set_maildir_synchronize_flags (notmuch_conffile_t *config,
747 bool synchronize_flags)
748 {
749 g_key_file_set_boolean (config->key_file,
750 "maildir", "synchronize_flags", synchronize_flags);
751 }
752