Commit 18de516a 18de516a00c01a4262310f09a1ac8586e97f8a1a by Sergey Poznyakoff

imap4d: redo signal handling

Previously implemented way of signal handling was unsafe because of
the use of unsafe functions in signal handlers. It also allowed for
recursive invocations of MU calls not supposed to handle recursion
(such as mu_mailbox_expunge, for example). This changeset fixes it.

* imap4d/imap4d.c (imap4d_child_signal_setup): Change signal set.
(imap4d_mainloop): Set a jump point for signal handling.
Restore default handling for SIGILL, SIGBUS, SIGFPE, SIGSEGV, SIGSTOP.
(master_jmp): New variable.
(imap4d_master_signal): New function.
(main): Redo signal handling.
* imap4d/imap4d.h (child_jmp): New extern.
(imap4d_enter_critical,imap4d_leave_critical): New protos.
* imap4d/signal.c (imap4d_master_signal): Move to imap4d.c
(imap4d_enter_critical,imap4d_leave_critical): New functions.
(imap4d_child_signal): Rewrite.

* imap4d/append.c: Protect critical sections.
* imap4d/bye.c: Likewise.
* imap4d/close.c: Likewise.
* imap4d/copy.c: Likewise.
* imap4d/delete.c: Likewise.
* imap4d/expunge.c: Likewise.
* imap4d/rename.c: Likewise.
* imap4d/select.c: Likewise.
* imap4d/status.c: Likewise.

* scheme/Makefile.am (sievemod_DATA): Add guimb.scmi.
1 parent c7376cec
......@@ -124,7 +124,8 @@ imap4d_append0 (mu_mailbox_t mbox, int flags, char *date_time, char *text,
mu_message_destroy (&msg, &tm);
return 1;
}
imap4d_enter_critical ();
rc = mu_mailbox_append_message (mbox, msg);
if (rc == 0)
{
......@@ -140,7 +141,8 @@ imap4d_append0 (mu_mailbox_t mbox, int flags, char *date_time, char *text,
/* FIXME: If not INBOX */
quota_update (size);
}
imap4d_leave_critical ();
mu_message_destroy (&msg, &tm);
return rc;
}
......
......@@ -36,90 +36,102 @@ imap4d_bye0 (int reason, struct imap4d_command *command)
{
int status = EX_SOFTWARE;
if (mbox)
/* Some clients may close the connection immediately after sending
LOGOUT. Do not treat this as error (RFC 2683).
To be on the safe side, ignore broken pipes for any command: at
this stage it is of no significance. */
static int sigtab[] = { SIGPIPE };
mu_set_signals (sigpipe, sigtab, MU_ARRAY_SIZE (sigtab));
if (setjmp (pipejmp))
{
mu_mailbox_flush (mbox, 0);
mu_mailbox_close (mbox);
mu_mailbox_destroy (&mbox);
mu_set_signals (SIG_IGN, sigtab, MU_ARRAY_SIZE (sigtab));
/* Invalidate iostream. This creates a mild memory leak, as the
stream is not destroyed by util_bye, but at least this avoids
endless loop which would happen when mu_stream_flush would
try to flush buffers on an already broken pipe.
FIXME: There must be a special function for that, I guess.
Something like mu_stream_invalidate. */
iostream = NULL;
}
switch (reason)
else
{
case ERR_NO_MEM:
io_untagged_response (RESP_BYE, "Server terminating: no more resources.");
mu_diag_output (MU_DIAG_ERROR, _("not enough memory"));
break;
case ERR_TERMINATE:
status = EX_OK;
io_untagged_response (RESP_BYE, "Server terminating on request.");
mu_diag_output (MU_DIAG_NOTICE, _("terminating on request"));
break;
case ERR_SIGNAL:
mu_diag_output (MU_DIAG_ERROR, _("quitting on signal"));
exit (status);
case ERR_TIMEOUT:
status = EX_TEMPFAIL;
io_untagged_response (RESP_BYE, "Session timed out");
if (state == STATE_NONAUTH)
mu_diag_output (MU_DIAG_INFO, _("session timed out for no user"));
else
mu_diag_output (MU_DIAG_INFO, _("session timed out for user: %s"), auth_data->name);
break;
case ERR_NO_OFILE:
status = EX_IOERR;
mu_diag_output (MU_DIAG_INFO, _("write error on control stream"));
break;
case ERR_NO_IFILE:
status = EX_IOERR;
mu_diag_output (MU_DIAG_INFO, _("read error on control stream"));
break;
case ERR_MAILBOX_CORRUPTED:
status = EX_OSERR;
mu_diag_output (MU_DIAG_ERROR, _("mailbox modified by third party"));
break;
case ERR_STREAM_CREATE:
status = EX_UNAVAILABLE;
mu_diag_output (MU_DIAG_ERROR, _("cannot create transport stream"));
break;
if (mbox)
{
imap4d_enter_critical ();
mu_mailbox_flush (mbox, 0);
mu_mailbox_close (mbox);
mu_mailbox_destroy (&mbox);
imap4d_leave_critical ();
}
switch (reason)
{
case ERR_NO_MEM:
io_untagged_response (RESP_BYE,
"Server terminating: no more resources.");
mu_diag_output (MU_DIAG_ERROR, _("not enough memory"));
break;
case ERR_TERMINATE:
status = EX_OK;
io_untagged_response (RESP_BYE, "Server terminating on request.");
mu_diag_output (MU_DIAG_NOTICE, _("terminating on request"));
break;
case ERR_SIGNAL:
mu_diag_output (MU_DIAG_ERROR, _("quitting on signal"));
exit (status);
case ERR_TIMEOUT:
status = EX_TEMPFAIL;
io_untagged_response (RESP_BYE, "Session timed out");
if (state == STATE_NONAUTH)
mu_diag_output (MU_DIAG_INFO, _("session timed out for no user"));
else
mu_diag_output (MU_DIAG_INFO, _("session timed out for user: %s"),
auth_data->name);
break;
case ERR_NO_OFILE:
status = EX_IOERR;
mu_diag_output (MU_DIAG_INFO, _("write error on control stream"));
break;
case ERR_NO_IFILE:
status = EX_IOERR;
mu_diag_output (MU_DIAG_INFO, _("read error on control stream"));
break;
case ERR_MAILBOX_CORRUPTED:
status = EX_OSERR;
mu_diag_output (MU_DIAG_ERROR, _("mailbox modified by third party"));
break;
case ERR_STREAM_CREATE:
status = EX_UNAVAILABLE;
mu_diag_output (MU_DIAG_ERROR, _("cannot create transport stream"));
break;
case OK:
status = EX_OK;
io_untagged_response (RESP_BYE, "Session terminating.");
if (state == STATE_NONAUTH)
mu_diag_output (MU_DIAG_INFO, _("session terminating"));
else
mu_diag_output (MU_DIAG_INFO, _("session terminating for user: %s"), auth_data->name);
break;
default:
io_untagged_response (RESP_BYE, "Quitting (reason unknown)");
mu_diag_output (MU_DIAG_ERROR, _("quitting (numeric reason %d)"), reason);
break;
}
if (status == EX_OK && command)
{
/* Some clients may close the connection immediately after sending
LOGOUT. Do not treat this as error (RFC 2683). */
static int sigtab[] = { SIGPIPE };
mu_set_signals (sigpipe, sigtab, MU_ARRAY_SIZE (sigtab));
if (setjmp (pipejmp) == 0)
case OK:
status = EX_OK;
io_untagged_response (RESP_BYE, "Session terminating.");
if (state == STATE_NONAUTH)
mu_diag_output (MU_DIAG_INFO, _("session terminating"));
else
mu_diag_output (MU_DIAG_INFO,
_("session terminating for user: %s"),
auth_data->name);
break;
default:
io_untagged_response (RESP_BYE, "Quitting (reason unknown)");
mu_diag_output (MU_DIAG_ERROR, _("quitting (numeric reason %d)"),
reason);
break;
}
if (status == EX_OK && command)
io_completion_response (command, RESP_OK, "Completed");
else
/* Invalidate iostream. This creates a mild memory leak, as the
stream is not destroyed by util_bye, but at least this avoids
endless loop which would happen when mu_stream_flush would
try to flush buffers on an already broken pipe.
FIXME: There must be a special function for that, I guess.
Something like mu_stream_invalidate. */
iostream = NULL;
}
util_bye ();
......
......@@ -30,7 +30,9 @@ imap4d_close0 (struct imap4d_command *command, imap4d_tokbuf_t tok,
mu_mailbox_get_flags (mbox, &flags);
if (flags & MU_STREAM_WRITE)
{
imap4d_enter_critical ();
status = mu_mailbox_flush (mbox, expunge);
imap4d_leave_critical ();
if (status)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_mailbox_flush", NULL, status);
......@@ -40,7 +42,9 @@ imap4d_close0 (struct imap4d_command *command, imap4d_tokbuf_t tok,
/* No messages are removed, and no error is given, if the mailbox is
selected by an EXAMINE command or is otherwise selected read-only. */
imap4d_enter_critical ();
status = mu_mailbox_close (mbox);
imap4d_leave_critical ();
if (status)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_mailbox_close", NULL, status);
......
......@@ -119,7 +119,9 @@ try_copy (mu_mailbox_t dst, mu_mailbox_t src, size_t n, size_t *set)
return RESP_BAD;
}
imap4d_enter_critical ();
status = mu_mailbox_append_message (dst, msg);
imap4d_leave_critical ();
if (status)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_mailbox_append_message",
......@@ -185,8 +187,10 @@ safe_copy (mu_mailbox_t dst, mu_mailbox_t src, size_t n, size_t *set,
mu_attribute_set_userflag (attr, MU_ATTRIBUTE_DELETED);
}
}
imap4d_enter_critical ();
status = mu_mailbox_flush (dst, 1);
imap4d_leave_critical ();
if (status)
{
mu_url_t url = NULL;
......
......@@ -56,7 +56,9 @@ imap4d_delete (struct imap4d_command *command, imap4d_tokbuf_t tok)
rc = mu_mailbox_create (&tmpbox, name);
if (rc == 0)
{
imap4d_enter_critical ();
rc = mu_mailbox_remove (tmpbox);
imap4d_leave_critical ();
mu_mailbox_destroy (&tmpbox);
if (rc)
mu_diag_funcall (MU_DIAG_ERROR, "mu_mailbox_remove", name, rc);
......
......@@ -36,9 +36,11 @@ imap4d_expunge (struct imap4d_command *command, imap4d_tokbuf_t tok)
if (imap4d_tokbuf_argc (tok) != 2)
return io_completion_response (command, RESP_BAD, "Invalid arguments");
imap4d_enter_critical ();
/* FIXME: check for errors. */
mu_mailbox_expunge (mbox);
imap4d_leave_critical ();
imap4d_sync_invalidate ();
imap4d_sync ();
return io_completion_response (command, RESP_OK, "Completed");
......
......@@ -385,11 +385,12 @@ get_client_address (int fd, struct sockaddr_in *pcs)
return 0;
}
void
imap4d_child_signal_setup (RETSIGTYPE (*handler) (int signo))
{
static int sigtab[] = { SIGILL, SIGBUS, SIGFPE, SIGSEGV, SIGSTOP, SIGPIPE,
SIGABRT, SIGINT, SIGQUIT, SIGTERM, SIGHUP, SIGALRM };
static int sigtab[] = { SIGPIPE, SIGABRT, SIGINT, SIGQUIT,
SIGTERM, SIGHUP, SIGALRM };
mu_set_signals (handler, sigtab, MU_ARRAY_SIZE (sigtab));
}
......@@ -399,8 +400,40 @@ imap4d_mainloop (int ifd, int ofd)
imap4d_tokbuf_t tokp;
char *text;
int debug_mode = isatty (ifd);
int signo;
imap4d_child_signal_setup (imap4d_child_signal);
if ((signo = setjmp (child_jmp)))
{
mu_diag_output (MU_DIAG_CRIT, _("got signal `%s'"), strsignal (signo));
switch (signo)
{
case SIGTERM:
case SIGHUP:
signo = ERR_TERMINATE;
break;
case SIGALRM:
signo = ERR_TIMEOUT;
break;
case SIGPIPE:
signo = ERR_NO_OFILE;
break;
default:
signo = ERR_SIGNAL;
}
imap4d_bye (signo);
}
else
{
/* Restore default handling for these signals: */
static int defsigtab[] = { SIGILL, SIGBUS, SIGFPE, SIGSEGV, SIGSTOP };
mu_set_signals (SIG_DFL, defsigtab, MU_ARRAY_SIZE (defsigtab));
/* Set child-specific signal handlers */
imap4d_child_signal_setup (imap4d_child_signal);
}
io_setio (ifd, ofd);
if (imap4d_preauth_setup (ifd) == 0)
......@@ -484,6 +517,17 @@ imap4d_check_home_dir (const char *dir, uid_t uid, gid_t gid)
return 0;
}
jmp_buf master_jmp;
RETSIGTYPE
imap4d_master_signal (int signo)
{
longjmp (master_jmp, signo);
}
int
main (int argc, char **argv)
{
......@@ -578,6 +622,27 @@ main (int argc, char **argv)
}
/* Set the signal handlers. */
if ((status = setjmp (master_jmp)))
{
int code;
mu_diag_output (MU_DIAG_CRIT, _("MASTER: exiting on signal (%s)"),
strsignal (status));
switch (status)
{
case SIGTERM:
case SIGHUP:
case SIGQUIT:
case SIGINT:
code = EX_OK;
break;
default:
code = EX_SOFTWARE;
break;
}
exit (code);
}
mu_set_signals (imap4d_master_signal, sigtab, MU_ARRAY_SIZE (sigtab));
mu_stdstream_strerr_setup (mu_log_syslog ?
......
......@@ -202,6 +202,7 @@ extern int imap4d_transcript;
extern mu_list_t imap4d_id_list;
extern int imap4d_argc;
extern char **imap4d_argv;
extern jmp_buf child_jmp;
/* Input functions */
extern mu_stream_t iostream;
......@@ -334,6 +335,9 @@ extern RETSIGTYPE imap4d_master_signal (int);
extern RETSIGTYPE imap4d_child_signal (int);
extern int imap4d_bye (int);
extern int imap4d_bye0 (int reason, struct imap4d_command *command);
void imap4d_enter_critical (void);
void imap4d_leave_critical (void);
/* Namespace functions */
extern mu_list_t namespace[NS_MAX];
......
......@@ -182,12 +182,17 @@ imap4d_rename (struct imap4d_command *command, imap4d_tokbuf_t tok)
if (mu_mailbox_get_message (inbox, no, &message) == 0)
{
mu_attribute_t attr = NULL;
imap4d_enter_critical ();
mu_mailbox_append_message (newmbox, message);
imap4d_leave_critical ();
mu_message_get_attribute (message, &attr);
mu_attribute_set_deleted (attr);
}
}
imap4d_enter_critical ();
mu_mailbox_expunge (inbox);
imap4d_leave_critical ();
mu_mailbox_close (inbox);
mu_mailbox_destroy (&inbox);
}
......
......@@ -46,8 +46,10 @@ imap4d_select0 (struct imap4d_command *command, const char *mboxname,
currently selected mailbox without doing an expunge. */
if (mbox)
{
imap4d_enter_critical ();
mu_mailbox_sync (mbox);
mu_mailbox_close (mbox);
imap4d_leave_critical ();
mu_mailbox_destroy (&mbox);
/* Destroy the old uid table. */
imap4d_sync ();
......
......@@ -15,57 +15,34 @@
You should have received a copy of the GNU General Public License
along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. */
#define __USE_MISC
#include "imap4d.h"
/* Default signal handler to call the imap4d_bye() function */
jmp_buf child_jmp;
static int __critical_section;
static int __got_signal;
RETSIGTYPE
imap4d_master_signal (int signo)
void
imap4d_enter_critical ()
{
int code;
mu_diag_output (MU_DIAG_CRIT, _("MASTER: exiting on signal (%s)"),
strsignal (signo));
switch (signo)
{
case SIGTERM:
case SIGHUP:
case SIGQUIT:
case SIGINT:
code = EX_OK;
break;
default:
code = EX_SOFTWARE;
break;
}
__critical_section = 1;
if (__got_signal)
__critical_section++;
}
exit (code);
void
imap4d_leave_critical ()
{
if (__got_signal && __critical_section != 2)
longjmp (child_jmp, __got_signal);
__critical_section = 0;
}
RETSIGTYPE
imap4d_child_signal (int signo)
{
imap4d_child_signal_setup (SIG_IGN);
mu_diag_output (MU_DIAG_CRIT, _("got signal `%s'"), strsignal (signo));
switch (signo)
{
case SIGTERM:
case SIGHUP:
signo = ERR_TERMINATE;
break;
case SIGALRM:
signo = ERR_TIMEOUT;
break;
case SIGPIPE:
signo = ERR_NO_OFILE;
break;
default:
signo = ERR_SIGNAL;
}
imap4d_bye (signo);
if (__critical_section)
__got_signal = signo;
else
longjmp (child_jmp, signo);
}
......
......@@ -88,7 +88,9 @@ imap4d_status (struct imap4d_command *command, imap4d_tokbuf_t tok)
/* We may be opening the current mailbox, so make sure the attributes are
preserved */
imap4d_enter_critical ();
mu_mailbox_sync (mbox);
imap4d_leave_critical ();
status = mu_mailbox_create_default (&smbox, mailbox_name);
if (status == 0)
......
......@@ -56,5 +56,6 @@ sievemod_DATA=\
EXTRA_DIST=\
$(sievemod_DATA)\
sieve-core.scm\
sieve2scm.scmi
sieve2scm.scmi\
guimb.scmi
......