diff options
Diffstat (limited to '')
-rw-r--r-- | src/plugins/quota/quota-status.c | 360 |
1 files changed, 360 insertions, 0 deletions
diff --git a/src/plugins/quota/quota-status.c b/src/plugins/quota/quota-status.c new file mode 100644 index 0000000..7db0cb4 --- /dev/null +++ b/src/plugins/quota/quota-status.c @@ -0,0 +1,360 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "str-sanitize.h" +#include "ostream.h" +#include "connection.h" +#include "restrict-access.h" +#include "settings-parser.h" +#include "master-service.h" +#include "master-service-settings.h" +#include "mail-namespace.h" +#include "mail-storage.h" +#include "mail-storage-settings.h" +#include "mail-storage-service.h" +#include "smtp-address.h" +#include "quota-private.h" +#include "quota-plugin.h" +#include "quota-status-settings.h" + +enum quota_protocol { + QUOTA_PROTOCOL_UNKNOWN = 0, + QUOTA_PROTOCOL_POSTFIX +}; + +struct quota_client { + struct connection conn; + + struct event *event; + + char *state; + char *recipient; + uoff_t size; + + bool warned_bad_state:1; +}; + +static struct event_category event_category_quota_status = { + .name = "quota-status" +}; + +static struct quota_status_settings *quota_status_settings; +static pool_t quota_status_pool; +static enum quota_protocol protocol; +static struct mail_storage_service_ctx *storage_service; +static struct connection_list *clients; +static char *nouser_reply; + +static void client_connected(struct master_service_connection *conn) +{ + struct quota_client *client; + + client = i_new(struct quota_client, 1); + + client->event = event_create(NULL); + client->conn.event_parent = client->event; + event_add_category(client->event, &event_category_quota_status); + connection_init_server(clients, &client->conn, + "quota-client", conn->fd, conn->fd); + master_service_client_connection_accept(conn); + + e_debug(client->event, "Client connected"); +} + +static void client_reset(struct quota_client *client) +{ + i_free(client->state); + i_free(client->recipient); +} + +static enum quota_alloc_result +quota_check(struct mail_user *user, uoff_t mail_size, const char **error_r) +{ + struct quota_user *quser = QUOTA_USER_CONTEXT(user); + struct mail_namespace *ns; + struct mailbox *box; + struct quota_transaction_context *ctx; + enum quota_alloc_result ret; + + if (quser == NULL) { + /* no quota for user */ + e_debug(user->event, "User has no quota"); + return QUOTA_ALLOC_RESULT_OK; + } + + ns = mail_namespace_find_inbox(user->namespaces); + box = mailbox_alloc(ns->list, "INBOX", MAILBOX_FLAG_POST_SESSION); + + ctx = quota_transaction_begin(box); + const char *internal_error; + ret = quota_test_alloc(ctx, I_MAX(1, mail_size), &internal_error); + if (ret == QUOTA_ALLOC_RESULT_TEMPFAIL) + e_error(user->event, "quota check failed: %s", internal_error); + *error_r = quota_alloc_result_errstr(ret, ctx); + quota_transaction_rollback(&ctx); + + mailbox_free(&box); + return ret; +} + +static int client_check_mta_state(struct quota_client *client) +{ + if (client->state == NULL || strcasecmp(client->state, "RCPT") == 0) + return 0; + + if (!client->warned_bad_state) { + e_warning(client->event, + "Received policy query from MTA in unexpected state %s " + "(service can only be used for recipient restrictions)", + client->state); + } + client->warned_bad_state = TRUE; + return -1; +} + +static void client_handle_request(struct quota_client *client) +{ + struct mail_storage_service_input input; + struct mail_storage_service_user *service_user; + struct mail_user *user; + struct smtp_address *rcpt; + const char *value = NULL, *error; + const char *detail ATTR_UNUSED; + char delim ATTR_UNUSED; + string_t *resp; + int ret; + + if (client_check_mta_state(client) < 0 || client->recipient == NULL) { + e_debug(client->event, "Response: action=DUNNO"); + o_stream_nsend_str(client->conn.output, "action=DUNNO\n\n"); + return; + } + + if (smtp_address_parse_path(pool_datastack_create(), client->recipient, + SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART | + SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL | + SMTP_ADDRESS_PARSE_FLAG_ALLOW_BAD_LOCALPART, + &rcpt, &error) < 0) { + e_error(client->event, + "Client sent invalid recipient address `%s': " + "%s", str_sanitize(client->recipient, 256), error); + e_debug(client->event, "Response: action=DUNNO"); + o_stream_nsend_str(client->conn.output, "action=DUNNO\n\n"); + return; + } + + i_zero(&input); + input.event_parent = client->event; + smtp_address_detail_parse_temp(quota_status_settings->recipient_delimiter, + rcpt, &input.username, &delim, + &detail); + ret = mail_storage_service_lookup_next(storage_service, &input, + &service_user, &user, &error); + restrict_access_allow_coredumps(TRUE); + if (ret == 0) { + e_debug(client->event, "User `%s' not found", input.username); + value = nouser_reply; + } else if (ret > 0) { + enum quota_alloc_result qret = quota_check(user, client->size, + &error); + if (qret == QUOTA_ALLOC_RESULT_OK) { + e_debug(client->event, + "Message is acceptable"); + } else { + e_debug(client->event, + "Quota check failed: %s", error); + } + + switch (qret) { + case QUOTA_ALLOC_RESULT_OK: /* under quota */ + value = mail_user_plugin_getenv(user, + "quota_status_success"); + if (value == NULL) + value = "OK"; + break; + case QUOTA_ALLOC_RESULT_OVER_MAXSIZE: + /* even over maximum quota */ + case QUOTA_ALLOC_RESULT_OVER_QUOTA_LIMIT: + value = mail_user_plugin_getenv(user, + "quota_status_toolarge"); + /* fall through */ + case QUOTA_ALLOC_RESULT_OVER_QUOTA: + if (value == NULL) + value = mail_user_plugin_getenv(user, + "quota_status_overquota"); + if (value == NULL) + value = t_strdup_printf("554 5.2.2 %s", error); + break; + case QUOTA_ALLOC_RESULT_TEMPFAIL: + case QUOTA_ALLOC_RESULT_BACKGROUND_CALC: + ret = -1; + break; + } + value = t_strdup(value); /* user's pool is being freed */ + mail_user_deinit(&user); + mail_storage_service_user_unref(&service_user); + } else { + e_error(client->event, + "Failed to lookup user %s: %s", input.username, error); + error = "Temporary internal error"; + } + + resp = t_str_new(256); + if (ret < 0) { + /* temporary failure */ + str_append(resp, "action=DEFER_IF_PERMIT "); + str_append(resp, error); + } else { + str_append(resp, "action="); + str_append(resp, value); + } + + e_debug(client->event, "Response: %s", str_c(resp)); + str_append(resp, "\n\n"); + o_stream_nsend_str(client->conn.output, str_c(resp)); +} + +static int client_input_line(struct connection *conn, const char *line) +{ + struct quota_client *client = (struct quota_client *)conn; + + e_debug(client->event, "Request: %s", str_sanitize(line, 1024)); + + if (*line == '\0') { + o_stream_cork(conn->output); + client_handle_request(client); + o_stream_uncork(conn->output); + client_reset(client); + return 1; + } + if (str_begins(line, "recipient=")) { + if (client->recipient == NULL) + client->recipient = i_strdup(line + 10); + } else if (str_begins(line, "size=")) { + if (str_to_uoff(line+5, &client->size) < 0) + client->size = 0; + } else if (str_begins(line, "protocol_state=")) { + if (client->state == NULL) + client->state = i_strdup(line + 15); + } + return 1; +} + +static void client_destroy(struct connection *conn) +{ + struct quota_client *client = (struct quota_client *)conn; + + e_debug(client->event, "Client disconnected"); + + connection_deinit(&client->conn); + client_reset(client); + event_unref(&client->event); + i_free(client); + + master_service_client_connection_destroyed(master_service); +} + +static struct connection_settings client_set = { + .input_max_size = SIZE_MAX, + .output_max_size = SIZE_MAX, + .client = FALSE +}; + +static const struct connection_vfuncs client_vfuncs = { + .destroy = client_destroy, + .input_line = client_input_line +}; + +static void main_preinit(void) +{ + restrict_access_by_env(RESTRICT_ACCESS_FLAG_ALLOW_ROOT, NULL); + restrict_access_allow_coredumps(TRUE); +} + +static void main_init(void) +{ + static const struct setting_parser_info *set_roots[] = { + "a_status_setting_parser_info, + NULL + }; + struct mail_storage_service_input input; + const struct setting_parser_info *user_info; + const struct setting_parser_context *set_parser; + const struct mail_user_settings *user_set; + const struct quota_status_settings *set; + const char *value, *error; + pool_t pool; + + clients = connection_list_init(&client_set, &client_vfuncs); + storage_service = mail_storage_service_init(master_service, set_roots, + MAIL_STORAGE_SERVICE_FLAG_ALLOW_ROOT | + MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP | + MAIL_STORAGE_SERVICE_FLAG_TEMP_PRIV_DROP | + MAIL_STORAGE_SERVICE_FLAG_ENABLE_CORE_DUMPS | + MAIL_STORAGE_SERVICE_FLAG_NO_CHDIR); + + i_zero(&input); + input.service = "quota-status"; + input.module = "mail"; + input.username = ""; + + quota_status_pool = pool_alloconly_create("quota status settings", 512); + pool = pool_alloconly_create("service all settings", 4096); + if (mail_storage_service_read_settings(storage_service, &input, pool, + &user_info, &set_parser, + &error) < 0) + i_fatal("%s", error); + user_set = master_service_settings_parser_get_others(master_service, + set_parser)[0]; + set = master_service_settings_get_others(master_service)[1]; + + quota_status_settings = settings_dup("a_status_setting_parser_info, set, + quota_status_pool); + value = mail_user_set_plugin_getenv(user_set, "quota_status_nouser"); + nouser_reply = p_strdup(quota_status_pool, + value != NULL ? value : "REJECT Unknown user"); + pool_unref(&pool); +} + +static void main_deinit(void) +{ + pool_unref("a_status_pool); + connection_list_deinit(&clients); + mail_storage_service_deinit(&storage_service); +} + +int main(int argc, char *argv[]) +{ + enum master_service_flags service_flags = + MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN; + int c; + + protocol = QUOTA_PROTOCOL_UNKNOWN; + master_service = master_service_init("quota-status", service_flags, + &argc, &argv, "p:"); + while ((c = master_getopt(master_service)) > 0) { + switch (c) { + case 'p': + if (strcmp(optarg, "postfix") == 0) + protocol = QUOTA_PROTOCOL_POSTFIX; + else + i_fatal("Unknown -p parameter: '%s'", optarg); + break; + default: + return FATAL_DEFAULT; + } + } + if (protocol == QUOTA_PROTOCOL_UNKNOWN) + i_fatal("Missing -p parameter"); + + master_service_init_log(master_service); + main_preinit(); + + main_init(); + master_service_init_finish(master_service); + master_service_run(master_service, client_connected); + main_deinit(); + master_service_deinit(&master_service); + return 0; +} |