/*
 * Sybase ASE hash support patch, based on hmailserver patch by James Nobis.
 * Sybase ASE hash format description : http://marcellmajor.com/sybase_sha256.html
 * Hacked together by Dhiru Kholia in February, 2011.
 *
 * This patch Copyright (C) 2010 by James Nobis - quel
 * quel NOSPAM quelrod NOSPAM net, and it is herby released to the general
 * public under the follow terms:
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted.
 *
 * 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.

 * Inspiration from the generic sha-1 and md5 (Copyright (c) 2010 by Solar Designer)
 */

#include <openssl/opensslv.h>
#if OPENSSL_VERSION_NUMBER >= 0x00908000

#include <string.h>

#include <openssl/sha.h>

#include "arch.h"
#include "params.h"
#include "common.h"
#include "formats.h"


#define FORMAT_LABEL        "sybasease"
#define FORMAT_NAME         "sybasease"

#define ALGORITHM_NAME      "32/" ARCH_BITS_STR

#define BENCHMARK_COMMENT   ""
#define BENCHMARK_LENGTH    0

#define PLAINTEXT_LENGTH    64
#define CIPHERTEXT_LENGTH   (6 + 16 + 64)
#define PREFIX_LENGTH       6

#define BINARY_SIZE         32
#define SALT_SIZE           8

#define MIN_KEYS_PER_CRYPT  96
#define MAX_KEYS_PER_CRYPT  96

static struct fmt_tests SybaseASE_tests[] = {
    {"0xc0074f9cc8c0d55d9803b0c0816e127f2a56ee080230af5b4ce3da1f3d9fcc5449fcfcf3fb9595eb8ea6", "test12"},
    {"0xc0074BE393C06BE420AD541671aa5e6f1a19a4a73bb51c59f45790f0887cfb70e0599747c6844d4556b3", "a"},
    {NULL}
};

static char saved_salt[SALT_SIZE];
static char saved_key[MAX_KEYS_PER_CRYPT][PLAINTEXT_LENGTH + 1];
static ARCH_WORD_32 crypt_out[MAX_KEYS_PER_CRYPT][8];

// TODO: strengthen checks
static int valid(char *ciphertext, struct fmt_main *pFmt)
{
    if(strncmp(ciphertext, "0xc007", 6)!=0)
        return 0;
    if(strlen(ciphertext) != CIPHERTEXT_LENGTH)
        return 0;

    return 1;
}

static void *get_binary(char *ciphertext)
{
    static unsigned char *out;
    int i;
    char *p = ciphertext + PREFIX_LENGTH + SALT_SIZE * 2;

    if (!out) out = mem_alloc_tiny(BINARY_SIZE, MEM_ALIGN_WORD);

    for (i = 0; i < BINARY_SIZE; i++) {
        out[i] = (atoi16[ARCH_INDEX(*p)] << 4) | atoi16[ARCH_INDEX(p[1])];
        p += 2;
    }
    return out;
}

static void *salt(char *ciphertext)
{
    static unsigned char out[SALT_SIZE];
    int i;
    char *p = ciphertext + PREFIX_LENGTH;
    for (i = 0; i < sizeof(out); i++) {
        out[i] = (atoi16[ARCH_INDEX(*p)] << 4) |atoi16[ARCH_INDEX(p[1])];
        p += 2;
    }
    return out;
}

static int binary_hash_0(void *binary)
{
    return *(ARCH_WORD_32 *)binary & 0xF;
}

static int binary_hash_1(void *binary)
{
    return *(ARCH_WORD_32 *)binary & 0xFF;
}

static int binary_hash_2(void *binary)
{
    return *(ARCH_WORD_32 *)binary & 0xFFF;
}

static int binary_hash_3(void *binary)
{
    return *(ARCH_WORD_32 *)binary & 0xFFFF;
}

static int binary_hash_4(void *binary)
{
    return *(ARCH_WORD_32 *)binary & 0xFFFFF;
}

static int get_hash_0(int index)
{
    return crypt_out[index][0] & 0xF;
}

static int get_hash_1(int index)
{
    return crypt_out[index][0] & 0xFF;
}

static int get_hash_2(int index)
{
    return crypt_out[index][0] & 0xFFF;
}

static int get_hash_3(int index)
{
    return crypt_out[index][0] & 0xFFFF;
}

static int get_hash_4(int index)
{
    return crypt_out[index][0] & 0xFFFFF;
}

static void set_salt(void *salt)
{
    memcpy(saved_salt, salt, SALT_SIZE);
}

static void set_key(char *key, int index)
{
    int saved_key_length = strlen(key);
    if(saved_key_length > PLAINTEXT_LENGTH)
        saved_key_length = PLAINTEXT_LENGTH;
    memcpy(saved_key[index], key, saved_key_length);
    saved_key[index][saved_key_length] = 0;
}

static char *get_key(int index)
{
    return saved_key[index];
}

static void crypt_all(int count)
{
    int index;

#ifdef _OPENMP
#pragma omp parallel for default(none) private(index) shared(count, crypt_out, saved_key, saved_salt)
#endif
    for(index = 0; index < count; index++) {
        unsigned char input[518] = {0};
        int i;
        unsigned char c;
        int position = 1;
        SHA256_CTX ctx;
        /* convert key to UTF-16 */
        for(i = 0; (c = saved_key[index][i]); i++) {
            input[position] = c;
            position += 2;
        }
        /* append salt at offset 510 */
        memcpy(input + 510, saved_salt, 8);
        SHA256_Init(&ctx);
        SHA256_Update(&ctx, input, 518);
        SHA256_Final((unsigned char *)crypt_out[index], &ctx);
    }
}

static int cmp_all(void *binary, int count)
{
    return 1;
}

static int cmp_one(void *binary, int index)
{
    return !strncmp((char *)binary, (const char*)crypt_out[index], BINARY_SIZE);
}

static int cmp_exact(char *source, int index)
{
    return 1;
}

struct fmt_main fmt_SybaseASE = {
    {
        FORMAT_LABEL,
        FORMAT_NAME,
        ALGORITHM_NAME,
        BENCHMARK_COMMENT,
        BENCHMARK_LENGTH,
        PLAINTEXT_LENGTH,
        BINARY_SIZE,
        SALT_SIZE,
        MIN_KEYS_PER_CRYPT,
        MAX_KEYS_PER_CRYPT,
        FMT_CASE | FMT_8_BIT | FMT_OMP,
        SybaseASE_tests
    }, {
        fmt_default_init,
	fmt_default_prepare,
        valid,
        fmt_default_split,
        get_binary,
        salt,
        {
            binary_hash_0,
            binary_hash_1,
            binary_hash_2,
            binary_hash_3,
            binary_hash_4
        },
        fmt_default_salt_hash, // XXX: slow loading
        set_salt,
        set_key,
        get_key,
        fmt_default_clear_keys,
        crypt_all,
        {
            get_hash_0,
            get_hash_1,
            get_hash_2,
            get_hash_3,
            get_hash_4
        },
        cmp_all,
        cmp_one,
        cmp_exact
    }
};

#else
#ifdef __GNUC__
#warning Note: SybaseASE format disabled - it needs OpenSSL 0.9.8 or above
#endif
#endif
