cli/insert: add --world-readable flag
authorDaniel Kahn Gillmor <dkg@fifthhorseman.net>
Fri, 9 Feb 2018 04:10:58 +0000 (23:10 -0500)
committerDavid Bremner <david@tethera.net>
Sat, 24 Mar 2018 23:08:11 +0000 (20:08 -0300)
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
notmuch-insert.c
test/T070-insert.sh

index 478845150e016e1c82cb590585d40491aeb603e6..86e2f567348ba5920c972a967b7e936c35e34d82 100644 (file)
@@ -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
index 48490b51deb26c315bae42b9a7591c828d7c45a7..d229c9dc7ac9fa28468436d6146ed3770cdb80cc 100644 (file)
@@ -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;
     }
index 40519bb2f2176c04d4ecf2ccde1dfe1be5b18c4e..05be473ab17ea629457733163f0fe1c693fc0b2e 100755 (executable)
@@ -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"