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