/* 
 * cryptint_gpg.c: gpg cryptography plugin
 * Copyright (C) 2002 Axel Burri <axel@purrly.ch>
 *
 * 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 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.
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <gpgme.h>
#include <string.h>
//#include <fcntl.h>
//#include <sys/stat.h>
#include "cryptint_gpg.h"
#include "passwd.h"
#include "debug.h"
#include "cryptint_user.h"

#define GPGME_VER_REQ "0.3.9" // required gpg version

#define DEFAULT_FN "/proc/dsr_crypt"


GpgmeCtx ctx;


/* 
 * sign a buffer with default key 
 * the signature is stored into sig_buf (buffer with length sig_buf_length),
 * and the signature size is returned in sig_length
 */
int sign_gpg(char *buf, size_t length, char *sig_buf, size_t sig_buf_length, size_t *sig_length) {
    GpgmeError err;
    GpgmeData dh_plain;
    GpgmeData dh_sig;

    DEBUGP("*sign_gpg*\n");

    /* use data from buffer, don't copy */
    if((err = gpgme_data_new_from_mem(&dh_plain, buf, length, 0))) {
	fprintf (stderr, "GpgME: data_new failed: %s\n", gpgme_strerror (err));
	exit (1);
    }

    /* assign output struct */
    if((err = gpgme_data_new(&dh_sig))) {
	fprintf (stderr, "GpgME: data_new failed: %s\n", gpgme_strerror (err));
	exit (1);
    }

    /* perform signing */
    if((err = gpgme_op_sign(ctx, dh_plain, dh_sig, GPGME_SIG_MODE_DETACH))) {
	fprintf (stderr, "GpgME: sign failed: %s\n", gpgme_strerror (err));
	exit (1);
    }
    DEBUGP("gpgme sign_op_info returns:\n%s\n", gpgme_get_op_info (ctx, 0));

    gpgme_data_release(dh_plain);

    if((err = gpgme_data_read(dh_sig, sig_buf, sig_buf_length, sig_length))) {
	fprintf (stderr, "GpgME: data_read (signature) failed: %s\n", gpgme_strerror (err));
	exit (1);
    }
//    return gpgme_data_release_and_get_mem(dh_sig, sig_length);
    return 0;
}


/* 
 * verify a buffers signature and return trust value
 */
int verify_gpg(char *plain, char *sig, size_t length_plain, size_t length_sig) {
    GpgmeError err;
    GpgmeData dh_plain;
    GpgmeData dh_sig;
    GpgmeSigStat stat;
    int validity = 0;
    int i = 0;

    DEBUGP("*verify_gpg* length_plain=%i, length_sig=%i\n", length_plain, length_sig);

    /* use data from buffer, don't copy */
    if((err = gpgme_data_new_from_mem(&dh_plain, plain, length_plain, 0))) {
	fprintf (stderr, "GpgME: data_new failed: %s\n", gpgme_strerror (err));
	exit (1);
    }
    if((err = gpgme_data_new_from_mem(&dh_sig, sig, length_sig, 0))) {
	fprintf (stderr, "GpgME: data_new failed: %s\n", gpgme_strerror (err));
	exit (1);
    }

    /* verify signature */
    if((err = gpgme_op_verify(ctx, dh_sig, dh_plain, &stat))) {
	fprintf (stderr, "GpgME: verify failed: %s\n", gpgme_strerror (err));
	exit (1);
    }

    gpgme_data_release(dh_sig);
    gpgme_data_release(dh_plain);

    DEBUGP("sig_stat: ");
    switch(stat) {
    case GPGME_SIG_STAT_GOOD: /* all signatures are valid */
	DEBUGP("GOOD\n"); 
	break;
    case GPGME_SIG_STAT_GOOD_EXP: /* all signatures are valid and expired */
	DEBUGP("GOOD_EXP\n"); 
	return 0;
    case GPGME_SIG_STAT_GOOD_EXPKEY: /* all signatures are valid and all keys used to verify the signature are expired */
	DEBUGP("GOOD_EXPKEY\n"); 
	return 0;
    case GPGME_SIG_STAT_BAD: /* all signatures are invalid */
	DEBUGP("BAD\n"); 
	return 0; 
    case GPGME_SIG_STAT_NOKEY: /* all signatures could not be checked due to missing keys */
	DEBUGP("NOKEY\n"); 
	return 0;
    case GPGME_SIG_STAT_NOSIG: /* the signature data provided was not a real signature */
	DEBUGP("NOSIG\n"); 
	return 0;
    case GPGME_SIG_STAT_ERROR: 
	DEBUGP("ERROR\n");
	return 0;
    case GPGME_SIG_STAT_DIFF: /* at least two signatures have a different status */
	DEBUGP("DIFF\n"); 
	/* TODO: get status of all keys: gpgme_get_sig_status */
	return 0;
	break;
    default: 
	DEBUGP("default (this should not happen!)\n");
	return 0;
    }


    validity = gpgme_get_sig_ulong_attr(ctx, i, GPGME_ATTR_VALIDITY, 0);	
    DEBUGP("key validity: ");
    switch(validity) {
    case GPGME_VALIDITY_UNKNOWN:
	DEBUGP("UNKNOWN\n"); 
	return 0;
    case GPGME_VALIDITY_UNDEFINED:
	DEBUGP("UNDEFINED\n"); 
	return 0;
    case GPGME_VALIDITY_NEVER:
	DEBUGP("NEVER\n"); 
	return 0;
    case GPGME_VALIDITY_MARGINAL:
	DEBUGP("MARGINAL\n"); 
	return 5;
    case GPGME_VALIDITY_FULL:
	DEBUGP("FULL\n"); 
	return 9;
    case GPGME_VALIDITY_ULTIMATE:
	DEBUGP("ULTIMATE\n"); 
	return 10;
    }

    return 0;
}



void test() {
    char *buf;
    char *in = "--- this buffer contains the raw packet ---";
    size_t sig_len;

    buf = malloc(4000);

    sign_gpg(in, strlen(in), buf, 4000, &sig_len);
    DEBUGP("sign output len: %i\n", sig_len);


    DEBUGP("signer trust: %i\n", verify_gpg(in, buf, strlen(in), sig_len));

    free(buf);
}




int main(int argc, char **argv)
{
    char ch;
    int i;
    GpgmeError err;
    GpgmeKey key;
    char *fn = DEFAULT_FN;
    
   /*
    * parse arguments
    */
    while ((ch = getopt(argc, argv, "f:")) != EOF)
    {
        switch(ch)
        {
        case 'f': /* set signing key */
	    fn = optarg;
            break;
        default:
            printf("usage: %s [-f file] [sign_key...]\n", argv[0]);
            return (-1);
        }
    }


    if(gpgme_check_version(GPGME_VER_REQ) == NULL) {
	printf("gpgme version mismatch. you should have at least version %s\n", GPGME_VER_REQ);
	return -1;
    }


    if ((err = gpgme_new(&ctx))) {
	fprintf (stderr, "GpgME: context creation failed: %s\n", gpgme_strerror (err));
	exit (1);
    }

    /* don't armor output */
    gpgme_set_armor (ctx, 0);
    gpgme_set_passphrase_cb (ctx, pwdCallBack, NULL);


    /* this is default */
    if ((err = gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP))) {
	fprintf (stderr, "gpgme: setting protocol failed: %s\n", gpgme_strerror (err));
	exit (1);
    }


    /* 
     * set sign keys 
     */
    gpgme_signers_clear (ctx);

    /* all non-option arguments are sign keys files */
    for (i = optind; i < argc; i++) {
        DEBUGP("searching sign key: %s\n", argv[i]);


	err = gpgme_op_keylist_start (ctx, argv[i], 0);
	while (!err && (err = gpgme_op_keylist_next (ctx, &key)) != GPGME_EOF) {
//	    printf("sig key: %s\n", gpgme_key_get_string_attr (key, GPGME_ATTR_NAME, 0, 0));
	    if((err = gpgme_signers_add (ctx,  key))) {
		fprintf (stderr, "gpgme: failed to add signing key: %s\n", gpgme_strerror (err));
		exit (1);
	    }
	    gpgme_key_release (key);
	    askPasswd();
	}
    }



    if(cryptint_init(fn, sign_gpg, verify_gpg)) {
	fprintf(stderr, "%s: failed to initialize cryptint\n", argv[0]);
	exit(1);
    }

//    test();
    
    /* TODO: install signal handler */
    /* TODO: deamonize */
    cryptint_listen();

    gpgme_release(ctx);
    return 0;
}
