Commit b9aaf724 b9aaf724c6428fb4e0d01950d4d7113671e048e5 by Sergey Poznyakoff

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
1 parent 811b3886
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
......
......@@ -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.
......
......@@ -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);
......