Newer
Older
/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
*/
#include "lib.h"
#include "ioloop.h"
#include "llist.h"
#include "str.h"
#include "hostpid.h"
#include "istream.h"
#include "ostream.h"

Stephan Bosch
committed
#include "iostream.h"
#include "iostream-rawlog.h"
#include "var-expand.h"
#include "time-util.h"
#include "master-service.h"
#include "mail-storage-service.h"
#include "mail-namespace.h"
#include "sieve.h"
#include "sieve-storage.h"
#include "managesieve-quote.h"
#include "managesieve-common.h"
#include "managesieve-commands.h"
#include "managesieve-client.h"
#include <unistd.h>
extern struct mail_storage_callbacks mail_storage_callbacks;
struct managesieve_module_register managesieve_module_register = { 0 };
struct client *managesieve_clients = NULL;
unsigned int managesieve_client_count = 0;
static const char *
managesieve_sieve_get_setting(void *context, const char *identifier)
{
struct mail_user *mail_user = (struct mail_user *) context;
return NULL;
return mail_user_plugin_getenv(mail_user, identifier);
}
static const struct sieve_callbacks managesieve_sieve_callbacks = {
NULL,
managesieve_sieve_get_setting
};
static void client_idle_timeout(struct client *client)
{
if (client->cmd.func != NULL) {
client_destroy(client,
"Disconnected for inactivity in reading our output");
} else {
client_send_bye(client, "Disconnected for inactivity");
client_destroy(client, "Disconnected for inactivity");
}
}
static struct sieve_storage *
client_get_storage(struct sieve_instance *svinst, struct event *event,
struct mail_user *user, int fd_out)
{
struct sieve_storage *storage;
enum sieve_error error;
const char *errormsg, *byemsg;
/* Open personal script storage */
storage = sieve_storage_create_main(svinst, user,
SIEVE_STORAGE_FLAG_READWRITE,
&error);
if (storage == NULL) {
switch (error) {
case SIEVE_ERROR_NOT_POSSIBLE:
byemsg = "BYE \"Sieve processing is disabled for this user.\"\r\n";
errormsg = "Failed to open Sieve storage: "
"Sieve is disabled for this user";
case SIEVE_ERROR_NOT_FOUND:
byemsg = "BYE \"This user cannot manage personal Sieve scripts.\"\r\n";
errormsg = "Failed to open Sieve storage: "
"Personal script storage disabled or not found.";
break;
default:
byemsg = t_strflocaltime(
"BYE \""CRITICAL_MSG_STAMP"\"\r\n", ioloop_time);
errormsg = "Failed to open Sieve storage.";
}
if (write(fd_out, byemsg, strlen(byemsg)) < 0) {
if (errno != EAGAIN && errno != EPIPE)
e_error(event, "write(client) failed: %m");
i_fatal("%s", errormsg);
return storage;
}
struct client *
client_create(int fd_in, int fd_out, const char *session_id,
struct event *event, struct mail_user *user,
const struct managesieve_settings *set)
{
struct client *client;
struct sieve_environment svenv;
struct sieve_instance *svinst;
struct sieve_storage *storage;

Stephan Bosch
committed
pool_t pool;
/* Initialize Sieve */
memset((void*)&svenv, 0, sizeof(svenv));
svenv.username = user->username;
(void)mail_user_get_home(user, &svenv.home_dir);
svenv.base_dir = user->set->base_dir;
svenv.event_parent = event;
svenv.flags = SIEVE_FLAG_HOME_RELATIVE;
svinst = sieve_init(&svenv, &managesieve_sieve_callbacks,
(void *) user, set->mail_debug);
/* Get Sieve storage */
storage = client_get_storage(svinst, event, user, fd_out);
/* always use nonblocking I/O */
net_set_nonblock(fd_in, TRUE);
net_set_nonblock(fd_out, TRUE);

Stephan Bosch
committed
pool = pool_alloconly_create("managesieve client", 1024);
client = p_new(pool, struct client, 1);
client->pool = pool;
client->event = event;
event_ref(client->event);
client->set = set;

Stephan Bosch
committed
client->session_id = p_strdup(pool, session_id);
client->fd_in = fd_in;
client->fd_out = fd_out;
client->input = i_stream_create_fd(
fd_in, set->managesieve_max_line_length);
client->output = o_stream_create_fd(fd_out, (size_t)-1);

Stephan Bosch
committed
o_stream_set_no_error_handling(client->output, TRUE);
i_stream_set_name(client->input, "<managesieve client>");
o_stream_set_name(client->output, "<managesieve client>");
o_stream_set_flush_callback(client->output, client_output, client);
client->last_input = ioloop_time;
client->to_idle = timeout_add(CLIENT_IDLE_TIMEOUT_MSECS,
client_idle_timeout, client);
client->cmd.pool = pool_alloconly_create(
MEMPOOL_GROWING"client command", 1024*12);
client->cmd.client = client;

Stephan Bosch
committed
client->cmd.event = event_create(client->event);
client->user = user;
client->svinst = svinst;
struct master_service_anvil_session anvil_session;
mail_user_get_anvil_session(client->user, &anvil_session);
if (master_service_anvil_connect(master_service, &anvil_session, TRUE,
client->anvil_conn_guid))
client->anvil_sent = TRUE;
managesieve_client_count++;
DLLIST_PREPEND(&managesieve_clients, client);
if (hook_client_created != NULL)
hook_client_created(&client);
managesieve_refresh_proctitle();
return client;
}
void client_create_finish(struct client *client)
{
if (client->set->rawlog_dir[0] != '\0') {
(void)iostream_rawlog_create(client->set->rawlog_dir,
&client->input, &client->output);
}
client->parser = managesieve_parser_create(
client->input, client->set->managesieve_max_line_length);
client->io = io_add_istream(client->input, client_input, client);
}
static const char *client_stats(struct client *client)
const struct var_expand_table logout_tab[] = {
{ 'i', dec2str(i_stream_get_absolute_offset(client->input)),
"input" },
{ 'o', dec2str(client->output->offset), "output" },
{ '\0', dec2str(client->put_bytes), "put_bytes" },
{ '\0', dec2str(client->put_count), "put_count" },
{ '\0', dec2str(client->get_bytes), "get_bytes" },
{ '\0', dec2str(client->get_count), "get_count" },
{ '\0', dec2str(client->check_bytes), "check_bytes" },
{ '\0', dec2str(client->check_count), "check_count" },
{ '\0', dec2str(client->deleted_count), "deleted_count" },
{ '\0', dec2str(client->renamed_count), "renamed_count" },
{ '\0', client->session_id, "session" },
{ '\0', NULL, NULL }
};
const struct var_expand_table *user_tab =
mail_user_var_expand_table(client->user);
const struct var_expand_table *tab =
t_var_expand_merge_tables(logout_tab, user_tab);
string_t *str;
str = t_str_new(128);
if (var_expand_with_funcs(str, client->set->managesieve_logout_format,
tab, mail_user_var_expand_func_table,
client->user, &error) < 0) {
e_error(client->event,
"Failed to expand managesieve_logout_format=%s: %s",
client->set->managesieve_logout_format, error);
}
event_add_int(client->event, "net_in_bytes", i_stream_get_absolute_offset(client->input));
event_add_int(client->event, "net_out_bytes", client->output->offset);
return str_c(str);
}
void client_destroy(struct client *client, const char *reason)
{
i_assert(!client->handling_input);
i_assert(!client->destroyed);
client->destroyed = TRUE;
client_disconnect(client, reason);
if (client->command_pending) {
/* try to deinitialize the command */
i_assert(client->cmd.func != NULL);
i_stream_close(client->input);
o_stream_close(client->output);
client->input_pending = FALSE;
ret = client->cmd.func(&client->cmd);
i_assert(ret);
}
if (client->anvil_sent) {
struct master_service_anvil_session anvil_session;
mail_user_get_anvil_session(client->user, &anvil_session);
master_service_anvil_disconnect(master_service, &anvil_session,
client->anvil_conn_guid);
}
managesieve_parser_destroy(&client->parser);
io_remove(&client->io);
timeout_remove(&client->to_idle_output);
timeout_remove(&client->to_idle);

Stephan Bosch
committed
/* i/ostreams are already closed at this stage, so fd can be closed */
fd_close_maybe_stdio(&client->fd_in, &client->fd_out);
/* Free the user after client is already disconnected. */
mail_user_deinit(&client->user);

Stephan Bosch
committed
/* free the i/ostreams after mail_user_unref(), which could trigger
mail_storage_callbacks notifications that write to the ostream. */

Stephan Bosch
committed
i_stream_destroy(&client->input);
o_stream_destroy(&client->output);
sieve_storage_unref(&client->storage);
sieve_deinit(&client->svinst);

Stephan Bosch
committed
event_unref(&client->cmd.event);
pool_unref(&client->cmd.pool);

Stephan Bosch
committed
managesieve_client_count--;
DLLIST_REMOVE(&managesieve_clients, client);
event_unref(&client->event);

Stephan Bosch
committed
pool_unref(&client->pool);
master_service_client_connection_destroyed(master_service);
managesieve_refresh_proctitle();
}

Stephan Bosch
committed
static void client_destroy_timeout(struct client *client)
{
client_destroy(client, NULL);
}
void client_disconnect(struct client *client, const char *reason)
{
if (client->disconnected)
return;
if (reason == NULL) {
reason = io_stream_get_disconnect_reason(client->input,
client->output);
e_info(client->event, "%s %s", reason, client_stats(client));
e_info(client->event, "Disconnected: %s %s",
reason, client_stats(client));
client->disconnected = TRUE;

Stephan Bosch
committed
o_stream_flush(client->output);
o_stream_uncork(client->output);
i_stream_close(client->input);
o_stream_close(client->output);

Stephan Bosch
committed
timeout_remove(&client->to_idle);
if (!client->destroyed)
client->to_idle = timeout_add(0, client_destroy_timeout, client);
}
void client_disconnect_with_error(struct client *client, const char *msg)
{
client_send_bye(client, msg);
client_disconnect(client, msg);
}
int client_send_line(struct client *client, const char *data)
{
struct const_iovec iov[2];
if (client->output->closed)
return -1;
iov[0].iov_base = data;
iov[0].iov_len = strlen(data);
iov[1].iov_base = "\r\n";
iov[1].iov_len = 2;
if (o_stream_sendv(client->output, iov, 2) < 0)
return -1;
client->last_output = ioloop_time;
if (o_stream_get_buffer_used_size(client->output) >=
CLIENT_OUTPUT_OPTIMAL_SIZE) {
/* buffer full, try flushing */
return o_stream_flush(client->output);
}
return 1;
}
void client_send_response(struct client *client, const char *oknobye,
const char *resp_code, const char *msg)
{
string_t *str;
str = t_str_new(128);
str_append(str, oknobye);
str_append(str, " (");
str_append(str, resp_code);
str_append_c(str, ')');
}
str_append_c(str, ' ');
managesieve_quote_append_string(str, msg, TRUE);
}
client_send_line(client, str_c(str));
}
struct event_passthrough *
client_command_create_finish_event(struct client_command_context *cmd)
{
uint64_t bytes_in = i_stream_get_absolute_offset(cmd->client->input) -
cmd->stats.bytes_in;
uint64_t bytes_out = cmd->client->output->offset - cmd->stats.bytes_out;
struct event_passthrough *e =
event_create_passthrough(cmd->event)->
set_name("managesieve_command_finished")->
add_int("net_in_bytes", bytes_in)->
add_int("net_out_bytes", bytes_out);
void client_send_command_error(struct client_command_context *cmd,
const char *msg)
{
struct client *client = cmd->client;
const char *error, *cmd_name;
bool fatal;
if (msg == NULL) {
msg = managesieve_parser_get_error(client->parser, &fatal);
if (fatal) {
client_disconnect_with_error(client, msg);
return;
}
}
if (cmd->name == NULL) {
error = t_strconcat("Error in MANAGESIEVE command: ",
msg, NULL);
} else {
cmd_name = t_str_ucase(cmd->name);
error = t_strconcat("Error in MANAGESIEVE command ",
cmd_name, ": ", msg, NULL);
}
client_send_no(client, error);
if (++client->bad_counter >= CLIENT_MAX_BAD_COMMANDS) {
client_disconnect_with_error(
client, "Too many invalid MANAGESIEVE commands.");
}
/* client_read_args() failures rely on this being set, so that the
command processing is stopped even while command function returns
FALSE. */
cmd->param_error = TRUE;
}
#undef client_command_storage_error
void client_command_storage_error(struct client_command_context *cmd,
const char *source_filename,
unsigned int source_linenum,
const char *log_prefix, ...)
{
struct event_log_params params = {
.log_type = LOG_TYPE_INFO,
.source_filename = source_filename,
.source_linenum = source_linenum,
};
struct client *client = cmd->client;
struct sieve_storage *storage = client->storage;
enum sieve_error error_code;
const char *error;
error = sieve_storage_get_last_error(storage, &error_code);
client_send_noresp(client, "TRYLATER", error);
break;
client_send_noresp(client, "QUOTA", error);
break;
client_send_noresp(client, "NONEXISTENT", error);
break;
client_send_noresp(client, "ACTIVE", error);
break;
client_send_noresp(client, "ALREADYEXISTS", error);
break;
case SIEVE_ERROR_NOT_POSSIBLE:
default:
client_send_no(client, error);
break;
}
struct event_passthrough *e =
client_command_create_finish_event(cmd)->
add_str("error", error);
va_start(args, log_prefix);
event_log(e->event(), ¶ms, "%s: %s",
t_strdup_vprintf(log_prefix, args), error);
va_end(args);
}
bool client_read_args(struct client_command_context *cmd, unsigned int count,
unsigned int flags, bool no_more,
const struct managesieve_arg **args_r)
{

Stephan Bosch
committed
const struct managesieve_arg *dummy_args_r = NULL;

Stephan Bosch
committed
string_t *str;
int ret;
if (args_r == NULL)
args_r = &dummy_args_r;

Stephan Bosch
committed
i_assert(count <= INT_MAX);
ret = managesieve_parser_read_args(cmd->client->parser,
(no_more ? 0 : count),
flags, args_r);
if (ret >= 0) {
if (count > 0 || no_more) {
if (ret < (int)count) {
client_send_command_error(
cmd, "Missing arguments.");

Stephan Bosch
committed
return FALSE;
} else if (no_more && ret > (int)count) {
client_send_command_error(
cmd, "Too many arguments.");

Stephan Bosch
committed
return FALSE;
}
}

Stephan Bosch
committed
str = t_str_new(256);
managesieve_write_args(str, *args_r);
cmd->args = p_strdup(cmd->pool, str_c(str));
event_add_str(cmd->event, "cmd_args", cmd->args);

Stephan Bosch
committed
/* all parameters read successfully */
return TRUE;
} else if (ret == -2) {
/* need more data */
if (cmd->client->input->closed) {
/* disconnected */
cmd->param_error = TRUE;
}
return FALSE;
} else {

Stephan Bosch
committed
/* error */
client_send_command_error(cmd, NULL);
return FALSE;
}
}
bool client_read_string_args(struct client_command_context *cmd,
bool no_more, unsigned int count, ...)
{

Stephan Bosch
committed
const struct managesieve_arg *msieve_args;
va_list va;
const char *str;
unsigned int i;
bool result = TRUE;
if (!client_read_args(cmd, count, 0, no_more, &msieve_args))
return FALSE;
va_start(va, count);
for (i = 0; i < count; i++) {
const char **ret = va_arg(va, const char **);
if (MANAGESIEVE_ARG_IS_EOL(&msieve_args[i])) {
client_send_command_error(cmd, "Missing arguments.");
result = FALSE;
break;
}
if (!managesieve_arg_get_string(&msieve_args[i], &str)) {
client_send_command_error(cmd, "Invalid arguments.");
result = FALSE;
break;
}
if (ret != NULL)
*ret = str;
}
va_end(va);
return result;
}
void _client_reset_command(struct client *client)
{
pool_t pool;
size_t size;
/* reset input idle time because command output might have taken a long
time and we don't want to disconnect client immediately then */
client->last_input = ioloop_time;
timeout_reset(client->to_idle);
client->command_pending = FALSE;
if (client->io == NULL && !client->disconnected) {
i_assert(i_stream_get_fd(client->input) >= 0);
client->io = io_add(i_stream_get_fd(client->input),
IO_READ, client_input, client);
}
o_stream_set_flush_callback(client->output, client_output, client);

Stephan Bosch
committed
event_unref(&client->cmd.event);
pool = client->cmd.pool;

Stephan Bosch
committed
i_zero(&client->cmd);
p_clear(pool);
client->cmd.pool = pool;
client->cmd.client = client;

Stephan Bosch
committed
client->cmd.event = event_create(client->event);
managesieve_parser_reset(client->parser);
/* if there's unread data in buffer, remember that there's input pending
and we should get around to calling client_input() soon. This is
mostly for APPEND/IDLE. */
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
(void)i_stream_get_data(client->input, &size);
if (size > 0 && !client->destroyed)
client->input_pending = TRUE;
}
/* Skip incoming data until newline is found,
returns TRUE if newline was found. */
static bool client_skip_line(struct client *client)
{
const unsigned char *data;
size_t i, data_size;
data = i_stream_get_data(client->input, &data_size);
for (i = 0; i < data_size; i++) {
if (data[i] == '\n') {
client->input_skip_line = FALSE;
i++;
break;
}
}
i_stream_skip(client->input, i);
return !client->input_skip_line;
}
static bool client_handle_input(struct client_command_context *cmd)
{
struct client *client = cmd->client;
if (cmd->func != NULL) {

Stephan Bosch
committed
bool finished;
event_push_global(cmd->event);
finished = cmd->func(cmd);
event_pop_global(cmd->event);
/* command is being executed - continue it */

Stephan Bosch
committed
if (finished || cmd->param_error) {
/* command execution was finished */
if (!cmd->param_error)
client->bad_counter = 0;
_client_reset_command(client);
return TRUE;
}
/* unfinished */
if (client->command_pending)
o_stream_set_flush_pending(client->output, TRUE);
return FALSE;
}
if (client->input_skip_line) {
/* we're just waiting for new line.. */
if (!client_skip_line(client))
return FALSE;
/* got the newline */
_client_reset_command(client);
/* pass through to parse next command */
}
if (cmd->name == NULL) {
cmd->name = managesieve_parser_read_word(client->parser);
if (cmd->name == NULL)
return FALSE; /* need more data */
cmd->name = p_strdup(cmd->pool, cmd->name);
managesieve_refresh_proctitle();
}
/* command not given - cmd_func is already NULL. */
} else {
/* find the command function */
struct command *command = command_find(cmd->name);
cmd->func = command->func;
}
client->input_skip_line = TRUE;
if (cmd->func == NULL) {
/* unknown command */
client_send_command_error(cmd, "Unknown command.");
_client_reset_command(client);
} else {
i_assert(!client->disconnected);
event_add_str(cmd->event, "cmd_name", t_str_ucase(cmd->name));
cmd->stats.bytes_in = i_stream_get_absolute_offset(client->input);
cmd->stats.bytes_out = client->output->offset;
client_handle_input(cmd);
}
return TRUE;
}
void client_input(struct client *client)
{
struct client_command_context *cmd = &client->cmd;
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
if (client->command_pending) {
/* already processing one command. wait. */
io_remove(&client->io);
return;
}
client->input_pending = FALSE;
client->last_input = ioloop_time;
timeout_reset(client->to_idle);
switch (i_stream_read(client->input)) {
case -1:
/* disconnected */
client_destroy(client, NULL);
return;
case -2:
/* parameter word is longer than max. input buffer size.
this is most likely an error, so skip the new data
until newline is found. */
client->input_skip_line = TRUE;
client_send_command_error(cmd, "Too long argument.");
_client_reset_command(client);
break;
}
client->handling_input = TRUE;
o_stream_cork(client->output);
do {
T_BEGIN {
ret = client_handle_input(cmd);
} T_END;
} while (ret && !client->disconnected);
o_stream_uncork(client->output);
client->handling_input = FALSE;
if (client->command_pending)
client->input_pending = TRUE;
if (client->output->closed)
client_destroy(client, NULL);
}
int client_output(struct client *client)
{
struct client_command_context *cmd = &client->cmd;
int ret;
bool finished;
client->last_output = ioloop_time;
timeout_reset(client->to_idle);
if (client->to_idle_output != NULL)
timeout_reset(client->to_idle_output);
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
if ((ret = o_stream_flush(client->output)) < 0) {
client_destroy(client, NULL);
return 1;
}
if (!client->command_pending)
return 1;
/* continue processing command */
o_stream_cork(client->output);
client->output_pending = TRUE;
finished = cmd->func(cmd) || cmd->param_error;
/* a bit kludgy check. normally we would want to get back to this
output handler, but IDLE is a special case which has command
pending but without necessarily anything to write. */
if (!finished && client->output_pending)
o_stream_set_flush_pending(client->output, TRUE);
o_stream_uncork(client->output);
if (finished) {
/* command execution was finished */
client->bad_counter = 0;
_client_reset_command(client);
if (client->input_pending)
client_input(client);
}
return ret;
}
void client_kick(struct client *client)
{

Timo Sirainen
committed
mail_storage_service_io_activate_user(client->user->service_user);
if (!client->command_pending)
client_send_bye(client, MASTER_SERVICE_SHUTTING_DOWN_MSG".");
client_destroy(client, MASTER_SERVICE_SHUTTING_DOWN_MSG);
void clients_destroy_all(void)
{
while (managesieve_clients != NULL)
client_kick(managesieve_clients);