summaryrefslogtreecommitdiffstats
path: root/src/plugins/zlib/zlib-plugin.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/zlib/zlib-plugin.c')
-rw-r--r--src/plugins/zlib/zlib-plugin.c388
1 files changed, 388 insertions, 0 deletions
diff --git a/src/plugins/zlib/zlib-plugin.c b/src/plugins/zlib/zlib-plugin.c
new file mode 100644
index 0000000..f716058
--- /dev/null
+++ b/src/plugins/zlib/zlib-plugin.c
@@ -0,0 +1,388 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "istream.h"
+#include "istream-seekable.h"
+#include "ostream.h"
+#include "str.h"
+#include "mail-user.h"
+#include "index-storage.h"
+#include "index-mail.h"
+#include "compression.h"
+#include "zlib-plugin.h"
+
+#include <fcntl.h>
+
+#define ZLIB_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, zlib_storage_module)
+#define ZLIB_MAIL_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, zlib_mail_module)
+#define ZLIB_USER_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, zlib_user_module)
+
+#define MAX_INBUF_SIZE (1024*1024)
+#define ZLIB_MAIL_CACHE_EXPIRE_MSECS (60*1000)
+
+struct zlib_mail {
+ union mail_module_context module_ctx;
+ bool verifying_save;
+};
+
+struct zlib_mail_cache {
+ struct timeout *to;
+ struct mailbox *box;
+ uint32_t uid;
+
+ struct istream *input;
+};
+
+struct zlib_user {
+ union mail_user_module_context module_ctx;
+
+ struct zlib_mail_cache cache;
+
+ const struct compression_handler *save_handler;
+ int save_level;
+};
+
+const char *zlib_plugin_version = DOVECOT_ABI_VERSION;
+
+static MODULE_CONTEXT_DEFINE_INIT(zlib_user_module,
+ &mail_user_module_register);
+static MODULE_CONTEXT_DEFINE_INIT(zlib_storage_module,
+ &mail_storage_module_register);
+static MODULE_CONTEXT_DEFINE_INIT(zlib_mail_module, &mail_module_register);
+
+static bool zlib_mailbox_is_permail(struct mailbox *box)
+{
+ enum mail_storage_class_flags class_flags = box->storage->class_flags;
+
+ return (class_flags & MAIL_STORAGE_CLASS_FLAG_OPEN_STREAMS) == 0 &&
+ (class_flags & MAIL_STORAGE_CLASS_FLAG_BINARY_DATA) != 0;
+}
+
+static void zlib_mail_cache_close(struct zlib_user *zuser)
+{
+ struct zlib_mail_cache *cache = &zuser->cache;
+
+ timeout_remove(&cache->to);
+ i_stream_unref(&cache->input);
+ i_zero(cache);
+}
+
+static struct istream *
+zlib_mail_cache_open(struct zlib_user *zuser, struct mail *mail,
+ struct istream *input, bool do_cache)
+{
+ struct zlib_mail_cache *cache = &zuser->cache;
+ struct istream *inputs[2];
+ string_t *temp_prefix = t_str_new(128);
+
+ if (do_cache)
+ zlib_mail_cache_close(zuser);
+
+ /* zlib istream is seekable, but very slow. create a seekable istream
+ which we can use to quickly seek around in the stream that's been
+ read so far. usually the partial IMAP FETCHes continue from where
+ the previous left off, so this isn't strictly necessary, but with
+ the way lib-imap-storage's CRLF-cache works it has to seek backwards
+ somewhat, which causes a zlib stream reset. And the CRLF-cache isn't
+ easy to fix.. */
+ input->seekable = FALSE;
+ inputs[0] = input;
+ inputs[1] = NULL;
+ mail_user_set_get_temp_prefix(temp_prefix, mail->box->storage->user->set);
+ input = i_stream_create_seekable_path(inputs,
+ i_stream_get_max_buffer_size(inputs[0]),
+ str_c(temp_prefix));
+ i_stream_set_name(input, t_strdup_printf("compress(%s)",
+ i_stream_get_name(inputs[0])));
+ i_stream_unref(&inputs[0]);
+
+ if (do_cache) {
+ cache->to = timeout_add(ZLIB_MAIL_CACHE_EXPIRE_MSECS,
+ zlib_mail_cache_close, zuser);
+ cache->box = mail->box;
+ cache->uid = mail->uid;
+ cache->input = input;
+ /* index-mail wants the stream to be destroyed at close, so create
+ a new stream instead of just increasing reference. */
+ return i_stream_create_limit(cache->input, UOFF_T_MAX);
+ } else {
+ return input;
+ }
+}
+
+static int zlib_istream_opened(struct mail *_mail, struct istream **stream)
+{
+ struct zlib_user *zuser = ZLIB_USER_CONTEXT(_mail->box->storage->user);
+ struct zlib_mail_cache *cache = &zuser->cache;
+ struct mail_private *mail = (struct mail_private *)_mail;
+ struct zlib_mail *zmail = ZLIB_MAIL_CONTEXT(mail);
+ struct istream *input;
+ const struct compression_handler *handler;
+
+ if (zmail->verifying_save) {
+ /* zlib_mail_save_finish() is verifying that the user-given
+ input doesn't look compressed. */
+ return zmail->module_ctx.super.istream_opened(_mail, stream);
+ }
+
+ if (_mail->uid > 0 && cache->uid == _mail->uid && cache->box == _mail->box) {
+ /* use the cached stream. when doing partial reads it should
+ already be seeked into the wanted offset. */
+ i_stream_unref(stream);
+ i_stream_seek(cache->input, 0);
+ *stream = i_stream_create_limit(cache->input, UOFF_T_MAX);
+ return zmail->module_ctx.super.istream_opened(_mail, stream);
+ }
+
+ handler = compression_detect_handler(*stream);
+ if (handler != NULL) {
+ if (handler->create_istream == NULL) {
+ mail_set_critical(_mail,
+ "zlib plugin: Detected %s compression "
+ "but support not compiled in", handler->ext);
+ return -1;
+ }
+
+ input = *stream;
+ *stream = handler->create_istream(input);
+ i_stream_unref(&input);
+ /* dont cache the stream if _mail->uid is 0 */
+ *stream = zlib_mail_cache_open(zuser, _mail, *stream, (_mail->uid > 0));
+ }
+ return zmail->module_ctx.super.istream_opened(_mail, stream);
+}
+
+static void zlib_mail_close(struct mail *_mail)
+{
+ struct mail_private *mail = (struct mail_private *)_mail;
+ struct zlib_mail *zmail = ZLIB_MAIL_CONTEXT(mail);
+ struct zlib_user *zuser = ZLIB_USER_CONTEXT(_mail->box->storage->user);
+ struct zlib_mail_cache *cache = &zuser->cache;
+ uoff_t size;
+
+ if (_mail->uid > 0 && cache->uid == _mail->uid && cache->box == _mail->box) {
+ /* make sure we have read the entire email into the seekable
+ stream (which causes the original input stream to be
+ unrefed). we can't safely keep the original input stream
+ open after the mail is closed. */
+ if (i_stream_get_size(cache->input, TRUE, &size) < 0)
+ zlib_mail_cache_close(zuser);
+ }
+ zmail->module_ctx.super.close(_mail);
+}
+
+static void zlib_mail_allocated(struct mail *_mail)
+{
+ struct mail_private *mail = (struct mail_private *)_mail;
+ struct mail_vfuncs *v = mail->vlast;
+ struct zlib_mail *zmail;
+
+ if (!zlib_mailbox_is_permail(_mail->box))
+ return;
+
+ zmail = p_new(mail->pool, struct zlib_mail, 1);
+ zmail->module_ctx.super = *v;
+ mail->vlast = &zmail->module_ctx.super;
+
+ v->istream_opened = zlib_istream_opened;
+ v->close = zlib_mail_close;
+ MODULE_CONTEXT_SET(mail, zlib_mail_module, zmail);
+}
+
+static int zlib_mail_save_finish(struct mail_save_context *ctx)
+{
+ struct mailbox *box = ctx->transaction->box;
+ union mailbox_module_context *zbox = ZLIB_CONTEXT(box);
+ struct mail_private *mail = (struct mail_private *)ctx->dest_mail;
+ struct zlib_mail *zmail = ZLIB_MAIL_CONTEXT(mail);
+ struct istream *input;
+ int ret;
+
+ if (zbox->super.save_finish(ctx) < 0)
+ return -1;
+
+ zmail->verifying_save = TRUE;
+ ret = mail_get_stream(ctx->dest_mail, NULL, NULL, &input);
+ zmail->verifying_save = FALSE;
+ if (ret < 0)
+ return -1;
+
+ if (compression_detect_handler(input) != NULL) {
+ mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
+ "Saving mails compressed by client isn't supported");
+ return -1;
+ }
+ return 0;
+}
+
+static int
+zlib_mail_save_compress_begin(struct mail_save_context *ctx,
+ struct istream *input)
+{
+ struct mailbox *box = ctx->transaction->box;
+ struct zlib_user *zuser = ZLIB_USER_CONTEXT(box->storage->user);
+ union mailbox_module_context *zbox = ZLIB_CONTEXT(box);
+ struct ostream *output;
+
+ if (zbox->super.save_begin(ctx, input) < 0)
+ return -1;
+
+ output = zuser->save_handler->create_ostream(ctx->data.output,
+ zuser->save_level);
+ o_stream_unref(&ctx->data.output);
+ ctx->data.output = output;
+ o_stream_cork(ctx->data.output);
+ return 0;
+}
+
+static void
+zlib_permail_alloc_init(struct mailbox *box, struct mailbox_vfuncs *v)
+{
+ struct zlib_user *zuser = ZLIB_USER_CONTEXT(box->storage->user);
+
+ if (zuser->save_handler == NULL) {
+ v->save_finish = zlib_mail_save_finish;
+ } else {
+ v->save_begin = zlib_mail_save_compress_begin;
+ }
+}
+
+static void zlib_mailbox_open_input(struct mailbox *box)
+{
+ const struct compression_handler *handler;
+ struct istream *input;
+ struct stat st;
+ int fd;
+
+ if (compression_lookup_handler_from_ext(box->name, &handler) <= 0)
+ return;
+
+ if (mail_storage_is_mailbox_file(box->storage)) {
+ /* looks like a compressed single file mailbox. we should be
+ able to handle this. */
+ const char *box_path = mailbox_get_path(box);
+
+ fd = open(box_path, O_RDONLY);
+ if (fd == -1) {
+ /* let the standard handler figure out what to do
+ with the failure */
+ return;
+ }
+ if (fstat(fd, &st) == 0 && S_ISDIR(st.st_mode)) {
+ i_close_fd(&fd);
+ return;
+ }
+ input = i_stream_create_fd_autoclose(&fd, MAX_INBUF_SIZE);
+ i_stream_set_name(input, box_path);
+ box->input = handler->create_istream(input);
+ i_stream_unref(&input);
+ box->flags |= MAILBOX_FLAG_READONLY;
+ }
+}
+
+static int zlib_mailbox_open(struct mailbox *box)
+{
+ union mailbox_module_context *zbox = ZLIB_CONTEXT(box);
+
+ if (box->input == NULL &&
+ (box->storage->class_flags &
+ MAIL_STORAGE_CLASS_FLAG_OPEN_STREAMS) != 0)
+ zlib_mailbox_open_input(box);
+
+ return zbox->super.open(box);
+}
+
+static void zlib_mailbox_close(struct mailbox *box)
+{
+ union mailbox_module_context *zbox = ZLIB_CONTEXT(box);
+ struct zlib_user *zuser = ZLIB_USER_CONTEXT(box->storage->user);
+
+ if (zuser->cache.box == box)
+ zlib_mail_cache_close(zuser);
+ zbox->super.close(box);
+}
+
+static void zlib_mailbox_allocated(struct mailbox *box)
+{
+ struct mailbox_vfuncs *v = box->vlast;
+ union mailbox_module_context *zbox;
+
+ zbox = p_new(box->pool, union mailbox_module_context, 1);
+ zbox->super = *v;
+ box->vlast = &zbox->super;
+ v->open = zlib_mailbox_open;
+ v->close = zlib_mailbox_close;
+
+ MODULE_CONTEXT_SET_SELF(box, zlib_storage_module, zbox);
+
+ if (zlib_mailbox_is_permail(box))
+ zlib_permail_alloc_init(box, v);
+}
+
+static void zlib_mail_user_deinit(struct mail_user *user)
+{
+ struct zlib_user *zuser = ZLIB_USER_CONTEXT(user);
+
+ zlib_mail_cache_close(zuser);
+ zuser->module_ctx.super.deinit(user);
+}
+
+static void zlib_mail_user_created(struct mail_user *user)
+{
+ struct mail_user_vfuncs *v = user->vlast;
+ struct zlib_user *zuser;
+ const char *name;
+ int ret;
+
+ zuser = p_new(user->pool, struct zlib_user, 1);
+ zuser->module_ctx.super = *v;
+ user->vlast = &zuser->module_ctx.super;
+ v->deinit = zlib_mail_user_deinit;
+
+ name = mail_user_plugin_getenv(user, "zlib_save");
+ if (name != NULL && *name != '\0') {
+ ret = compression_lookup_handler(name, &zuser->save_handler);
+ if (ret <= 0) {
+ i_error("zlib_save: %s: %s", ret == 0 ?
+ "Support not compiled in for handler" :
+ "Unknown handler", name);
+ zuser->save_handler = NULL;
+ }
+ }
+ name = zuser->save_handler == NULL ? NULL :
+ mail_user_plugin_getenv(user, "zlib_save_level");
+ if (name != NULL && name[0] != '\0') {
+ if (str_to_int(name, &zuser->save_level) < 0 ||
+ zuser->save_level < zuser->save_handler->get_min_level() ||
+ zuser->save_level > zuser->save_handler->get_max_level()) {
+ i_error("zlib_save_level: Level must be between %d..%d",
+ zuser->save_handler->get_min_level(),
+ zuser->save_handler->get_max_level());
+ zuser->save_level =
+ zuser->save_handler->get_default_level();
+ }
+ } else if (zuser->save_handler != NULL) {
+ zuser->save_level = zuser->save_handler->get_default_level();
+ }
+ MODULE_CONTEXT_SET(user, zlib_user_module, zuser);
+}
+
+static struct mail_storage_hooks zlib_mail_storage_hooks = {
+ .mail_user_created = zlib_mail_user_created,
+ .mailbox_allocated = zlib_mailbox_allocated,
+ .mail_allocated = zlib_mail_allocated
+};
+
+void zlib_plugin_init(struct module *module)
+{
+ mail_storage_hooks_add(module, &zlib_mail_storage_hooks);
+}
+
+void zlib_plugin_deinit(void)
+{
+ mail_storage_hooks_remove(&zlib_mail_storage_hooks);
+}