diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2009-08-20 23:39:45 +0300 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2009-08-20 23:39:45 +0300 |
commit | b9aaf724c6428fb4e0d01950d4d7113671e048e5 (patch) | |
tree | 50ac09e516ee6723e8352a819a44e4408d113a97 | |
parent | 811b38863c06207ab3012c586176a113c1f8def1 (diff) | |
download | mailutils-b9aaf724c6428fb4e0d01950d4d7113671e048e5.tar.gz mailutils-b9aaf724c6428fb4e0d01950d4d7113671e048e5.tar.bz2 |
Movemail: allow to copy mailbox ownership when run as root.
* movemail/movemail.c: Implement new configuration keyword
"mailbox-ownership" (and the --owner command line option).
* doc/texinfo/programs.texi: Document new movemail features.
(Ownership): New subsection stub.
* NEWS: Update
-rw-r--r-- | NEWS | 6 | ||||
-rw-r--r-- | doc/texinfo/programs.texi | 119 | ||||
-rw-r--r-- | movemail/movemail.c | 369 |
3 files changed, 360 insertions, 134 deletions
@@ -1,4 +1,4 @@ -GNU mailutils NEWS -- history of user-visible changes. 2009-08-14 +GNU mailutils NEWS -- history of user-visible changes. 2009-08-20 Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc. See the end of file for copying conditions. @@ -27,6 +27,10 @@ exists in the destination mailbox. The `--verbose' command line option enables outputting additional information. +The `--owner' command line option (and the corresponding +`mailbox-ownership' configuration file statement) copy mailbox +ownership, if the utility is run with root privileges. + * Mail ** The -f option diff --git a/doc/texinfo/programs.texi b/doc/texinfo/programs.texi index 4135911a8..5cf7ef23d 100644 --- a/doc/texinfo/programs.texi +++ b/doc/texinfo/programs.texi @@ -4371,13 +4371,15 @@ description of @code{Rmail} interface. Mailutils version of @command{movemail} is completely backward-compatible with its Emacs predecessor, so it should run -flawlessly with older versions of Emacs. Emacs version 21.4, which is -being developed at the time of this writing, will contain improved -@code{Rmail} interface for work with mailutils @command{movemail}. +flawlessly with older versions of Emacs. Emacs versions +starting from 22.1 contain improved @code{Rmail} interface and +are able to take advantage of all new features mailutils +@command{movemail} provides. @menu * Movemail Configuration:: * Movemail Options:: Description of the Available Options +* Ownership:: Setting Destination Mailbox Ownership * Summary:: Short Movemail Invocation Summary @end menu @@ -4408,6 +4410,31 @@ exists in the destination mailbox. Set verbosity level. @end deffn +@deffn {Movemail Config} mailbox-ownership @var{method-list} +Define list of methods for setting ownership of the destination +mailbox. The @var{method-list} argument can contain the following +elements: + +@anchor{mailbox-ownership-methods} +@table @asis +@item copy-id +Copy owner UID and GID from the source mailbox. This method works only +with local mailboxes, i.e.: @samp{mbox} (UNIX mailbox), @samp{maildir} +and @samp{mh}. + +@item copy-name +Get owner name from the source mailbox URL and obtain UID and GID for +this user using mailutils authorization methods. + +@item set-id=@var{uid}[:@var{gid}] +Set supplied @var{uid} and @var{gid}. If @var{gid} is not supplied, +it is read from the @file{/etc/passwd} record for this UID. + +@item set-name=@var{user} +Make destination mailbox owned by @var{user}. +@end table +@end deffn + @multitable @columnfractions 0.3 0.6 @headitem Statement @tab Reference @item debug @tab @xref{Debug Statement}. @@ -4439,58 +4466,13 @@ If the remote server supports @acronym{TLS} encryption, use @option{--tls} to instruct @command{movemail} to initiate encrypted connection. -Quite a few options control how @command{movemail} handles mail -locking (a way of preventing simultaneous access to the source -mailbox). By default, before accessing mailbox @var{file}, -@command{movemail} will first see if the file named -@file{@var{file}.lock} (so called @dfn{lock file}) exists. If so, it -will assume that the mailbox is being used by another program and will -sleep one second. If @file{@var{file}.lock} file disappears after this -wait period, the program will proceed. Otherwise, it will repeat this -action ten times. If after ten wait periods the lock file does not -disappear, @command{movemail} gives up and exits. - -If the lock file does not exist, @command{movemail} will create it, -thereby indicating to other programs that the mailbox is being used, -and will proceed to copying messages to the destination file. When -finished, @command{movemail} closes the mailbox and removes the lock -file. - -Several options control this behavior. To change the default sleep period -use @option{--lock-retry-timeout}. Its argument is the timeout value -in seconds. - -To change number of retries, use @option{--lock-retry-count}. For -example, setting @code{rmail-movemail-flags} to - -@smallexample ---lock-retry-timeout=2 --lock-retry-count=5 -@end smallexample - -@noindent -instructs @command{movemail} to make five attempts to acquire the lock -file, with two-second intervals between the attempts. - -You may also force @command{movemail} to remove the lock file if it is -older than a given amount of time (a so called @dfn{stale lock -file}). To do so, use the following option: - -@smallexample ---lock-expire-timeout=@var{seconds} -@end smallexample - -The @option{--lock-expire-timeout} sets the number of seconds after -which a lock file is considered stale. +@node Ownership +@subsection Setting Destination Mailbox Ownership +@UNREVISED -There are special programs that can be used to lock and unlock -mailboxes. A common example of such programs is @command{dotlock}. If -you wish to use such @dfn{external locking program} instead of the -default mailutils locking mechanism, use option -@option{--external-locker}. Argument to this option specifies the full -name of the external program to use. @node Summary -@subsection Summary of Movemail Usage +@subsection Movemail Usage Summary @smallexample movemail [@var{option}...] @var{inbox} @var{destfile} [@var{remote-password}] @@ -4500,7 +4482,7 @@ The first argument, @var{inbox}, is the @acronym{url} (@pxref{URL}) of the source mailbox. The second argument, @var{destfile}, traditionally means destination file, i.e. the UNIX mailbox to copy messages to. However, mailutils @command{movemail} extends the meaning of this -parameter. You may actually specify any valid @acronym{url} as +parameter. You may actually specify any valid @acronym{URL} as @var{destfile} parameter.@footnote{Rmail does not use this feature}. Finally, optional third argument is a traditional way of specifying user passwords for remote (@acronym{POP} or @acronym{IMAP}) @@ -4521,30 +4503,6 @@ Preserve the source mailbox @itemx --reverse Reverse the sorting order -@item --external-locker=@var{program} -Use given @var{program} as the external locker program. - -@item --lock-expire-timeout=@var{seconds} -Set number of seconds after which the lock expires - -@item --lock-flags=@var{flags} -Set locker flags. @var{flags} is composed of the following letters: -@samp{E} -- use external locker program @command{dotlock}, -@samp{R} -- retry 10 times if acquiring of the lock failed (see also -@option{--lock-retry-count} below), @samp{T} -- remove stale locks -after 10 minutes (see also @option{--lock-expire-timeout}, -and @samp{P} -- write process @acronym{PID} to the lock file. - -@item --lock-retry-count=@var{number} -Set the maximum number of times to retry acquiring the lockfile - -@item --lock-retry-timeout=@var{seconds} -Set timeout for acquiring the lockfile - -@item -m @var{url} -@itemx --mail-spool @var{URL} -Use specified URL as a mailspool directory - @item --tls[=@var{bool}] Enable (default) or disable TLS support @@ -4552,6 +4510,13 @@ Enable (default) or disable TLS support @item --uidl Use UIDLs to avoid downloading the same message twice. +@item -P @var{method-list} +@itemx --owner=@var{method-list} +Define list of methods for setting ownership of the destination +mailbox. @xref{mailbox-ownership-methods}, for a description of +@var{method-list}. This option is useful only when running +@command{movemail} as root. + @item -v @item --verbose Increase verbosity level. diff --git a/movemail/movemail.c b/movemail/movemail.c index bacfd407c..99dc5b05c 100644 --- a/movemail/movemail.c +++ b/movemail/movemail.c @@ -24,6 +24,7 @@ #include <stdlib.h> #include <string.h> #include <sys/stat.h> +#include <pwd.h> #include <grp.h> #include <unistd.h> #include <mailutils/mailutils.h> @@ -39,30 +40,74 @@ static char args_doc[] = N_("inbox-url destfile [POP-password]"); #define OPT_EMACS 256 static struct argp_option options[] = { - { "preserve", 'p', NULL, 0, N_("Preserve the source mailbox"), 0 }, + { "preserve", 'p', NULL, 0, N_("Preserve the source mailbox") }, { "keep-messages", 0, NULL, OPTION_ALIAS, NULL }, - { "reverse", 'r', NULL, 0, N_("Reverse the sorting order"), 0 }, + { "reverse", 'r', NULL, 0, N_("Reverse the sorting order") }, { "emacs", OPT_EMACS, NULL, 0, - N_("Output information used by Emacs rmail interface"), 0 }, - { "copy-permissions", 'P', NULL, 0, - N_("Copy original mailbox permissions and ownership when applicable"), - 0 }, + N_("Output information used by Emacs rmail interface") }, { "uidl", 'u', NULL, 0, - N_("Use UIDLs to avoid downloading the same message twice"), - 0 }, + N_("Use UIDLs to avoid downloading the same message twice") }, { "verbose", 'v', NULL, 0, - N_("Increase verbosity level"), - 0 }, + N_("Increase verbosity level") }, + { "owner", 'P', N_("MODELIST"), 0, + N_("Control mailbox ownership") }, { NULL, 0, NULL, 0, NULL, 0 } }; static int reverse_order; static int preserve_mail; static int emacs_mode; -static int copy_meta; static int uidl_option; static int verbose_option; +enum set_ownership_mode + { + copy_owner_id, + copy_owner_name, + set_owner_id, + set_owner_name + }; +#define SET_OWNERSHIP_MAX 4 + +struct user_id +{ + uid_t uid; + gid_t gid; +}; + +struct set_ownership_method +{ + enum set_ownership_mode mode; + union + { + char *name; + struct user_id id; + } owner; +}; + +static struct set_ownership_method so_methods[SET_OWNERSHIP_MAX]; +static int so_method_num; + +struct set_ownership_method * +get_next_so_method () +{ + if (so_method_num == MU_ARRAY_SIZE (so_methods)) + { + mu_error (_("ownership method table overflow")); + exit (1); + } + return so_methods + so_method_num++; +} + +mu_kwd_t method_kwd[] = { + { "copy-id", copy_owner_id }, + { "copy-name", copy_owner_name }, + { "set-name", set_owner_name }, + { "user", set_owner_name }, + { "set-id", set_owner_id }, + { NULL } +}; + static error_t parse_opt (int key, char *arg, struct argp_state *state) { @@ -79,7 +124,7 @@ parse_opt (int key, char *arg, struct argp_state *state) break; case 'P': - copy_meta = 1; + mu_argp_node_list_new (&lst, "mailbox-ownership", arg); break; case 'u': @@ -93,7 +138,7 @@ parse_opt (int key, char *arg, struct argp_state *state) case OPT_EMACS: mu_argp_node_list_new (&lst, "emacs", "yes"); break; - + case ARGP_KEY_INIT: mu_argp_node_list_init (&lst); break; @@ -118,6 +163,130 @@ static struct argp argp = { }; +static int +_cb_mailbox_ownership (mu_debug_t debug, const char *str) +{ + if (strcmp (str, "clear") == 0) + so_method_num = 0; + else + { + int code; + char *p; + size_t len = strcspn (str, "="); + struct set_ownership_method *meth; + + if (mu_kwd_xlat_name_len (method_kwd, str, len, &code)) + { + mu_cfg_format_error (debug, MU_DEBUG_ERROR, + _("Invalid ownership method: %s"), + str); + return 1; + } + + meth = get_next_so_method (); + meth->mode = code; + switch (meth->mode) + { + case copy_owner_id: + case copy_owner_name: + break; + + case set_owner_id: + if (!str[len]) + { + mu_cfg_format_error (debug, MU_DEBUG_ERROR, + _("Ownership method %s requires value"), + str); + return 1; + } + str += len + 1; + meth->owner.id.uid = strtoul (str, &p, 0); + if (*p) + { + if (*p == ':') + { + str = p + 1; + meth->owner.id.gid = strtoul (str, &p, 0); + if (*p) + { + mu_cfg_format_error (debug, MU_DEBUG_ERROR, + _("expected gid number, but found %s"), + str); + return 1; + } + } + else + { + mu_cfg_format_error (debug, MU_DEBUG_ERROR, + _("expected uid number, but found %s"), + str); + return 1; + } + } + else + meth->owner.id.gid = (gid_t) -1; + break; + + case set_owner_name: + if (!str[len]) + { + mu_cfg_format_error (debug, MU_DEBUG_ERROR, + _("Ownership method %s requires value"), + str); + return 1; + } + meth->owner.name = mu_strdup (str + len + 1); + } + } + return 0; +} + +static int +cb_mailbox_ownership (mu_debug_t debug, void *data, mu_config_value_t *val) +{ + int i; + + if (val->type == MU_CFG_STRING) + { + const char *str = val->v.string; + if (!strchr (str, ',')) + return _cb_mailbox_ownership (debug, str); + else + { + int argc; + char **argv; + + if (mu_argcv_get_np (str, strlen (str), ",", NULL, 0, + &argc, &argv, NULL)) + { + mu_cfg_format_error (debug, MU_DEBUG_ERROR, + _("cannot parse %s"), + str); + return 1; + } + + for (i = 0; i < argc; i++) + if (_cb_mailbox_ownership (debug, argv[i])) + return 1; + + mu_argcv_free (argc, argv); + return 0; + } + } + + if (mu_cfg_assert_value_type (val, MU_CFG_LIST, debug)) + return 1; + + for (i = 0; i < val->v.arg.c; i++) + { + if (mu_cfg_assert_value_type (&val->v.arg.v[i], MU_CFG_STRING, debug)) + return 1; + if (_cb_mailbox_ownership (debug, val->v.arg.v[i].v.string)) + return 1; + } + return 0; +} + struct mu_cfg_param movemail_cfg_param[] = { { "preserve", mu_cfg_bool, &preserve_mail, 0, NULL, N_("Do not remove messages from the source mailbox.") }, @@ -128,7 +297,16 @@ struct mu_cfg_param movemail_cfg_param[] = { { "uidl", mu_cfg_bool, &uidl_option, 0, NULL, N_("Use UIDLs to avoid downloading the same message twice.") }, { "verbose", mu_cfg_int, &verbose_option, 0, NULL, - N_("Increase verbosity level.") }, + N_("Set verbosity level.") }, + { "mailbox-ownership", mu_cfg_callback, NULL, 0, + cb_mailbox_ownership, + N_("Define a list of methods for setting mailbox ownership. Valid " + "methods are:\n" + " copy-id get owner UID and GID from the source mailbox\n" + " copy-name get owner name from the source mailbox URL\n" + " set-id=UID[:GID] set supplied UID and GID\n" + " set-name=USER make destination mailbox owned by USER"), + N_("methods: list") }, { NULL } }; @@ -300,29 +478,18 @@ close_mailboxes (void) mu_mailbox_close (dest); mu_mailbox_close (source); } - -static void -set_permissions (mu_mailbox_t mbox) + +static int +get_mbox_owner_id (mu_mailbox_t mbox, mu_url_t url, struct user_id *id) { - mu_url_t url = NULL; const char *s; - int rc; - uid_t uid; - gid_t gid; - - if (getuid () != 0) - { - mu_error (_("must be root to use --copy-permissions")); - exit (1); - } - mu_mailbox_get_url (mbox, &url); - rc = mu_url_sget_scheme (url, &s); + int rc = mu_url_sget_scheme (url, &s); if (rc) die (mbox, _("Cannot get scheme"), rc); - if (strcmp (s, "/") == 0 - || strcmp (s, "mbox") == 0 - || strcmp (s, "mh") == 0 - || strcmp (s, "maildir") == 0) + if ((strcmp (s, "/") == 0 + || strcmp (s, "mbox") == 0 + || strcmp (s, "mh") == 0 + || strcmp (s, "maildir") == 0)) { struct stat st; @@ -335,33 +502,124 @@ set_permissions (mu_mailbox_t mbox) mu_strerror (errno)); exit (1); } - uid = st.st_uid; - gid = st.st_gid; + id->uid = st.st_uid; + id->gid = st.st_gid; + return 0; } - else + else if (verbose_option) + mu_diag_output (MU_DIAG_WARNING, + _("ignoring copy-name: not a local mailbox")); + return 1; +} + +static int +get_user_id (const char *name, struct user_id *id) +{ + struct mu_auth_data *auth = mu_get_auth_by_name (name); + + if (!auth) { - struct mu_auth_data *auth; - - rc = mu_url_sget_user (url, &s); - if (rc) - die (mbox, _("Cannot get user"), rc); - - auth = mu_get_auth_by_name (s); - if (!auth) - { - mu_error (_("No such user: %s"), s); - exit (1); - } - else + if (verbose_option) + mu_diag_output (MU_DIAG_WARNING, _("no such user: %s"), name); + return 1; + } + + id->uid = auth->uid; + id->gid = auth->gid; + mu_auth_data_free (auth); + return 0; +} + +static int +get_mbox_owner_name (mu_mailbox_t mbox, mu_url_t url, struct user_id *id) +{ + const char *s; + int rc = mu_url_sget_user (url, &s); + if (rc) + /* FIXME */ + die (mbox, _("Cannot get mailbox owner name"), rc); + + return get_user_id (s, id); +} + +static int +guess_mbox_owner (mu_mailbox_t mbox, struct user_id *id) +{ + mu_url_t url = NULL; + int rc; + struct set_ownership_method *meth; + + rc = mu_mailbox_get_url (mbox, &url); + if (rc) + die (mbox, _("Cannot get url"), rc); + + rc = 1; + for (meth = so_methods; rc == 1 && meth < so_methods + so_method_num; meth++) + { + switch (meth->mode) { - uid = auth->uid; - gid = auth->gid; + case copy_owner_id: + rc = get_mbox_owner_id (mbox, url, id); + break; + + case copy_owner_name: + rc = get_mbox_owner_name (mbox, url, id); + break; + + case set_owner_id: + id->uid = meth->owner.id.uid; + rc = 0; + if (meth->owner.id.gid == (gid_t)-1) + { + struct passwd *pw = getpwuid (id->uid); + if (pw) + id->gid = pw->pw_gid; + else + { + if (verbose_option) + mu_diag_output (MU_DIAG_WARNING, + _("no user with uid %lu found"), + (unsigned long) id->uid); + rc = 1; + } + } + break; + + case set_owner_name: + rc = get_user_id (meth->owner.name, id); + break; } - mu_auth_data_free (auth); } + + return rc; +} - if (mu_switch_to_privs (uid, gid, NULL)) - exit (1); +static void +switch_owner (mu_mailbox_t mbox) +{ + struct user_id user_id; + + if (so_method_num == 0) + return; + + if (getuid ()) + { + if (verbose_option) + mu_diag_output (MU_DIAG_WARNING, + _("ignoring mailbox-ownership statement")); + return; + } + + if (guess_mbox_owner (mbox, &user_id) == 0) + { + if (mu_switch_to_privs (user_id.uid, user_id.gid, NULL)) + exit (1); + } + else + { + mu_error (_("no suitable method for setting mailbox ownership")); + exit (1); + } } static int @@ -443,8 +701,7 @@ main (int argc, char **argv) else open_mailbox (&source, source_name, flags, argv[2]); - if (copy_meta) - set_permissions (source); + switch_owner (source); open_mailbox (&dest, dest_name, MU_STREAM_RDWR | MU_STREAM_CREAT, NULL); |