/*
 *  Copyright (C) 2004 Morten Fjord-Larsen
 *
 *  $Id: encstream.c,v 1.2 2004/06/17 14:31:32 fjord Exp $
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Library General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <string.h>
#include <gnome.h>
#include <glib.h>


#include "encstream.h"

/*
 * FIXME: Allthough, the interface suggests that files may be opened in
 * in r/w mode, we only support *either* read mode *or* write mode.
 */


encstream *encstream_open(const char *path, 
			  const char *mode,
			  const char *password, 
			  int do_encrypt)
{
    unsigned char iv[] = { 5, 23, 1, 123, 12, 3, 54, 94 };
    encstream *result;
    unsigned char key[EVP_MAX_MD_SIZE];
    EVP_MD_CTX mdctx;
    int md_len;

    FILE *file = fopen(path, mode);
    if (file == NULL)
	return NULL;

    result = malloc(sizeof(encstream));
    if (result == NULL)
	return NULL;

    /* Compute SHA-1 digest of password */
    EVP_MD_CTX_init(&mdctx);
    EVP_DigestInit_ex(&mdctx, EVP_sha1(), NULL);
    EVP_DigestUpdate(&mdctx, password, strlen(password));
    EVP_DigestFinal_ex(&mdctx, key, &md_len);
    EVP_MD_CTX_cleanup(&mdctx);

    /* Setup cipher. We use blowfish. */
    EVP_CIPHER_CTX_init(&result->ctx);
    EVP_CipherInit_ex(&result->ctx,EVP_bf_cbc(),NULL,NULL,NULL, do_encrypt);
    EVP_CIPHER_CTX_set_key_length(&result->ctx, md_len);
    EVP_CipherInit_ex(&result->ctx, NULL, NULL, key, iv, do_encrypt);

    result->buflen = 0;
    result->file = file;

    return result;
}

int encstream_close(encstream *es)
{
    // XXX: we should really only do this, if we were opened in write
    // mode.
    if (EVP_CipherFinal_ex(&es->ctx, es->buffer, &es->buflen)) {
	EVP_CIPHER_CTX_cleanup(&es->ctx);
	fwrite(es->buffer, 1, es->buflen, es->file);
    }

    fclose(es->file);
    return 0;
}


char *encstream_gets(char *s, int size, encstream *es)
{
    int inlen;
    int block_size = EVP_CIPHER_CTX_block_size(&es->ctx);
    char buf[EVP_MAX_BLOCK_LENGTH];
    char *ptr = s;
    int done = 0;

    if (es->buflen == 0 && feof(es->file))
	return NULL;

    for (;;) {
	if (es->buflen > 0) {

	    char *p = memchr(es->buffer, '\n', es->buflen);
	    if (p) {
		int n = p - es->buffer + 1;
		// FIXME: what if n > size, stupid!
		memcpy(ptr, es->buffer, n);
		ptr[n] = '\0';
		memmove(es->buffer, es->buffer+n, es->buflen - n);
		es->buflen -= n;
		return s;
	    }

	    if (es->buflen >= size - 1) {
		memcpy(ptr, es->buffer, size-1);
		ptr[size-1] = '\0';
		es->buflen -= size-1;
		return s;
	    }

	    memcpy(ptr, es->buffer, es->buflen);
	    ptr += es->buflen;
	    ptr[es->buflen] = '\0';
	    size -= es->buflen;
	    es->buflen = 0;

	}
	if (done)
	    return s;

	inlen = fread(buf, 1, block_size, es->file);
	if (inlen == 0)
	{
	    if(!EVP_CipherFinal_ex(&es->ctx, es->buffer, &es->buflen)) {
		return NULL;
	    }
	    done = 1;
	    continue;
	}

	if (!EVP_CipherUpdate(&es->ctx, es->buffer, &es->buflen, buf, inlen)) {
	    printf("EVP_CipherUpdate() failed\n");
	    return NULL;
	}
    }
    /* not reached */
}


int encstream_read(char *buffer, size_t size, encstream *es)
{
    int inlen;
    int block_size = EVP_CIPHER_CTX_block_size(&es->ctx);
    char *ptr = buffer;
    char buf[EVP_MAX_BLOCK_LENGTH];
    int done = 0;
    int n = 0;

    if (es->buflen == 0 && feof(es->file))
	return 0;

    for (;;) {
	if (es->buflen > 0) {

	    if (es->buflen >= size) {
		memcpy(ptr, es->buffer, size);
		memmove(es->buffer, es->buffer+size, es->buflen-size);
		es->buflen -= size;
		return n + size;
	    }

	    memcpy(ptr, es->buffer, es->buflen);
	    n += es->buflen;
	    ptr += es->buflen;
	    size -= es->buflen;
	    es->buflen = 0;
	}

	if (done) {
	    return n;
	}

	inlen = fread(buf, 1, block_size, es->file);
	if (inlen == 0)
	{
	    if(!EVP_CipherFinal_ex(&es->ctx, es->buffer, &es->buflen)) {
		return 0;
	    }
	    done = 1;
	    continue;
	}

	if (!EVP_CipherUpdate(&es->ctx, es->buffer, &es->buflen, buf, inlen)) {
	    printf("EVP_CipherUpdate() failed\n");
	    return 0;
	}
    }
    /* not reached */
}


int encstream_printf(encstream *es, const char *format, ...)
{
    va_list ap;
    gchar *str;
    int rc;
    va_start(ap, format);
    str = g_strdup_vprintf(format, ap);
    va_end(ap);
    rc = encstream_puts(str, es);
    g_free(str);
    return rc;
}


int encstream_write(const char *buf, size_t size, encstream *es)
{
    int i, inlen;
    int rc;
    int block_size = EVP_CIPHER_CTX_block_size(&es->ctx);

    if (size == 0)
	return 0;

    for (i = 0; i < size; i += block_size) {
	if (i + block_size > size)
	    inlen = size - i;
	else
	    inlen = block_size;

	if (!EVP_CipherUpdate(&es->ctx,es->buffer,&es->buflen,buf+i,inlen)) {
	    return -1;
	}

	rc = fwrite(es->buffer, es->buflen, 1, es->file);
	if (rc == 0 && ferror(es->file))
	    return -1;
    }

    return 0;
}


int encstream_puts(const char *s, encstream *es)
{
    return encstream_write(s, strlen(s), es);
}
