#include "login-common.h"
#include "buffer.h"
#include "ioloop.h"
#include "istream.h"
#include "ostream.h"
#include "safe-memset.h"
#include "str.h"
#include "strescape.h"
#include "master-service.h"
#include "master-auth.h"
#include "auth-client.h"
#include "ssl-proxy.h"
#include "managesieve-parser.h"
#include "managesieve-quote.h"
#include "client.h"
#include "client-authenticate.h"
#include "managesieve-login-settings.h"
#include "managesieve-proxy.h"
#include <stdlib.h>
#define CLIENT_MAX_BAD_COMMANDS 3
struct managesieve_command {
const char *name;
int (*func)
(struct managesieve_client *client, const struct managesieve_arg *args);
int preparsed_args;
};
bool client_skip_line(struct managesieve_client *client)
{
const unsigned char *data;
size_t i, data_size;
data = i_stream_get_data(client->common.input, &data_size);
for (i = 0; i < data_size; i++) {
if (data[i] == '\n') {
i_stream_skip(client->common.input, i+1);
return TRUE;
}
}
return FALSE;
}
static void client_send_capabilities(struct client *client)
{
struct managesieve_client *msieve_client =
(struct managesieve_client *) client;
const char *saslcap;
T_BEGIN {
saslcap = client_authenticate_get_capabilities(client);
client_send_raw(client, t_strconcat("\"IMPLEMENTATION\" \"",
msieve_client->set->managesieve_implementation_string, "\"\r\n", NULL));
client_send_raw(client, t_strconcat("\"SIEVE\" \"",
msieve_client->set->managesieve_sieve_capability, "\"\r\n", NULL));
if ( msieve_client->set->managesieve_notify_capability != NULL )
client_send_raw(client, t_strconcat("\"NOTIFY\" \"",
msieve_client->set->managesieve_notify_capability, "\"\r\n", NULL));
client_send_raw
(client, t_strconcat("\"SASL\" \"", saslcap, "\"\r\n", NULL));
if (ssl_initialized && !client->tls)
client_send_raw(client, "\"STARTTLS\"\r\n" );
client_send_raw(client, "\"VERSION\" \"1.0\"\r\n");
} T_END;
}
static int cmd_capability
(struct managesieve_client *client,
const struct managesieve_arg *args ATTR_UNUSED)
{
o_stream_cork(client->common.output);
client_send_capabilities(&client->common);
client_send_ok(&client->common, "Capability completed.");
o_stream_uncork(client->common.output);
return 1;
}
static int cmd_starttls
(struct managesieve_client *client,
const struct managesieve_arg *args ATTR_UNUSED)
{
client_cmd_starttls(&client->common);
return 1;
}
static void managesieve_client_notify_starttls
(struct client *client, bool success, const char *text)
{
if ( success )
client_send_ok(client, text);
else
client_send_no(client, text);
}
static int cmd_noop
(struct managesieve_client *client,
const struct managesieve_arg *args)
{
const char *text;
string_t *resp_code;
if ( MANAGESIEVE_ARG_IS_EOL(&args[0]) ) {
client_send_ok(&client->common, "NOOP Completed");
return 1;
}
if ( !MANAGESIEVE_ARG_IS_EOL(&args[1]) )
return -1;
if ( !managesieve_arg_get_string(&args[0], &text) ) {
client_send_no(&client->common, "Invalid echo tag.");
return 1;
}
resp_code = t_str_new(256);
str_append(resp_code, "TAG ");
managesieve_quote_append_string(resp_code, text, FALSE);
client_send_okresp(&client->common, str_c(resp_code), "Done");
return 1;
}
static int cmd_logout
(struct managesieve_client *client,
const struct managesieve_arg *args ATTR_UNUSED)
{
client_send_ok(&client->common, "Logout completed.");
client_destroy(&client->common, "Aborted login");
return 1;
}
static int cmd_forbidden(struct managesieve_client *client,
const struct managesieve_arg *args ATTR_UNUSED)
{
client_send_bye(&client->common, "Invalid MANAGESIEVE command.");
client_destroy(&client->common, "Disconnected due to forbidden command");
return 1;
}
static struct managesieve_command commands[] = {
{ "AUTHENTICATE", cmd_authenticate, 1 },
{ "CAPABILITY", cmd_capability, -1 },
{ "STARTTLS", cmd_starttls, -1 },
{ "NOOP", cmd_noop, 0 },
{ "LOGOUT", cmd_logout, -1},
{ "CONNECT", cmd_forbidden, -2 },
{ "GET", cmd_forbidden, -2 },
{ "POST", cmd_forbidden, -2 },
{ NULL, NULL, 0}
};
static bool client_handle_input(struct managesieve_client *client)
{
const struct managesieve_arg *args = NULL;
const char *msg;
int ret = 1;
bool fatal;
i_assert(!client->common.authenticating);
if (client->cmd_finished) {
client->cmd_name = NULL;
client->cmd_parsed_args = FALSE;
client->cmd = NULL;
managesieve_parser_reset(client->parser);
if (client->skip_line) {
if (!client_skip_line(client))
return FALSE;
client->skip_line = FALSE;
}
client->cmd_finished = FALSE;
}
if (client->cmd == NULL) {
struct managesieve_command *cmd;
const char *cmd_name;
client->cmd_name = managesieve_parser_read_word(client->parser);
if (client->cmd_name == NULL)
return FALSE;
cmd_name = t_str_ucase(client->cmd_name);
cmd = commands;
while ( cmd->name != NULL ) {
if ( strcmp(cmd->name, cmd_name) == 0 )
break;
cmd++;
}
if ( cmd->name != NULL )
client->cmd = cmd;
else
client->skip_line = TRUE;
}
if ( client->cmd != NULL && !client->cmd_parsed_args ) {
unsigned int arg_count =
( client->cmd->preparsed_args > 0 ? client->cmd->preparsed_args : 0 );
switch (managesieve_parser_read_args(client->parser, arg_count, 0, &args)) {
case -1:
msg = managesieve_parser_get_error(client->parser, &fatal);
if (fatal) {
client_send_bye(&client->common, msg);
client_destroy(&client->common, t_strconcat("Disconnected: ",
msg, NULL));
return FALSE;
}
client_send_no(&client->common, msg);
client->cmd_finished = TRUE;
client->skip_line = TRUE;
return TRUE;
case -2:
return FALSE;
}
if (arg_count == 0 ) {
if (!client_skip_line(client))
i_unreached();
} else {
client->skip_line = TRUE;
}
client->cmd_parsed_args = TRUE;
if (client->cmd->preparsed_args == -1) {
if ( args[0].type != MANAGESIEVE_ARG_EOL )
ret = -1;
}
}
if (client->cmd == NULL) {
ret = -1;
client->cmd_finished = TRUE;
} else {
if (ret > 0)
ret = client->cmd->func(client, args);
if (ret != 0)
client->cmd_finished = TRUE;
}
if (ret < 0) {
if (++client->common.bad_counter >= CLIENT_MAX_BAD_COMMANDS) {
client_send_bye(&client->common,
"Too many invalid MANAGESIEVE commands.");
client_destroy(&client->common,
"Disconnected: Too many invalid commands.");
return FALSE;
}
client_send_no(&client->common,
"Error in MANAGESIEVE command received by server.");
}
return ret != 0 && !client->common.destroyed;
}
static void managesieve_client_input(struct client *client)
{
struct managesieve_client *managesieve_client =
(struct managesieve_client *) client;
if (!client_read(client))
return;
client_ref(client);
o_stream_cork(managesieve_client->common.output);
for (;;) {
if (!auth_client_is_connected(auth_client)) {
if (client->to_auth_waiting != NULL)
timeout_remove(&client->to_auth_waiting);
client->input_blocked = TRUE;
break;
} else {
if (!client_handle_input(managesieve_client))
break;
}
}
o_stream_uncork(managesieve_client->common.output);
client_unref(&client);
}
static struct client *managesieve_client_alloc(pool_t pool)
{
struct managesieve_client *msieve_client;
msieve_client = p_new(pool, struct managesieve_client, 1);
return &msieve_client->common;
}
static void managesieve_client_create
(struct client *client, void **other_sets)
{
struct managesieve_client *msieve_client =
(struct managesieve_client *) client;
msieve_client->set = other_sets[0];
msieve_client->parser = managesieve_parser_create
(msieve_client->common.input, MAX_MANAGESIEVE_LINE);
client->io = io_add(client->fd, IO_READ, client_input, client);
}
static void managesieve_client_destroy(struct client *client)
{
struct managesieve_client *managesieve_client =
(struct managesieve_client *) client;
managesieve_parser_destroy(&managesieve_client->parser);
}
static void managesieve_client_notify_auth_ready(struct client *client)
{
o_stream_cork(client->output);
client_send_capabilities(client);
client_send_ok(client, client->set->login_greeting);
o_stream_uncork(client->output);
}
static void managesieve_client_starttls(struct client *client)
{
struct managesieve_client *msieve_client =
(struct managesieve_client *) client;
managesieve_parser_destroy(&msieve_client->parser);
msieve_client->parser = managesieve_parser_create
(msieve_client->common.input, MAX_MANAGESIEVE_LINE);
msieve_client->skip_line = FALSE;
o_stream_cork(client->output);
client_send_capabilities(client);
client_send_ok(client, "TLS negotiation successful.");
o_stream_uncork(client->output);
}
static void
client_send_reply_raw(struct client *client,
const char *prefix, const char *resp_code,
const char *text)
{
T_BEGIN {
string_t *line = t_str_new(256);
str_append(line, prefix);
if (resp_code != NULL) {
str_append(line, " (");
str_append(line, resp_code);
str_append_c(line, ')');
}
if ( text != NULL ) {
str_append_c(line, ' ');
managesieve_quote_append_string(line, text, TRUE);
}
str_append(line, "\r\n");
client_send_raw_data(client, str_data(line), str_len(line));
} T_END;
}
void client_send_reply_code
(struct client *client, enum managesieve_cmd_reply reply, const char *resp_code,
const char *text)
{
const char *prefix = "NO";
switch (reply) {
case MANAGESIEVE_CMD_REPLY_OK:
prefix = "OK";
break;
case MANAGESIEVE_CMD_REPLY_NO:
break;
case MANAGESIEVE_CMD_REPLY_BYE:
prefix = "BYE";
break;
}
client_send_reply_raw(client, prefix, resp_code, text);
}
void client_send_reply
(struct client *client, enum managesieve_cmd_reply reply, const char *text)
{
client_send_reply_code(client, reply, NULL, text);
}
static void
managesieve_client_notify_disconnect
(struct client *client, enum client_disconnect_reason reason, const char *text)
{
if ( reason == CLIENT_DISCONNECT_SYSTEM_SHUTDOWN ) {
client_send_reply_code
(client, MANAGESIEVE_CMD_REPLY_BYE, "TRYLATER", text);
} else {
client_send_reply_code
(client, MANAGESIEVE_CMD_REPLY_BYE, NULL, text);
}
}
static void managesieve_login_preinit(void)
{
login_set_roots = managesieve_login_settings_set_roots;
}
static void managesieve_login_init(void)
{
}
static void managesieve_login_deinit(void)
{
clients_destroy_all();
}
static struct client_vfuncs managesieve_client_vfuncs = {
managesieve_client_alloc,
managesieve_client_create,
managesieve_client_destroy,
managesieve_client_notify_auth_ready,
managesieve_client_notify_disconnect,
NULL,
managesieve_client_notify_starttls,
managesieve_client_starttls,
managesieve_client_input,
managesieve_client_auth_send_challenge,
managesieve_client_auth_parse_response,
managesieve_client_auth_result,
managesieve_proxy_reset,
managesieve_proxy_parse_line,
managesieve_proxy_error
};
static const struct login_binary managesieve_login_binary = {
.protocol = "sieve",
.process_name = "managesieve-login",
.default_port = 4190,
.client_vfuncs = &managesieve_client_vfuncs,
.preinit = managesieve_login_preinit,
.init = managesieve_login_init,
.deinit = managesieve_login_deinit
};
int main(int argc, char *argv[])
{
return login_binary_run(&managesieve_login_binary, argc, argv);
}