/*
 * pgp-pgsql.c
 *		PostgreSQL wrappers for pgp.
 *
 * Copyright (c) 2005 Marko Kreen
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *	  notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *	  notice, this list of conditions and the following disclaimer in the
 *	  documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.	IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * contrib/pgcrypto/pgp-pgsql.c
 */

#include "postgres.h"
#include "knl/knl_variable.h"

#include "mb/pg_wchar.h"
#include "utils/builtins.h"

#include "mbuf.h"
#include "px.h"
#include "pgp.h"

/*
 * public functions
 */
extern "C" Datum pgp_sym_encrypt_text(PG_FUNCTION_ARGS);
extern "C" Datum pgp_sym_encrypt_bytea(PG_FUNCTION_ARGS);
extern "C" Datum pgp_sym_decrypt_text(PG_FUNCTION_ARGS);
extern "C" Datum pgp_sym_decrypt_bytea(PG_FUNCTION_ARGS);

extern "C" Datum pgp_pub_encrypt_text(PG_FUNCTION_ARGS);
extern "C" Datum pgp_pub_encrypt_bytea(PG_FUNCTION_ARGS);
extern "C" Datum pgp_pub_decrypt_text(PG_FUNCTION_ARGS);
extern "C" Datum pgp_pub_decrypt_bytea(PG_FUNCTION_ARGS);

extern "C" Datum pgp_key_id_w(PG_FUNCTION_ARGS);

extern "C" Datum pg_armor(PG_FUNCTION_ARGS);
extern "C" Datum pg_dearmor(PG_FUNCTION_ARGS);

/* function headers */

PG_FUNCTION_INFO_V1(pgp_sym_encrypt_bytea);
PG_FUNCTION_INFO_V1(pgp_sym_encrypt_text);
PG_FUNCTION_INFO_V1(pgp_sym_decrypt_bytea);
PG_FUNCTION_INFO_V1(pgp_sym_decrypt_text);

PG_FUNCTION_INFO_V1(pgp_pub_encrypt_bytea);
PG_FUNCTION_INFO_V1(pgp_pub_encrypt_text);
PG_FUNCTION_INFO_V1(pgp_pub_decrypt_bytea);
PG_FUNCTION_INFO_V1(pgp_pub_decrypt_text);

PG_FUNCTION_INFO_V1(pgp_key_id_w);

PG_FUNCTION_INFO_V1(pg_armor);
PG_FUNCTION_INFO_V1(pg_dearmor);

/*
 * Mix a block of data into RNG.
 */
static void add_block_entropy(PX_MD* md, text* data)
{
    uint8 sha1[20];

    px_md_reset(md);
    px_md_update(md, (uint8*)VARDATA(data), VARSIZE(data) - VARHDRSZ);
    px_md_finish(md, sha1);

    px_add_entropy(sha1, 20);

    memset(sha1, 0, 20);
}

/*
 * Mix user data into RNG.	It is for user own interests to have
 * RNG state shuffled.
 */
static void add_entropy(text* data1, text* data2, text* data3)
{
    PX_MD* md = NULL;
    uint8 rnd[3];

    if (!data1 && !data2 && !data3)
        return;

    if (px_get_random_bytes(rnd, 3) < 0)
        return;

    if (px_find_digest("sha1", &md) < 0)
        return;

    /*
     * Try to make the feeding unpredictable.
     *
     * Prefer data over keys, as it's rather likely that key is same in
     * several calls.
     */

    /* chance: 7/8 */
    if (data1 && rnd[0] >= 32)
        add_block_entropy(md, data1);

    /* chance: 5/8 */
    if (data2 && rnd[1] >= 160)
        add_block_entropy(md, data2);

    /* chance: 5/8 */
    if (data3 && rnd[2] >= 160)
        add_block_entropy(md, data3);

    px_md_free(md);
    memset(rnd, 0, sizeof(rnd));
}

/*
 * returns src in case of no conversion or error
 */
static text* convert_charset(text* src, int cset_from, int cset_to)
{
    int src_len = VARSIZE(src) - VARHDRSZ;
    unsigned char* dst = NULL;
    unsigned char* csrc = (unsigned char*)VARDATA(src);
    text* res = NULL;

    dst = pg_do_encoding_conversion(csrc, src_len, cset_from, cset_to);
    if (dst == csrc)
        return src;

    res = cstring_to_text((char*)dst);
    pfree(dst);
    return res;
}

static text* convert_from_utf8(text* src)
{
    return convert_charset(src, PG_UTF8, GetDatabaseEncoding());
}

static text* convert_to_utf8(text* src)
{
    return convert_charset(src, GetDatabaseEncoding(), PG_UTF8);
}

static void clear_and_pfree(text* p)
{
    memset(p, 0, VARSIZE(p));
    pfree(p);
}

/*
 * expect-* arguments storage
 */
struct debug_expect {
    int debug;
    int expect;
    int cipher_algo;
    int s2k_mode;
    int s2k_cipher_algo;
    int s2k_digest_algo;
    int compress_algo;
    int use_sess_key;
    int disable_mdc;
    int unicode_mode;
};

static void fill_expect(struct debug_expect* ex, int text_mode)
{
    ex->debug = 0;
    ex->expect = 0;
    ex->cipher_algo = -1;
    ex->s2k_mode = -1;
    ex->s2k_cipher_algo = -1;
    ex->s2k_digest_algo = -1;
    ex->compress_algo = -1;
    ex->use_sess_key = -1;
    ex->disable_mdc = -1;
    ex->unicode_mode = -1;
}

#define EX_MSG(arg) \
    ereport(NOTICE, (errmsg("pgp_decrypt: unexpected %s: expected %d got %d", CppAsString(arg), ex->arg, ctx->arg)))

#define EX_CHECK(arg)                            \
    do {                                         \
        if (ex->arg >= 0 && ex->arg != ctx->arg) \
            EX_MSG(arg);                         \
    } while (0)

static void check_expect(PGP_Context* ctx, struct debug_expect* ex)
{
    EX_CHECK(cipher_algo);
    EX_CHECK(s2k_mode);
    EX_CHECK(s2k_digest_algo);
    EX_CHECK(use_sess_key);
    if (ctx->use_sess_key)
        EX_CHECK(s2k_cipher_algo);
    EX_CHECK(disable_mdc);
    EX_CHECK(compress_algo);
    EX_CHECK(unicode_mode);
}

static void show_debug(const char* msg)
{
    ereport(NOTICE, (errmsg("dbg: %s", msg)));
}

static int SetDebugExpect(struct debug_expect* ex, char* key, char* val)
{
    if (strcmp(key, "expect-cipher-algo") == 0) {
        ex->expect = 1;
        ex->cipher_algo = pgp_get_cipher_code(val);
    } else if (strcmp(key, "expect-disable-mdc") == 0) {
        ex->expect = 1;
        ex->disable_mdc = atoi(val);
    } else if (strcmp(key, "expect-sess-key") == 0) {
        ex->expect = 1;
        ex->use_sess_key = atoi(val);
    } else if (strcmp(key, "expect-s2k-mode") == 0) {
        ex->expect = 1;
        ex->s2k_mode = atoi(val);
    } else if (strcmp(key, "expect-s2k-digest-algo") == 0) {
        ex->expect = 1;
        ex->s2k_digest_algo = pgp_get_digest_code(val);
    } else if (strcmp(key, "expect-s2k-cipher-algo") == 0) {
        ex->expect = 1;
        ex->s2k_cipher_algo = pgp_get_cipher_code(val);
    } else if (strcmp(key, "expect-compress-algo") == 0) {
        ex->expect = 1;
        ex->compress_algo = atoi(val);
    } else if (strcmp(key, "expect-unicode-mode") == 0) {
        ex->expect = 1;
        ex->unicode_mode = atoi(val);
    } else {
        return PXE_ARGUMENT_ERROR;
    }

    return 0;
}

static int SetDebugArg(struct debug_expect* ex, char* key, char* val)
{
    if (strcmp(key, "debug") == 0) {
        ex->debug = atoi(val);
        return 0;
    }
    return SetDebugExpect(ex, key, val);
}

static int set_arg(PGP_Context* ctx, char* key, char* val, struct debug_expect* ex)
{
    int res = 0;

    if (strcmp(key, "cipher-algo") == 0) {
        res = pgp_set_cipher_algo(ctx, val);
    } else if (strcmp(key, "disable-mdc") == 0) {
        res = pgp_disable_mdc(ctx, atoi(val));
    } else if (strcmp(key, "sess-key") == 0) {
        res = pgp_set_sess_key(ctx, atoi(val));
    } else if (strcmp(key, "s2k-mode") == 0) {
        res = pgp_set_s2k_mode(ctx, atoi(val));
    } else if (strcmp(key, "s2k-digest-algo") == 0) {
        res = pgp_set_s2k_digest_algo(ctx, val);
    } else if (strcmp(key, "s2k-cipher-algo") == 0) {
        res = pgp_set_s2k_cipher_algo(ctx, val);
    } else if (strcmp(key, "compress-algo") == 0) {
        res = pgp_set_compress_algo(ctx, atoi(val));
    } else if (strcmp(key, "compress-level") == 0) {
        res = pgp_set_compress_level(ctx, atoi(val));
    } else if (strcmp(key, "convert-crlf") == 0) {
        res = pgp_set_convert_crlf(ctx, atoi(val));
    } else if (strcmp(key, "unicode-mode") == 0) {
        res = pgp_set_unicode_mode(ctx, atoi(val));
    } else if (strcmp(key, "max-decompressed-size") == 0) {
        res = PgpSetMaxDecompressedSize(ctx, atoi(val));
    } else if (ex != NULL) {
        res = SetDebugArg(ex, key, val);
    } else {
        res = PXE_ARGUMENT_ERROR;
    }

    return res;
}

/*
 * Find next word.	Handle ',' and '=' as words.  Skip whitespace.
 * Put word info into res_p, res_len.
 * Returns ptr to next word.
 */
static char* getword(char* p, char** res_p, int* res_len)
{
    /* whitespace at start */
    while (*p && (*p == ' ' || *p == '\t' || *p == '\n'))
        p++;

    /* word data */
    *res_p = p;
    if (*p == '=' || *p == ',')
        p++;
    else
        while (*p && !(*p == ' ' || *p == '\t' || *p == '\n' || *p == '=' || *p == ','))
            p++;

    /* word end */
    *res_len = p - *res_p;

    /* whitespace at end */
    while (*p && (*p == ' ' || *p == '\t' || *p == '\n'))
        p++;

    return p;
}

/*
 * Convert to lowercase asciiz string.
 */
static char* downcase_convert(const uint8* s, int len)
{
    int c, i;
    char* res = (char*)palloc(len + 1);

    for (i = 0; i < len; i++) {
        c = s[i];
        if (c >= 'A' && c <= 'Z')
            c += 'a' - 'A';
        res[i] = c;
    }
    res[len] = 0;
    return res;
}

static int parse_args(PGP_Context* ctx, uint8* args, int arg_len, struct debug_expect* ex)
{
    char* str = downcase_convert(args, arg_len);
    char *key, *val;
    int key_len, val_len;
    int res = 0;
    char* p = str;

    while (*p) {
        res = PXE_ARGUMENT_ERROR;
        p = getword(p, &key, &key_len);
        if (*p++ != '=')
            break;
        p = getword(p, &val, &val_len);
        if (*p == '\0')
            ;
        else if (*p++ != ',')
            break;

        if (*key == 0 || *val == 0 || val_len == 0)
            break;

        key[key_len] = 0;
        val[val_len] = 0;

        res = set_arg(ctx, key, val, ex);
        if (res < 0)
            break;
    }
    pfree(str);
    return res;
}

static MBuf* create_mbuf_from_vardata(text* data)
{
    return mbuf_create_from_data((uint8*)VARDATA(data), VARSIZE(data) - VARHDRSZ);
}

static void init_work(PGP_Context** ctx_p, int is_text, text* args, struct debug_expect* ex)
{
    int err = pgp_init(ctx_p);

    fill_expect(ex, is_text);

    if (err == 0 && args != NULL)
        err = parse_args(*ctx_p, (uint8*)VARDATA(args), VARSIZE(args) - VARHDRSZ, ex);

    if (err) {
        ereport(ERROR, (errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION), errmsg("%s", px_strerror(err))));
    }

    if (ex->debug)
        px_set_debug_handler(show_debug);

    pgp_set_text_mode(*ctx_p, is_text);
}

static bytea* encrypt_internal(int is_pubenc, int is_text, text* data, text* key, text* args)
{
    MBuf *src, *dst;
    uint8 tmp[VARHDRSZ];
    uint8* restmp = NULL;
    bytea* res = NULL;
    int res_len;
    PGP_Context* ctx = NULL;
    int err;
    struct debug_expect ex;
    text* tmp_data = NULL;

    /*
     * Add data and key info RNG.
     */
    add_entropy(data, key, NULL);

    init_work(&ctx, is_text, args, &ex);

    if (is_text && pgp_get_unicode_mode(ctx)) {
        tmp_data = convert_to_utf8(data);
        if (tmp_data == data)
            tmp_data = NULL;
        else
            data = tmp_data;
    }

    src = create_mbuf_from_vardata(data);
    dst = mbuf_create(VARSIZE(data) + 128);

    /*
     * reserve room for header
     */
    mbuf_append(dst, tmp, VARHDRSZ);

    /*
     * set key
     */
    if (is_pubenc) {
        MBuf* kbuf = create_mbuf_from_vardata(key);

        err = pgp_set_pubkey(ctx, kbuf, NULL, 0, 0);
        mbuf_free(kbuf);
    } else
        err = pgp_set_symkey(ctx, (uint8*)VARDATA(key), VARSIZE(key) - VARHDRSZ);

    /*
     * encrypt
     */
    if (err >= 0)
        err = pgp_encrypt(ctx, src, dst);

    /*
     * check for error
     */
    if (err) {
        if (ex.debug)
            px_set_debug_handler(NULL);
        if (tmp_data)
            clear_and_pfree(tmp_data);
        pgp_free(ctx);
        mbuf_free(src);
        mbuf_free(dst);
        ereport(ERROR, (errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION), errmsg("%s", px_strerror(err))));
    }

    /* res_len includes VARHDRSZ */
    res_len = mbuf_steal_data(dst, &restmp);
    res = (bytea*)restmp;
    SET_VARSIZE(res, res_len);

    if (tmp_data)
        clear_and_pfree(tmp_data);
    pgp_free(ctx);
    mbuf_free(src);
    mbuf_free(dst);

    px_set_debug_handler(NULL);

    return res;
}

static bytea* decrypt_internal(int is_pubenc, int need_text, text* data, text* key, text* keypsw, text* args)
{
    int err;
    MBuf *src = NULL, *dst = NULL;
    uint8 tmp[VARHDRSZ];
    uint8* restmp = NULL;
    bytea* res = NULL;
    int res_len;
    PGP_Context* ctx = NULL;
    struct debug_expect ex;
    int got_unicode = 0;
    uint8* psw = NULL;
    int pswLen = 0;

    init_work(&ctx, need_text, args, &ex);

    src = mbuf_create_from_data((uint8*)VARDATA(data), VARSIZE(data) - VARHDRSZ);
    dst = mbuf_create(VARSIZE(data) + 2048);

    /*
     * reserve room for header
     */
    mbuf_append(dst, tmp, VARHDRSZ);

    /*
     * limit total decrypted output to avoid unbounded MBuf growth
     */
    if (ctx->maxDecompressedSize > 0) {
        int limit = ctx->maxDecompressedSize;

        if (limit > PG_INT32_MAX - (int)VARHDRSZ)
            limit = PG_INT32_MAX - (int)VARHDRSZ;
        MbufSetMaxSize(dst, limit + VARHDRSZ);
    }

    /*
     * set key
     */
    if (is_pubenc) {
        MBuf* kbuf = NULL;

        if (keypsw) {
            psw = (uint8*)VARDATA(keypsw);
            pswLen = VARSIZE(keypsw) - VARHDRSZ;
        }
        kbuf = create_mbuf_from_vardata(key);
        err = pgp_set_pubkey(ctx, kbuf, psw, pswLen, 1);
        mbuf_free(kbuf);
    } else
        err = pgp_set_symkey(ctx, (uint8*)VARDATA(key), VARSIZE(key) - VARHDRSZ);

    /*
     * decrypt
     */
    if (err >= 0)
        err = pgp_decrypt(ctx, src, dst);

    /*
     * failed?
     */
    if (err < 0)
        goto out;

    if (ex.expect)
        check_expect(ctx, &ex);

    /* remember the setting */
    got_unicode = pgp_get_unicode_mode(ctx);

out:
    if (psw != NULL && pswLen > 0) {
        (void)memset_s(psw, pswLen, 0, pswLen);
    }
    (void)memset_s(&pswLen, sizeof(pswLen), 0, sizeof(pswLen));

    if (src)
        mbuf_free(src);
    if (ctx)
        pgp_free(ctx);

    if (err) {
        px_set_debug_handler(NULL);
        if (dst)
            mbuf_free(dst);
        ereport(ERROR, (errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION), errmsg("%s", px_strerror(err))));
    }

    res_len = mbuf_steal_data(dst, &restmp);
    mbuf_free(dst);

    /* res_len includes VARHDRSZ */
    res = (bytea*)restmp;
    SET_VARSIZE(res, res_len);

    if (need_text && got_unicode) {
        text* utf = convert_from_utf8(res);

        if (utf != res) {
            clear_and_pfree(res);
            res = utf;
        }
    }
    px_set_debug_handler(NULL);

    /*
     * add successful decryptions also into RNG
     */
    add_entropy(res, key, keypsw);

    return res;
}

/*
 * Wrappers for symmetric-key functions
 */
Datum pgp_sym_encrypt_bytea(PG_FUNCTION_ARGS)
{
    bytea *data, *key;
    text* arg = NULL;
    text* res = NULL;

    data = PG_GETARG_BYTEA_P(0);
    key = PG_GETARG_BYTEA_P(1);
    if (PG_NARGS() > 2)
        arg = PG_GETARG_BYTEA_P(2);

    res = encrypt_internal(0, 0, data, key, arg);

    PG_FREE_IF_COPY(data, 0);
    PG_FREE_IF_COPY(key, 1);
    if (PG_NARGS() > 2)
        PG_FREE_IF_COPY(arg, 2);
    PG_RETURN_TEXT_P(res);
}

Datum pgp_sym_encrypt_text(PG_FUNCTION_ARGS)
{
    bytea *data, *key;
    text* arg = NULL;
    text* res = NULL;

    data = PG_GETARG_BYTEA_P(0);
    key = PG_GETARG_BYTEA_P(1);
    if (PG_NARGS() > 2)
        arg = PG_GETARG_BYTEA_P(2);

    res = encrypt_internal(0, 1, data, key, arg);

    PG_FREE_IF_COPY(data, 0);
    PG_FREE_IF_COPY(key, 1);
    if (PG_NARGS() > 2)
        PG_FREE_IF_COPY(arg, 2);
    PG_RETURN_TEXT_P(res);
}

Datum pgp_sym_decrypt_bytea(PG_FUNCTION_ARGS)
{
    bytea *data, *key;
    text* arg = NULL;
    text* res = NULL;

    data = PG_GETARG_BYTEA_P(0);
    key = PG_GETARG_BYTEA_P(1);
    if (PG_NARGS() > 2)
        arg = PG_GETARG_BYTEA_P(2);

    res = decrypt_internal(0, 0, data, key, NULL, arg);

    PG_FREE_IF_COPY(data, 0);
    PG_FREE_IF_COPY(key, 1);
    if (PG_NARGS() > 2)
        PG_FREE_IF_COPY(arg, 2);
    PG_RETURN_TEXT_P(res);
}

Datum pgp_sym_decrypt_text(PG_FUNCTION_ARGS)
{
    bytea *data, *key;
    text* arg = NULL;
    text* res = NULL;

    data = PG_GETARG_BYTEA_P(0);
    key = PG_GETARG_BYTEA_P(1);
    if (PG_NARGS() > 2)
        arg = PG_GETARG_BYTEA_P(2);

    res = decrypt_internal(0, 1, data, key, NULL, arg);

    PG_FREE_IF_COPY(data, 0);
    PG_FREE_IF_COPY(key, 1);
    if (PG_NARGS() > 2)
        PG_FREE_IF_COPY(arg, 2);
    PG_RETURN_TEXT_P(res);
}

/*
 * Wrappers for public-key functions
 */

Datum pgp_pub_encrypt_bytea(PG_FUNCTION_ARGS)
{
    bytea *data, *key;
    text* arg = NULL;
    text* res = NULL;

    data = PG_GETARG_BYTEA_P(0);
    key = PG_GETARG_BYTEA_P(1);
    if (PG_NARGS() > 2)
        arg = PG_GETARG_BYTEA_P(2);

    res = encrypt_internal(1, 0, data, key, arg);

    PG_FREE_IF_COPY(data, 0);
    PG_FREE_IF_COPY(key, 1);
    if (PG_NARGS() > 2)
        PG_FREE_IF_COPY(arg, 2);
    PG_RETURN_TEXT_P(res);
}

Datum pgp_pub_encrypt_text(PG_FUNCTION_ARGS)
{
    bytea *data, *key;
    text* arg = NULL;
    text* res = NULL;

    data = PG_GETARG_BYTEA_P(0);
    key = PG_GETARG_BYTEA_P(1);
    if (PG_NARGS() > 2)
        arg = PG_GETARG_BYTEA_P(2);

    res = encrypt_internal(1, 1, data, key, arg);

    PG_FREE_IF_COPY(data, 0);
    PG_FREE_IF_COPY(key, 1);
    if (PG_NARGS() > 2)
        PG_FREE_IF_COPY(arg, 2);
    PG_RETURN_TEXT_P(res);
}

Datum pgp_pub_decrypt_bytea(PG_FUNCTION_ARGS)
{
    bytea *data, *key;
    text *psw = NULL, *arg = NULL;
    text* res = NULL;
    const int dataArg = 0;
    const int keyArg = 1;
    const int pswArg = 2;
    const int argArg = 3;

    data = PG_GETARG_BYTEA_P(dataArg);
    key = PG_GETARG_BYTEA_P(keyArg);
    if (PG_NARGS() > pswArg)
        psw = PG_GETARG_BYTEA_P(pswArg);
    if (PG_NARGS() > argArg)
        arg = PG_GETARG_BYTEA_P(argArg);

    res = decrypt_internal(1, 0, data, key, psw, arg);

    PG_FREE_IF_COPY(data, dataArg);
    PG_FREE_IF_COPY(key, keyArg);
    if (PG_NARGS() > pswArg) {
        if (psw != NULL) {
            int pswLen = VARSIZE(psw);
            (void)memset_s(psw, pswLen, 0, pswLen);
        }
        PG_FREE_IF_COPY(psw, pswArg);
    }
    if (PG_NARGS() > argArg)
        PG_FREE_IF_COPY(arg, argArg);
    PG_RETURN_TEXT_P(res);
}

Datum pgp_pub_decrypt_text(PG_FUNCTION_ARGS)
{
    bytea *data, *key;
    text *psw = NULL, *arg = NULL;
    text* res = NULL;
    const int dataArg = 0;
    const int keyArg = 1;
    const int pswArg = 2;
    const int argArg = 3;

    data = PG_GETARG_BYTEA_P(dataArg);
    key = PG_GETARG_BYTEA_P(keyArg);
    if (PG_NARGS() > pswArg)
        psw = PG_GETARG_BYTEA_P(pswArg);
    if (PG_NARGS() > argArg)
        arg = PG_GETARG_BYTEA_P(argArg);

    res = decrypt_internal(1, 1, data, key, psw, arg);

    PG_FREE_IF_COPY(data, dataArg);
    PG_FREE_IF_COPY(key, keyArg);
    if (PG_NARGS() > pswArg) {
        if (psw != NULL) {
            int pswLen = VARSIZE(psw);
            (void)memset_s(psw, pswLen, 0, pswLen);
        }
        PG_FREE_IF_COPY(psw, pswArg);
    }
    if (PG_NARGS() > argArg)
        PG_FREE_IF_COPY(arg, argArg);
    PG_RETURN_TEXT_P(res);
}

/*
 * Wrappers for PGP ascii armor
 */

Datum pg_armor(PG_FUNCTION_ARGS)
{
    bytea* data = NULL;
    text* res = NULL;
    int data_len, res_len, guess_len;

    data = PG_GETARG_BYTEA_P(0);
    data_len = VARSIZE(data) - VARHDRSZ;

    guess_len = pgp_armor_enc_len(data_len);
    res = (text*)palloc(VARHDRSZ + guess_len);

    res_len = pgp_armor_encode((uint8*)VARDATA(data), data_len, (uint8*)VARDATA(res));
    if (res_len > guess_len)
        ereport(ERROR,
            (errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION), errmsg("Overflow - encode estimate too small")));
    SET_VARSIZE(res, VARHDRSZ + res_len);

    PG_FREE_IF_COPY(data, 0);
    PG_RETURN_TEXT_P(res);
}

Datum pg_dearmor(PG_FUNCTION_ARGS)
{
    text* data = NULL;
    bytea* res = NULL;
    int data_len, res_len, guess_len;

    data = PG_GETARG_TEXT_P(0);
    data_len = VARSIZE(data) - VARHDRSZ;

    guess_len = pgp_armor_dec_len(data_len);
    res = (bytea*)palloc(VARHDRSZ + guess_len);

    res_len = pgp_armor_decode((uint8*)VARDATA(data), data_len, (uint8*)VARDATA(res));
    if (res_len < 0)
        ereport(ERROR, (errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION), errmsg("%s", px_strerror(res_len))));
    if (res_len > guess_len)
        ereport(ERROR,
            (errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION), errmsg("Overflow - decode estimate too small")));
    SET_VARSIZE(res, VARHDRSZ + res_len);

    PG_FREE_IF_COPY(data, 0);
    PG_RETURN_TEXT_P(res);
}

/*
 * Wrappers for PGP key id
 */

Datum pgp_key_id_w(PG_FUNCTION_ARGS)
{
    bytea* data = NULL;
    text* res = NULL;
    int res_len;
    MBuf* buf = NULL;

    data = PG_GETARG_BYTEA_P(0);
    buf = create_mbuf_from_vardata(data);
    res = (text*)palloc(VARHDRSZ + 17);

    res_len = pgp_get_keyid(buf, VARDATA(res));
    mbuf_free(buf);
    if (res_len < 0)
        ereport(ERROR, (errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION), errmsg("%s", px_strerror(res_len))));
    SET_VARSIZE(res, VARHDRSZ + res_len);

    PG_FREE_IF_COPY(data, 0);
    PG_RETURN_TEXT_P(res);
}