summaryrefslogtreecommitdiffstats
path: root/src/plugins/var-expand-crypt/var-expand-crypt-plugin.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/var-expand-crypt/var-expand-crypt-plugin.c')
-rw-r--r--src/plugins/var-expand-crypt/var-expand-crypt-plugin.c335
1 files changed, 335 insertions, 0 deletions
diff --git a/src/plugins/var-expand-crypt/var-expand-crypt-plugin.c b/src/plugins/var-expand-crypt/var-expand-crypt-plugin.c
new file mode 100644
index 0000000..1f6cce7
--- /dev/null
+++ b/src/plugins/var-expand-crypt/var-expand-crypt-plugin.c
@@ -0,0 +1,335 @@
+/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hex-binary.h"
+#include "base64.h"
+#include "str.h"
+#include "strescape.h"
+#include "var-expand.h"
+#include "var-expand-private.h"
+#include "dcrypt.h"
+
+#define VAR_EXPAND_CRYPT_DEFAULT_ALGO "AES-256-CBC"
+
+struct module;
+
+enum crypt_field_format {
+ FORMAT_HEX,
+ FORMAT_BASE64
+};
+
+struct var_expand_crypt_context {
+ struct var_expand_context *ctx;
+ const char *algo;
+ string_t *iv;
+ string_t *enckey;
+ enum crypt_field_format format;
+ bool enc_result_only:1;
+};
+
+static bool var_expand_crypt_initialize(const char **error_r);
+
+void var_expand_crypt_init(struct module *module);
+void var_expand_crypt_deinit(void);
+void auth_var_expand_crypt_init(struct module *module);
+void auth_var_expand_crypt_deinit(void);
+
+static int
+var_expand_crypt_settings(struct var_expand_crypt_context *ctx,
+ const char *const *args, const char **error_r)
+{
+ while(args != NULL && *args != NULL) {
+ const char *k = t_strcut(*args, '=');
+ const char *value = strchr(*args, '=');
+ if (value == NULL) {
+ args++;
+ continue;
+ } else {
+ value++;
+ }
+
+ if (strcmp(k, "iv") == 0) {
+ str_truncate(ctx->iv, 0);
+ if (var_expand_with_funcs(ctx->iv, value, ctx->ctx->table,
+ ctx->ctx->func_table,
+ ctx->ctx->context, error_r) < 0) {
+ return -1;
+ }
+ const char *hexiv = t_strdup(str_c(ctx->iv));
+ /* try to decode IV */
+ str_truncate(ctx->iv, 0);
+ hex_to_binary(hexiv, ctx->iv);
+ } if (strcmp(k, "noiv") == 0) {
+ ctx->enc_result_only = strcasecmp(value, "yes")==0;
+ } if (strcmp(k, "algo") == 0) {
+ ctx->algo = value;
+ } else if (strcmp(k, "key") == 0) {
+ str_truncate(ctx->enckey, 0);
+ if (var_expand_with_funcs(ctx->enckey, value,
+ ctx->ctx->table,
+ ctx->ctx->func_table,
+ ctx->ctx->context,
+ error_r) < 0) {
+ return -1;
+ }
+ const char *hexkey = t_strdup(str_c(ctx->enckey));
+ str_truncate(ctx->enckey, 0);
+ hex_to_binary(hexkey, ctx->enckey);
+ } else if (strcmp(k, "format") == 0) {
+ if (strcmp(value, "hex") == 0) {
+ ctx->format = FORMAT_HEX;
+ } else if (strcmp(value, "base64") == 0) {
+ ctx->format = FORMAT_BASE64;
+ } else {
+ *error_r = t_strdup_printf(
+ "Cannot parse hash arguments:"
+ "'%s' is not supported format",
+ value);
+ return -1;
+ }
+ }
+ args++;
+ }
+
+ if (ctx->algo == NULL) {
+ ctx->algo = "AES-256-CBC";
+ }
+
+ return 0;
+}
+
+static int
+var_expand_crypt(struct dcrypt_context_symmetric *dctx, buffer_t *key, buffer_t *iv,
+ const buffer_t *input, buffer_t *output, const char **error_r)
+{
+ /* make sure IV is correct */
+ if (iv->used == 0) {
+ dcrypt_ctx_sym_set_key_iv_random(dctx);
+ /* acquire IV */
+ dcrypt_ctx_sym_get_iv(dctx, iv);
+ } else if (dcrypt_ctx_sym_get_iv_length(dctx) != iv->used) {
+ *error_r = t_strdup_printf("crypt: IV length invalid (%zu != %u)",
+ iv->used,
+ dcrypt_ctx_sym_get_iv_length(dctx));
+ return -1;
+ } else {
+ dcrypt_ctx_sym_set_iv(dctx, iv->data, iv->used);
+ }
+
+ if (dcrypt_ctx_sym_get_key_length(dctx) != key->used) {
+ *error_r = t_strdup_printf("crypt: Key length invalid (%zu != %u)",
+ key->used,
+ dcrypt_ctx_sym_get_key_length(dctx));
+ return -1;
+ } else {
+ dcrypt_ctx_sym_set_key(dctx, key->data, key->used);
+ }
+
+ if (!dcrypt_ctx_sym_init(dctx, error_r) ||
+ !dcrypt_ctx_sym_update(dctx, input->data,
+ input->used, output, error_r) ||
+ !dcrypt_ctx_sym_final(dctx, output, error_r))
+ return -1;
+ return 0;
+}
+
+static int
+var_expand_encrypt(struct var_expand_context *_ctx,
+ const char *key, const char *field,
+ const char **result_r, const char **error_r)
+{
+ if (!var_expand_crypt_initialize(error_r))
+ return -1;
+
+ const char *p = strchr(key, ';');
+ const char *const *args = NULL;
+ const char *value;
+ struct var_expand_crypt_context ctx;
+ string_t *dest;
+ int ret = 0;
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.ctx = _ctx;
+ ctx.format = FORMAT_HEX;
+
+ if (p != NULL) {
+ args = t_strsplit(p+1, ",");
+ }
+
+ string_t *field_value = t_str_new(64);
+ ctx.iv = t_str_new(64);
+ ctx.enckey = t_str_new(64);
+ string_t *tmp = t_str_new(128);
+
+ if ((ret = var_expand_long(_ctx, field, strlen(field),
+ &value, error_r)) < 1) {
+ return ret;
+ }
+
+ if (*value == '\0') {
+ *result_r = value;
+ return ret;
+ }
+
+ if (var_expand_crypt_settings(&ctx, args, error_r) < 0)
+ return -1;
+
+ str_append(field_value, value);
+
+ struct dcrypt_context_symmetric *dctx;
+ if (!dcrypt_ctx_sym_create(ctx.algo, DCRYPT_MODE_ENCRYPT, &dctx, error_r))
+ return -1;
+
+ ret = var_expand_crypt(dctx, ctx.enckey, ctx.iv, field_value, tmp, error_r);
+ dcrypt_ctx_sym_destroy(&dctx);
+
+ if (ret == 0) {
+ /* makes compiler happy */
+ const char *enciv = "";
+ const char *res = "";
+
+ switch(ctx.format) {
+ case FORMAT_HEX:
+ enciv = binary_to_hex(ctx.iv->data, ctx.iv->used);
+ res = binary_to_hex(tmp->data, tmp->used);
+ break;
+ case FORMAT_BASE64:
+ dest = t_str_new(32);
+ base64_encode(ctx.iv->data, ctx.iv->used, dest);
+ enciv = str_c(dest);
+ dest = t_str_new(32);
+ base64_encode(tmp->data, tmp->used, dest);
+ res = str_c(dest);
+ break;
+ default:
+ i_unreached();
+ }
+ if (ctx.enc_result_only)
+ *result_r = t_strdup(res);
+ else
+ *result_r = t_strdup_printf("%s$%s$", enciv, res);
+ ret = 1;
+ }
+
+ return ret;
+}
+
+static int
+var_expand_decrypt(struct var_expand_context *_ctx,
+ const char *key, const char *field,
+ const char **result_r, const char **error_r)
+{
+ if (!var_expand_crypt_initialize(error_r))
+ return -1;
+
+ const char *p = strchr(key, ';');
+ const char *const *args = NULL;
+ const char *value;
+ struct var_expand_crypt_context ctx;
+ int ret = 0;
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.ctx = _ctx;
+ ctx.format = FORMAT_HEX;
+
+ if (p != NULL) {
+ args = t_strsplit(p+1, ",");
+ }
+
+ string_t *field_value = t_str_new(64);
+ ctx.iv = t_str_new(64);
+ ctx.enckey = t_str_new(64);
+ string_t *tmp = t_str_new(128);
+
+ if ((ret = var_expand_long(_ctx, field, strlen(field),
+ &value, error_r)) < 1) {
+ return ret;
+ }
+
+ if (*value == '\0') {
+ *result_r = value;
+ return ret;
+ }
+
+ if (var_expand_crypt_settings(&ctx, args, error_r) < 0)
+ return -1;
+
+ const char *encdata = value;
+ const char *enciv = "";
+
+ /* make sure IV is correct */
+ if (ctx.iv->used == 0 && (p = strchr(encdata, '$')) != NULL) {
+ /* see if IV can be taken from data */
+ enciv = t_strcut(encdata, '$');
+ encdata = t_strcut(p+1,'$');
+ }
+
+ str_truncate(field_value, 0);
+
+ /* try to decode iv and encdata */
+ switch(ctx.format) {
+ case FORMAT_HEX:
+ if (ctx.iv->used == 0)
+ hex_to_binary(enciv, ctx.iv);
+ hex_to_binary(encdata, field_value);
+ break;
+ case FORMAT_BASE64:
+ if (ctx.iv->used == 0)
+ str_append_str(ctx.iv, t_base64_decode_str(enciv));
+ str_append_str(field_value, t_base64_decode_str(encdata));
+ break;
+ }
+
+ if (ctx.iv->used == 0) {
+ *error_r = t_strdup_printf("decrypt: IV missing");
+ return -1;
+ }
+
+ struct dcrypt_context_symmetric *dctx;
+ if (!dcrypt_ctx_sym_create(ctx.algo, DCRYPT_MODE_DECRYPT, &dctx, error_r))
+ return -1;
+ ret = var_expand_crypt(dctx, ctx.enckey, ctx.iv, field_value, tmp, error_r);
+ dcrypt_ctx_sym_destroy(&dctx);
+
+ if (ret == 0) {
+ *result_r = str_c(tmp);
+ ret = 1;
+ }
+
+ return ret;
+}
+
+static const struct var_expand_extension_func_table funcs[] = {
+ { "encrypt", var_expand_encrypt },
+ { "decrypt", var_expand_decrypt },
+ { NULL, NULL, }
+};
+
+static bool var_expand_crypt_initialize(const char **error_r)
+{
+ return dcrypt_initialize(NULL, NULL, error_r);
+}
+
+void var_expand_crypt_init(struct module *module ATTR_UNUSED)
+{
+ var_expand_register_func_array(funcs);
+ /* do not initialize dcrypt here - saves alot of memory
+ to not load openssl every time. Only load it if
+ needed */
+}
+
+void var_expand_crypt_deinit(void)
+{
+ var_expand_unregister_func_array(funcs);
+}
+
+void auth_var_expand_crypt_init(struct module *module)
+{
+ var_expand_crypt_init(module);
+}
+
+void auth_var_expand_crypt_deinit(void)
+{
+ var_expand_crypt_deinit();
+}