cli/insert: add --world-readable flag

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>
This commit is contained in:
Daniel Kahn Gillmor 2018-02-08 23:10:58 -05:00 committed by David Bremner
parent 0cbe982bfd
commit b6e3efde05
3 changed files with 65 additions and 11 deletions

View file

@ -51,6 +51,12 @@ Supported options for **insert** include
``--no-hooks`` ``--no-hooks``
Prevent hooks from being run. 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)`` ``--decrypt=(true|nostash|auto|false)``
If ``true`` and the message is encrypted, try to decrypt the If ``true`` and the message is encrypted, try to decrypt the
message while indexing, stashing any session keys discovered. If message while indexing, stashing any session keys discovered. If

View file

@ -159,10 +159,10 @@ mkdir_recursive (const void *ctx, const char *path, int mode)
* otherwise. Partial results are not cleaned up on errors. * otherwise. Partial results are not cleaned up on errors.
*/ */
static bool 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 char *subdirs[] = { "cur", "new", "tmp" };
const int mode = 0700; const int mode = (world_readable ? 0755 : 0700);
char *subdir; char *subdir;
unsigned int i; unsigned int i;
@ -211,10 +211,11 @@ tempfilename (const void *ctx)
* is not touched). * is not touched).
*/ */
static int 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; char *filename, *path;
int fd; int fd;
const int mode = (world_readable ? 0644 : 0600);
do { do {
filename = tempfilename (ctx); filename = tempfilename (ctx);
@ -227,7 +228,7 @@ maildir_mktemp (const void *ctx, const char *maildir, char **path_out)
return -1; 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); } while (fd == -1 && errno == EEXIST);
if (fd == -1) { if (fd == -1) {
@ -289,12 +290,12 @@ copy_fd (int fdout, int fdin)
* the file, or NULL on errors. * the file, or NULL on errors.
*/ */
static char * 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; char *path;
int fdout; int fdout;
fdout = maildir_mktemp (ctx, maildir, &path); fdout = maildir_mktemp (ctx, maildir, world_readable, &path);
if (fdout < 0) if (fdout < 0)
return NULL; return NULL;
@ -323,11 +324,11 @@ FAIL:
* errors. * errors.
*/ */
static char * 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; char *cleanpath, *tmppath, *newpath, *newdir;
tmppath = maildir_write_tmp (ctx, fdin, maildir); tmppath = maildir_write_tmp (ctx, fdin, maildir, world_readable);
if (! tmppath) if (! tmppath)
return NULL; return NULL;
cleanpath = tmppath; cleanpath = tmppath;
@ -457,6 +458,7 @@ notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
bool create_folder = false; bool create_folder = false;
bool keep = false; bool keep = false;
bool hooks = true; bool hooks = true;
bool world_readable = false;
bool synchronize_flags; bool synchronize_flags;
char *maildir; char *maildir;
char *newpath; char *newpath;
@ -468,6 +470,7 @@ notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
{ .opt_bool = &create_folder, .name = "create-folder" }, { .opt_bool = &create_folder, .name = "create-folder" },
{ .opt_bool = &keep, .name = "keep" }, { .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_indexing_options },
{ .opt_inherit = notmuch_shared_options }, { .opt_inherit = notmuch_shared_options },
{ } { }
@ -523,7 +526,7 @@ notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
} }
strip_trailing (maildir, '/'); strip_trailing (maildir, '/');
if (create_folder && ! maildir_create_folder (config, maildir)) if (create_folder && ! maildir_create_folder (config, maildir, world_readable))
return EXIT_FAILURE; return EXIT_FAILURE;
/* Set up our handler for SIGINT. We do not set SA_RESTART so that copying /* 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); sigaction (SIGINT, &action, NULL);
/* Write the message to the Maildir new directory. */ /* 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) { if (! newpath) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View file

@ -4,6 +4,10 @@ test_description='"notmuch insert"'
test_require_external_prereq gdb 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. # Create directories and database before inserting.
mkdir -p "$MAIL_DIR"/{cur,new,tmp} mkdir -p "$MAIL_DIR"/{cur,new,tmp}
mkdir -p "$MAIL_DIR"/Drafts/{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") cur_msg_filename=$(notmuch search --output=files "subject:insert-subject")
test_expect_equal_file "$cur_msg_filename" "$gen_msg_filename" 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" test_begin_subtest "Insert message adds default tags"
output=$(notmuch show --format=json "subject:insert-subject") output=$(notmuch show --format=json "subject:insert-subject")
expected='[[[{ expected='[[[{
@ -73,6 +80,27 @@ notmuch insert +custom < "$gen_msg_filename"
output=$(notmuch search --output=messages tag:custom) output=$(notmuch search --output=messages tag:custom)
test_expect_equal "$output" "id:$gen_msg_id" 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" test_begin_subtest "Insert message, add/remove tags"
gen_insert_msg gen_insert_msg
notmuch insert +custom -unread < "$gen_msg_filename" 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") basename=$(basename "$output")
test_expect_equal_file "$gen_msg_filename" "${MAIL_DIR}/F/G/H/I/J/new/${basename}" 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" test_begin_subtest "Insert message, create existing subfolder"
gen_insert_msg gen_insert_msg
notmuch insert --folder=F/G/H/I/J --create-folder +folder < "$gen_msg_filename" notmuch insert --folder=F/G/H/I/J --create-folder +folder < "$gen_msg_filename"