[PATCH v3] cli/insert: add --world-readable flag

Subject: [PATCH v3] cli/insert: add --world-readable flag

Date: Thu, 8 Feb 2018 23:10:58 -0500

To: Notmuch Mail

Cc:

From: Daniel Kahn Gillmor


In some cases (e.g. when building a publicly-visible e-mail archive)
it doesn't make any sense to restrict visibility of the message to the
current user account.

This adds a --world-readable boolean option for "notmuch insert", so
that those who want to archive their mail publicly can feed their
archiver with:

    notmuch insert --world-readable

Other local delivery agents (postfix's local, and dovecot's lda) all
default to delivery in mode 0600 rather than relying on the user's
umask, so this fix doesn't change the default.

Also, this does not override the user's umask.  if the umask is
already set tight, it will not become looser as the result of passing
--world-readable.

Signed-off-by: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
---
 doc/man1/notmuch-insert.rst |  6 ++++++
 notmuch-insert.c            | 25 ++++++++++++++-----------
 test/T070-insert.sh         | 45 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 65 insertions(+), 11 deletions(-)

diff --git a/doc/man1/notmuch-insert.rst b/doc/man1/notmuch-insert.rst
index 47884515..86e2f567 100644
--- a/doc/man1/notmuch-insert.rst
+++ b/doc/man1/notmuch-insert.rst
@@ -51,6 +51,12 @@ Supported options for **insert** include
 ``--no-hooks``
     Prevent hooks from being run.
 
+``--world-readable``
+    When writing mail to the mailbox, allow it to be read by users
+    other than the current user.  Note that this does not override
+    umask.  By default, delivered mail is only readable by the current
+    user.
+
 ``--decrypt=(true|nostash|auto|false)``
     If ``true`` and the message is encrypted, try to decrypt the
     message while indexing, stashing any session keys discovered.  If
diff --git a/notmuch-insert.c b/notmuch-insert.c
index 48490b51..d229c9dc 100644
--- a/notmuch-insert.c
+++ b/notmuch-insert.c
@@ -159,10 +159,10 @@ mkdir_recursive (const void *ctx, const char *path, int mode)
  * otherwise. Partial results are not cleaned up on errors.
  */
 static bool
-maildir_create_folder (const void *ctx, const char *maildir)
+maildir_create_folder (const void *ctx, const char *maildir, bool world_readable)
 {
     const char *subdirs[] = { "cur", "new", "tmp" };
-    const int mode = 0700;
+    const int mode = (world_readable ? 0755 : 0700);
     char *subdir;
     unsigned int i;
 
@@ -211,10 +211,11 @@ tempfilename (const void *ctx)
  * is not touched).
  */
 static int
-maildir_mktemp (const void *ctx, const char *maildir, char **path_out)
+maildir_mktemp (const void *ctx, const char *maildir, bool world_readable, char **path_out)
 {
     char *filename, *path;
     int fd;
+    const int mode = (world_readable ? 0644 : 0600);
 
     do {
 	filename = tempfilename (ctx);
@@ -227,7 +228,7 @@ maildir_mktemp (const void *ctx, const char *maildir, char **path_out)
 	    return -1;
 	}
 
-	fd = open (path, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600);
+	fd = open (path, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, mode);
     } while (fd == -1 && errno == EEXIST);
 
     if (fd == -1) {
@@ -289,12 +290,12 @@ copy_fd (int fdout, int fdin)
  * the file, or NULL on errors.
  */
 static char *
-maildir_write_tmp (const void *ctx, int fdin, const char *maildir)
+maildir_write_tmp (const void *ctx, int fdin, const char *maildir, bool world_readable)
 {
     char *path;
     int fdout;
 
-    fdout = maildir_mktemp (ctx, maildir, &path);
+    fdout = maildir_mktemp (ctx, maildir, world_readable, &path);
     if (fdout < 0)
 	return NULL;
 
@@ -323,11 +324,11 @@ FAIL:
  * errors.
  */
 static char *
-maildir_write_new (const void *ctx, int fdin, const char *maildir)
+maildir_write_new (const void *ctx, int fdin, const char *maildir, bool world_readable)
 {
     char *cleanpath, *tmppath, *newpath, *newdir;
 
-    tmppath = maildir_write_tmp (ctx, fdin, maildir);
+    tmppath = maildir_write_tmp (ctx, fdin, maildir, world_readable);
     if (! tmppath)
 	return NULL;
     cleanpath = tmppath;
@@ -457,6 +458,7 @@ notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
     bool create_folder = false;
     bool keep = false;
     bool hooks = true;
+    bool world_readable = false;
     bool synchronize_flags;
     char *maildir;
     char *newpath;
@@ -467,7 +469,8 @@ notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
 	{ .opt_string = &folder, .name = "folder", .allow_empty = true },
 	{ .opt_bool = &create_folder, .name = "create-folder" },
 	{ .opt_bool = &keep, .name = "keep" },
-	{ .opt_bool =  &hooks, .name = "hooks" },
+	{ .opt_bool = &hooks, .name = "hooks" },
+	{ .opt_bool = &world_readable, .name = "world-readable" },
 	{ .opt_inherit = notmuch_shared_indexing_options },
 	{ .opt_inherit = notmuch_shared_options },
 	{ }
@@ -523,7 +526,7 @@ notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
     }
 
     strip_trailing (maildir, '/');
-    if (create_folder && ! maildir_create_folder (config, maildir))
+    if (create_folder && ! maildir_create_folder (config, maildir, world_readable))
 	return EXIT_FAILURE;
 
     /* Set up our handler for SIGINT. We do not set SA_RESTART so that copying
@@ -535,7 +538,7 @@ notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
     sigaction (SIGINT, &action, NULL);
 
     /* Write the message to the Maildir new directory. */
-    newpath = maildir_write_new (config, STDIN_FILENO, maildir);
+    newpath = maildir_write_new (config, STDIN_FILENO, maildir, world_readable);
     if (! newpath) {
 	return EXIT_FAILURE;
     }
diff --git a/test/T070-insert.sh b/test/T070-insert.sh
index 40519bb2..05be473a 100755
--- a/test/T070-insert.sh
+++ b/test/T070-insert.sh
@@ -4,6 +4,10 @@ test_description='"notmuch insert"'
 
 test_require_external_prereq gdb
 
+# subtests about file permissions assume that we're working with umask
+# 022 by default.
+umask 022
+
 # Create directories and database before inserting.
 mkdir -p "$MAIL_DIR"/{cur,new,tmp}
 mkdir -p "$MAIL_DIR"/Drafts/{cur,new,tmp}
@@ -37,6 +41,9 @@ notmuch insert < "$gen_msg_filename"
 cur_msg_filename=$(notmuch search --output=files "subject:insert-subject")
 test_expect_equal_file "$cur_msg_filename" "$gen_msg_filename"
 
+test_begin_subtest "Permissions on inserted message should be 0600"
+test_expect_equal "600" "$(stat -c %a "$cur_msg_filename")"
+
 test_begin_subtest "Insert message adds default tags"
 output=$(notmuch show --format=json "subject:insert-subject")
 expected='[[[{
@@ -73,6 +80,27 @@ notmuch insert +custom < "$gen_msg_filename"
 output=$(notmuch search --output=messages tag:custom)
 test_expect_equal "$output" "id:$gen_msg_id"
 
+test_begin_subtest "Insert tagged world-readable message"
+gen_insert_msg
+notmuch insert --world-readable +world-readable-test < "$gen_msg_filename"
+cur_msg_filename=$(notmuch search --output=files "tag:world-readable-test")
+test_expect_equal_file "$cur_msg_filename" "$gen_msg_filename"
+
+test_begin_subtest "Permissions on inserted world-readable message should be 0644"
+test_expect_equal "644" "$(stat -c %a "$cur_msg_filename")"
+
+test_begin_subtest "Insert tagged world-readable message with group-only umask"
+oldumask=$(umask)
+umask 027
+gen_insert_msg
+notmuch insert --world-readable +world-readable-umask-test < "$gen_msg_filename"
+cur_msg_filename=$(notmuch search --output=files "tag:world-readable-umask-test")
+umask "$oldumask"
+test_expect_equal_file "$cur_msg_filename" "$gen_msg_filename"
+
+test_begin_subtest "Permissions on inserted world-readable message with funny umask should be 0640"
+test_expect_equal "640" "$(stat -c %a "$cur_msg_filename")"
+
 test_begin_subtest "Insert message, add/remove tags"
 gen_insert_msg
 notmuch insert +custom -unread < "$gen_msg_filename"
@@ -170,6 +198,23 @@ output=$(notmuch search --output=files path:F/G/H/I/J/new tag:folder)
 basename=$(basename "$output")
 test_expect_equal_file "$gen_msg_filename" "${MAIL_DIR}/F/G/H/I/J/new/${basename}"
 
+test_begin_subtest "Created subfolder should have permissions 0700"
+test_expect_equal "700" "$(stat -c %a "${MAIL_DIR}/F/G/H/I/J")"
+test_begin_subtest "Created subfolder new/ should also have permissions 0700"
+test_expect_equal "700" "$(stat -c %a "${MAIL_DIR}/F/G/H/I/J/new")"
+
+test_begin_subtest "Insert message, create world-readable subfolder"
+gen_insert_msg
+notmuch insert --folder=F/G/H/I/J/K --create-folder --world-readable +folder-world-readable < "$gen_msg_filename"
+output=$(notmuch search --output=files path:F/G/H/I/J/K/new tag:folder-world-readable)
+basename=$(basename "$output")
+test_expect_equal_file "$gen_msg_filename" "${MAIL_DIR}/F/G/H/I/J/K/new/${basename}"
+
+test_begin_subtest "Created world-readable subfolder should have permissions 0755"
+test_expect_equal "755" "$(stat -c %a "${MAIL_DIR}/F/G/H/I/J/K")"
+test_begin_subtest "Created world-readable subfolder new/ should also have permissions 0755"
+test_expect_equal "755" "$(stat -c %a "${MAIL_DIR}/F/G/H/I/J/K/new")"
+
 test_begin_subtest "Insert message, create existing subfolder"
 gen_insert_msg
 notmuch insert --folder=F/G/H/I/J --create-folder +folder < "$gen_msg_filename"
-- 
2.15.1

_______________________________________________
notmuch mailing list
notmuch@notmuchmail.org
https://notmuchmail.org/mailman/listinfo/notmuch

Thread: