/*
  +--------------------------------------------------------------------+
  | PECL :: gnupg                                                      |
  +--------------------------------------------------------------------+
  | Redistribution and use in source and binary forms, with or without |
  | modification, are permitted provided that the conditions mentioned |
  | in the accompanying LICENSE file are met.                          |
  +--------------------------------------------------------------------+
  | Copyright (c) 2006, Thilo Raufeisen <traufeisen@php.net>           |
  | Copyright (c) 2013, Jim Jagielski <jimjag@php.net>                 |
  +--------------------------------------------------------------------+
*/

/* $Id: gnupg.c 335919 2015-02-11 18:10:16Z jimjag $ */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_ini.h"
#include "zend_exceptions.h"
#include "ext/standard/info.h"
#include "php_gnupg.h"

#ifdef ZEND_ENGINE_2
#include "php_gnupg_keylistiterator.h"
#endif

static int le_gnupg;

#ifdef ZEND_ENGINE_2
static zend_object_handlers gnupg_object_handlers;
#endif

/* {{{ defs */
#ifdef ZEND_ENGINE_2

#define GNUPG_GETOBJ() \
    zval *this = getThis(); \
    gnupg_object *intern; \
    zval *res; \
    if(this){ \
        intern  =   (gnupg_object*) zend_object_store_get_object(getThis() TSRMLS_CC); \
        if(!intern){ \
            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid or unitialized gnupg object"); \
            RETURN_FALSE; \
        } \
    }

#define GNUPG_ERR(error) \
    if(intern){ \
		switch (intern->errormode) { \
			case 1: \
				php_error_docref(NULL TSRMLS_CC, E_WARNING, (char*)error); \
				break; \
			case 2: \
				zend_throw_exception(zend_exception_get_default(TSRMLS_C), (char*) error, 0 TSRMLS_CC); \
				break; \
			default: \
				intern->errortxt = (char*)error; \
		} \
    }else{ \
        php_error_docref(NULL TSRMLS_CC, E_WARNING, (char*)error); \
    } \
	if(return_value){ \
	    RETVAL_FALSE; \
	}
#else
#define GNUPG_ERR(error) \
	if(intern && intern->errormode!=1) { \
		intern->errortxt = (char*)error; \
	}else{ \
		php_error_docref(NULL TSRMLS_CC, E_WARNING, (char*)error); \
	} \
	if(return_value){ \
		RETVAL_FALSE; \
	}

#define GNUPG_GETOBJ() \
	zval *this = NULL; \
	zval *res; \
	gnupg_object *intern;

#endif
/* }}} */

/* {{{ free encryptkeys */
static void gnupg_free_encryptkeys(gnupg_object *intern TSRMLS_DC){
	if(intern){
		int idx;
		/* loop through all encryptkeys and unref them in the gpgme-lib */
		for(idx=0;idx<intern->encrypt_size;idx++){
			gpgme_key_unref (intern->encryptkeys[idx]);
		}
		/* it´s an odd-thing, but other solutions makes problems :
		*  erealloc(x,0) gives a segfault with PHP 4 and debug enabled
		*  efree(x) alone ends in a segfault
		*/
		efree(erealloc(intern->encryptkeys,0));	
        intern->encryptkeys = NULL;
        intern->encrypt_size = 0;
	}
}
/* }}} */

/* {{{ free_resource */
static void gnupg_free_resource_ptr(gnupg_object *intern TSRMLS_DC){
    if(intern){
        if(intern->ctx){
			/* clear all signers from the gpgme-lib and finally release it */
            gpgme_signers_clear (intern->ctx);
            gpgme_release       (intern->ctx);
            intern->ctx = NULL;
        }
		/* basic cleanup */
        gnupg_free_encryptkeys(intern TSRMLS_CC);
		zend_hash_destroy(intern->signkeys);
		FREE_HASHTABLE(intern->signkeys);
		zend_hash_destroy(intern->decryptkeys);
        FREE_HASHTABLE(intern->decryptkeys);
    }
}
/* }}} */

/* {{{ gnupg_res_dtor */
static void gnupg_res_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC) {
    gnupg_object *intern;
    intern = (gnupg_object *) rsrc->ptr;
    gnupg_free_resource_ptr(intern TSRMLS_CC);
	efree(intern);
}
/* }}} */

/* {{{ gnupg_res_init */
static void gnupg_res_init(gnupg_object *intern TSRMLS_DC){
	/* init the gpgme-lib and set the default values */
	gpgme_ctx_t	ctx;
	gpgme_error_t err;
	gpgme_check_version			(NULL);
	err =						gpgme_new(&ctx);
	if(err == GPG_ERR_NO_ERROR) {
#ifdef GNUPG_PATH
		gpgme_ctx_set_engine_info(ctx, GPGME_PROTOCOL_OpenPGP, GNUPG_PATH, NULL);
#endif
		gpgme_set_armor				(ctx,1);
	}
	intern->ctx				=	ctx;
	intern->encryptkeys		=	NULL;
	intern->encrypt_size	=	0;
	intern->signmode		=	GPGME_SIG_MODE_CLEAR;
	intern->errortxt		=	NULL;
	intern->errormode		=	3;
	ALLOC_HASHTABLE				(intern->signkeys);
    zend_hash_init				(intern->signkeys, 0, NULL, NULL, 0);
    ALLOC_HASHTABLE				(intern->decryptkeys);
    zend_hash_init				(intern->decryptkeys, 0, NULL, NULL, 0);
	return;
}
/* }}} */

#ifdef ZEND_ENGINE_2
/* {{{ free_storage */
static void gnupg_obj_dtor(gnupg_object *intern TSRMLS_DC){
	if(!intern){
		return;
	}
	gnupg_free_resource_ptr(intern TSRMLS_CC);
	if(intern->zo.properties){
		zend_hash_destroy(intern->zo.properties);
		FREE_HASHTABLE(intern->zo.properties);
	}
	efree(intern);
}
/* }}} */


/* {{{ objects_new */
zend_object_value gnupg_obj_new(zend_class_entry *class_type TSRMLS_DC){
	gnupg_object *intern;
#if PHP_VERSION_ID < 50399
	zval *tmp;
#endif
	zend_object_value retval;
	
	intern					=	ecalloc(1, sizeof(gnupg_object));
	intern->zo.ce			=	class_type;

#if PHP_VERSION_ID < 50399
	intern->zo.properties	=	NULL;
	ALLOC_HASHTABLE	(intern->zo.properties);
	zend_hash_init	(intern->zo.properties, 0, NULL, ZVAL_PTR_DTOR, 0);
	zend_hash_copy	(intern->zo.properties, &class_type->default_properties, (copy_ctor_func_t) zval_add_ref, (void *) &tmp, sizeof(zval *));
#else	
	object_properties_init(&intern->zo, class_type);
#endif
	
	retval.handle		=	zend_objects_store_put(intern,NULL,(zend_objects_free_object_storage_t) gnupg_obj_dtor,NULL TSRMLS_CC);
	retval.handlers		=	(zend_object_handlers *) & gnupg_object_handlers;
	gnupg_res_init	(intern TSRMLS_CC);
	
	return retval;
}
/* }}} */

/* {{{ methodlist gnupg */
static zend_function_entry gnupg_methods[] = {
    PHP_FALIAS(keyinfo,             gnupg_keyinfo,          NULL)
    PHP_FALIAS(verify,              gnupg_verify,           NULL)
    PHP_FALIAS(geterror,            gnupg_geterror,         NULL)
    PHP_FALIAS(clearsignkeys,       gnupg_clearsignkeys,    NULL)
    PHP_FALIAS(clearencryptkeys,    gnupg_clearencryptkeys, NULL)
    PHP_FALIAS(cleardecryptkeys,    gnupg_cleardecryptkeys, NULL)
    PHP_FALIAS(setarmor,            gnupg_setarmor,         NULL)
    PHP_FALIAS(encrypt,             gnupg_encrypt,          NULL)
    PHP_FALIAS(decrypt,             gnupg_decrypt,          NULL)
    PHP_FALIAS(export,              gnupg_export,           NULL)
    PHP_FALIAS(import,              gnupg_import,           NULL)
    PHP_FALIAS(getprotocol,         gnupg_getprotocol,      NULL)
    PHP_FALIAS(setsignmode,         gnupg_setsignmode,      NULL)
    PHP_FALIAS(sign,                gnupg_sign,             NULL)
    PHP_FALIAS(encryptsign,         gnupg_encryptsign,      NULL)
    PHP_FALIAS(decryptverify,       gnupg_decryptverify,    NULL)
    PHP_FALIAS(addsignkey,          gnupg_addsignkey,       NULL)
    PHP_FALIAS(addencryptkey,       gnupg_addencryptkey,    NULL)
    PHP_FALIAS(adddecryptkey,       gnupg_adddecryptkey,    NULL)
    PHP_FALIAS(deletekey,           gnupg_deletekey,        NULL)
    PHP_FALIAS(gettrustlist,        gnupg_gettrustlist,     NULL)
    PHP_FALIAS(listsignatures,      gnupg_listsignatures,   NULL)
    PHP_FALIAS(seterrormode,        gnupg_seterrormode,     NULL)
	{NULL, NULL, NULL}
};
/* }}} */

/* {{{ class constants */
static void gnupg_declare_long_constant(const char *const_name, long value TSRMLS_DC){
#if PHP_MAJOR_VERSION > 5 || PHP_MINOR_VERSION >= 1
    zend_declare_class_constant_long(gnupg_class_entry, (char*)const_name, strlen(const_name), value TSRMLS_CC);
#else
    zval *constant = malloc(sizeof(*constant));
    ZVAL_LONG(constant,value);
    INIT_PZVAL(constant);
    zend_hash_update(&gnupg_class_entry->constants_table, (char*)const_name, strlen(const_name)+1, &constant, sizeof(zval*), NULL);
#endif
}
/* }}} */

#endif  /* ZEND_ENGINE_2 */

/* {{{ functionlist gnupg */
static zend_function_entry gnupg_functions[] = {
	PHP_FE(gnupg_init,				NULL)
	PHP_FE(gnupg_keyinfo,			NULL)
	PHP_FE(gnupg_sign,				NULL)
	PHP_FE(gnupg_verify,			NULL)
	PHP_FE(gnupg_clearsignkeys,		NULL)
	PHP_FE(gnupg_clearencryptkeys,	NULL)
	PHP_FE(gnupg_cleardecryptkeys,	NULL)
	PHP_FE(gnupg_setarmor,			NULL)
	PHP_FE(gnupg_encrypt,			NULL)
	PHP_FE(gnupg_decrypt,			NULL)
	PHP_FE(gnupg_export,			NULL)
	PHP_FE(gnupg_import,			NULL)
	PHP_FE(gnupg_getprotocol,		NULL)
	PHP_FE(gnupg_setsignmode,		NULL)
	PHP_FE(gnupg_encryptsign,		NULL)
	PHP_FE(gnupg_decryptverify,		NULL)
	PHP_FE(gnupg_geterror,			NULL)
	PHP_FE(gnupg_addsignkey,		NULL)
	PHP_FE(gnupg_addencryptkey,		NULL)
	PHP_FE(gnupg_adddecryptkey,		NULL)
	PHP_FE(gnupg_deletekey,			NULL)
	PHP_FE(gnupg_gettrustlist,		NULL)
	PHP_FE(gnupg_listsignatures,	NULL)
	PHP_FE(gnupg_seterrormode,		NULL)
	{NULL, NULL, NULL}
};
/* }}} */

/* {{{ gnupg_module_entry
 */
zend_module_entry gnupg_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
	STANDARD_MODULE_HEADER,
#endif
	"gnupg",
	gnupg_functions,
	PHP_MINIT(gnupg),
	PHP_MSHUTDOWN(gnupg),
	NULL,		
	NULL,	
	PHP_MINFO(gnupg),
#if ZEND_MODULE_API_NO >= 20010901
	PHP_GNUPG_VERSION,
#endif
	STANDARD_MODULE_PROPERTIES
};
/* }}} */

#ifdef COMPILE_DL_GNUPG
ZEND_GET_MODULE(gnupg)
#endif

/* {{{ PHP_MINIT_FUNCTION
 */
PHP_MINIT_FUNCTION(gnupg)
{
	le_gnupg			=	zend_register_list_destructors_ex(gnupg_res_dtor, NULL, "ctx", module_number);
#ifdef ZEND_ENGINE_2
	zend_class_entry ce;
    INIT_CLASS_ENTRY(ce, "gnupg", gnupg_methods);
    ce.create_object    =   gnupg_obj_new;
    gnupg_class_entry   =   zend_register_internal_class(&ce TSRMLS_CC);
    memcpy(&gnupg_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
	if (SUCCESS != gnupg_keylistiterator_init()){
		return FAILURE;
	}
    gnupg_declare_long_constant("SIG_MODE_NORMAL",            GPGME_SIG_MODE_NORMAL TSRMLS_CC);
    gnupg_declare_long_constant("SIG_MODE_DETACH",            GPGME_SIG_MODE_DETACH TSRMLS_CC);
    gnupg_declare_long_constant("SIG_MODE_CLEAR",             GPGME_SIG_MODE_CLEAR TSRMLS_CC);
    gnupg_declare_long_constant("VALIDITY_UNKNOWN",           GPGME_VALIDITY_UNKNOWN TSRMLS_CC);
    gnupg_declare_long_constant("VALIDITY_UNDEFINED",         GPGME_VALIDITY_UNDEFINED TSRMLS_CC);
    gnupg_declare_long_constant("VALIDITY_NEVER",             GPGME_VALIDITY_NEVER TSRMLS_CC);
    gnupg_declare_long_constant("VALIDITY_MARGINAL",          GPGME_VALIDITY_MARGINAL TSRMLS_CC);
    gnupg_declare_long_constant("VALIDITY_FULL",              GPGME_VALIDITY_FULL TSRMLS_CC);
    gnupg_declare_long_constant("VALIDITY_ULTIMATE",          GPGME_VALIDITY_ULTIMATE TSRMLS_CC);
    gnupg_declare_long_constant("PROTOCOL_OpenPGP",           GPGME_PROTOCOL_OpenPGP TSRMLS_CC);
    gnupg_declare_long_constant("PROTOCOL_CMS",               GPGME_PROTOCOL_CMS TSRMLS_CC);
    gnupg_declare_long_constant("SIGSUM_VALID",               GPGME_SIGSUM_VALID TSRMLS_CC);
    gnupg_declare_long_constant("SIGSUM_GREEN",               GPGME_SIGSUM_GREEN TSRMLS_CC);
    gnupg_declare_long_constant("SIGSUM_RED",                 GPGME_SIGSUM_RED TSRMLS_CC);
    gnupg_declare_long_constant("SIGSUM_KEY_REVOKED",         GPGME_SIGSUM_KEY_REVOKED TSRMLS_CC);
    gnupg_declare_long_constant("SIGSUM_KEY_EXPIRED",         GPGME_SIGSUM_KEY_EXPIRED TSRMLS_CC);
    gnupg_declare_long_constant("SIGSUM_SIG_EXPIRED",         GPGME_SIGSUM_SIG_EXPIRED TSRMLS_CC);
    gnupg_declare_long_constant("SIGSUM_KEY_MISSING",         GPGME_SIGSUM_KEY_MISSING TSRMLS_CC);
    gnupg_declare_long_constant("SIGSUM_CRL_MISSING",         GPGME_SIGSUM_CRL_MISSING TSRMLS_CC);
    gnupg_declare_long_constant("SIGSUM_CRL_TOO_OLD",         GPGME_SIGSUM_CRL_TOO_OLD TSRMLS_CC);
    gnupg_declare_long_constant("SIGSUM_BAD_POLICY",          GPGME_SIGSUM_BAD_POLICY TSRMLS_CC);
    gnupg_declare_long_constant("SIGSUM_SYS_ERROR",           GPGME_SIGSUM_SYS_ERROR TSRMLS_CC);
	gnupg_declare_long_constant("ERROR_WARNING",              1 TSRMLS_CC);
	gnupg_declare_long_constant("ERROR_EXCEPTION",            2 TSRMLS_CC);
	gnupg_declare_long_constant("ERROR_SILENT",            	  3 TSRMLS_CC);
#endif
    REGISTER_LONG_CONSTANT("GNUPG_SIG_MODE_NORMAL",            GPGME_SIG_MODE_NORMAL, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("GNUPG_SIG_MODE_DETACH",            GPGME_SIG_MODE_DETACH, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("GNUPG_SIG_MODE_CLEAR",             GPGME_SIG_MODE_CLEAR, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("GNUPG_VALIDITY_UNKNOWN",           GPGME_VALIDITY_UNKNOWN, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("GNUPG_VALIDITY_UNDEFINED",         GPGME_VALIDITY_UNDEFINED, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("GNUPG_VALIDITY_NEVER",             GPGME_VALIDITY_NEVER, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("GNUPG_VALIDITY_MARGINAL",          GPGME_VALIDITY_MARGINAL, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("GNUPG_VALIDITY_FULL",              GPGME_VALIDITY_FULL, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("GNUPG_VALIDITY_ULTIMATE",          GPGME_VALIDITY_ULTIMATE, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("GNUPG_PROTOCOL_OpenPGP",           GPGME_PROTOCOL_OpenPGP, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("GNUPG_PROTOCOL_CMS",               GPGME_PROTOCOL_CMS, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("GNUPG_SIGSUM_VALID",               GPGME_SIGSUM_VALID, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("GNUPG_SIGSUM_GREEN",               GPGME_SIGSUM_GREEN, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("GNUPG_SIGSUM_RED",                 GPGME_SIGSUM_RED, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("GNUPG_SIGSUM_KEY_REVOKED",         GPGME_SIGSUM_KEY_REVOKED, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("GNUPG_SIGSUM_KEY_EXPIRED",         GPGME_SIGSUM_KEY_EXPIRED, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("GNUPG_SIGSUM_SIG_EXPIRED",         GPGME_SIGSUM_SIG_EXPIRED, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("GNUPG_SIGSUM_KEY_MISSING",         GPGME_SIGSUM_KEY_MISSING, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("GNUPG_SIGSUM_CRL_MISSING",         GPGME_SIGSUM_CRL_MISSING, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("GNUPG_SIGSUM_CRL_TOO_OLD",         GPGME_SIGSUM_CRL_TOO_OLD, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("GNUPG_SIGSUM_BAD_POLICY",          GPGME_SIGSUM_BAD_POLICY, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("GNUPG_SIGSUM_SYS_ERROR",           GPGME_SIGSUM_SYS_ERROR, CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("GNUPG_ERROR_WARNING",              1, CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("GNUPG_ERROR_EXCEPTION",            2, CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("GNUPG_ERROR_SILENT",               3, CONST_CS | CONST_PERSISTENT);
	return SUCCESS;
}
/* }}} */

/* {{{ PHP_MSHUTDOWN_FUNCTION
 */
PHP_MSHUTDOWN_FUNCTION(gnupg)
{
	return SUCCESS;
}
/* }}} */

/* {{{ PHP_MINFO_FUNCTION
 */
PHP_MINFO_FUNCTION(gnupg)
{
	php_info_print_table_start();
	php_info_print_table_header(2, "gnupg support", "enabled");
	php_info_print_table_row(2,"GPGme Version",gpgme_check_version(NULL));
	php_info_print_table_row(2,"Extension Version",PHP_GNUPG_VERSION);
	php_info_print_table_end();
}
/* }}} */

/* {{{ callback func for setting the passphrase */

gpgme_error_t passphrase_cb (gnupg_object *intern, const char *uid_hint, const char *passphrase_info,int last_was_bad, int fd TSRMLS_DC){
	char uid[17];
	int idx;
	char *passphrase = NULL;
	zval *return_value = NULL;

	if(last_was_bad){
		GNUPG_ERR("Incorrent passphrase");
		return 1;
	}
	for(idx=0;idx<16;idx++){
		uid[idx] = uid_hint[idx];
	}
	uid[16] = '\0';
	if(zend_hash_find(intern->signkeys,(char *) uid,17,(void **) &passphrase)==FAILURE){
		GNUPG_ERR("no passphrase set");
		return 1;
	}
	if(!passphrase){
		GNUPG_ERR("no passphrase set");
        return 1;
	}
	
	if (write (fd, passphrase, strlen(passphrase))==strlen(passphrase)
		&& write (fd, "\n", 1)==1) {
		return 0;
	}
	GNUPG_ERR("write failed");
	return 1;
}

gpgme_error_t passphrase_decrypt_cb (gnupg_object *intern, const char *uid_hint, const char *passphrase_info,int last_was_bad, int fd TSRMLS_DC){
    char uid[17];
    int idx;
    char *passphrase = NULL;
	zval *return_value = NULL;

    if(last_was_bad){
		GNUPG_ERR("Incorrent passphrase");
        return 1;
    }
    for(idx=0;idx<16;idx++){
        uid[idx] = uid_hint[idx];
    }
    uid[16] = '\0';
    if(zend_hash_find(intern->decryptkeys,(char *) uid,17,(void **) &passphrase)==FAILURE){
		GNUPG_ERR("no passphrase set");
        return 1;
    }
    if(!passphrase){
		GNUPG_ERR("no passphrase set");
        return 1;
    }
	if (write (fd, passphrase, strlen(passphrase))==strlen(passphrase)
		&& write (fd, "\n", 1)==1) {
		return 0;
	}
	GNUPG_ERR("write failed");
	return 1;
}

/* }}} */

/* {{{ gnupg_fetchsignatures */
int gnupg_fetchsignatures(gpgme_signature_t gpgme_signatures, zval *main_arr){
	zval *sig_arr;

	array_init              (main_arr);
    while(gpgme_signatures){
        ALLOC_INIT_ZVAL     (sig_arr);
        array_init          (sig_arr);
        add_assoc_string    (sig_arr,  "fingerprint",  gpgme_signatures->fpr,        1);
        add_assoc_long      (sig_arr,  "validity",     gpgme_signatures->validity    );
        add_assoc_long      (sig_arr,  "timestamp",    gpgme_signatures->timestamp   );
        add_assoc_long      (sig_arr,  "status",       gpgme_signatures->status      );
		add_assoc_long		(sig_arr,  "summary",	   gpgme_signatures->summary     );
        add_next_index_zval (main_arr, sig_arr);

        gpgme_signatures    =   gpgme_signatures->next;
    }
	return 1;
}
/* }}} */

/* {{{ proto resource gnupg_init()
 * inits gnupg and returns a resource
*/
PHP_FUNCTION(gnupg_init){
	gnupg_object *intern;
	intern =  emalloc(sizeof(gnupg_object));
	gnupg_res_init(intern TSRMLS_CC);
	ZEND_REGISTER_RESOURCE(return_value,intern,le_gnupg);
}
/* }}} */

/* {{{ proto bool gnupg_setarmor(int armor)
 * turn on/off armor mode
 * 0 = off
 * >0 = on
 * */
PHP_FUNCTION(gnupg_setarmor){
	long		armor;

	GNUPG_GETOBJ();

	if(this){
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &armor) == FAILURE){
            return;
        }
    }else{
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rl", &res, &armor) == FAILURE){
            return;
        }
        ZEND_FETCH_RESOURCE(intern,gnupg_object *, &res, -1, "ctx", le_gnupg);
    }

	if(armor > 1){
		armor = 1; /*just to make sure */
	}

	gpgme_set_armor (intern->ctx,armor);
	RETURN_TRUE;
}
/* }}} */

/* {{{ proto bool gnupg_seterrormode(int errormde) */
PHP_FUNCTION(gnupg_seterrormode){
	long errormode;

	GNUPG_GETOBJ();

	if(this){
		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &errormode) == FAILURE){
            return;
        }
    }else{
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rl", &res, &errormode) == FAILURE){
            return;
        }
        ZEND_FETCH_RESOURCE(intern,gnupg_object *, &res, -1, "ctx", le_gnupg);
    }

	switch(errormode){
		case 1:		/* warning */
		case 3:		/* silent */
			intern->errormode = errormode;
			break;
#ifdef ZEND_ENGINE_2
		case 2:		/* exception */
			intern->errormode = errormode;
			break;
#endif
		default:
			GNUPG_ERR("invalid errormode");
	}
}
/* }}} */

/* {{{ proto bool gnupg_setsignmode(int signmode)
 * sets the mode for signing operations
 */
PHP_FUNCTION(gnupg_setsignmode){
	long			 signmode;

	GNUPG_GETOBJ();

	if(this){
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &signmode) == FAILURE){
            return;
        }
    }else{
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rl", &res, &signmode) == FAILURE){
            return;
        }
        ZEND_FETCH_RESOURCE(intern,gnupg_object *, &res, -1, "ctx", le_gnupg);
    }
	switch(signmode){
		case GPGME_SIG_MODE_NORMAL:
		case GPGME_SIG_MODE_DETACH:
		case GPGME_SIG_MODE_CLEAR:
			intern->signmode = signmode;
			RETVAL_TRUE;
			break;
		default:
			GNUPG_ERR("invalid signmode");
			RETVAL_FALSE;
			break;
	}
}
/* }}} */

/* {{{ proto string gnupg_geterror(void)
 * returns the last errormessage
 */
PHP_FUNCTION(gnupg_geterror){
	GNUPG_GETOBJ();
	
	if(!this){
		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &res) == FAILURE){
            return;
        }
        ZEND_FETCH_RESOURCE(intern,gnupg_object *, &res, -1, "ctx", le_gnupg);
	}
	if(!intern->errortxt){
		RETURN_FALSE;
	}else{
		RETURN_STRINGL(intern->errortxt, strlen(intern->errortxt), 1);
	}
}
/* }}} */

/* {{{ proto int gnupg_getprotocol(void)
 * returns the currently used pgp-protocol.
 * atm only OpenPGP is supported
 */
PHP_FUNCTION(gnupg_getprotocol){
	RETURN_LONG(GPGME_PROTOCOL_OpenPGP);
}

/* }}} */

/* {{{ proto array gnupg_keyinfo(string pattern)
 * returns an array with informations about all keys, that matches the given pattern
 */
PHP_FUNCTION(gnupg_keyinfo)
{
	char		*searchkey = NULL;
	int			*searchkey_len;
	zval		*subarr;
	zval		*userid;
	zval		*userids;
	zval		*subkey;
	zval		*subkeys;
	
	gpgme_key_t		gpgme_key;
	gpgme_subkey_t	gpgme_subkey;
	gpgme_user_id_t gpgme_userid;

	GNUPG_GETOBJ();

	if(this){	
		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &searchkey, &searchkey_len) == FAILURE){
			return;
		}
	}else{
		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &res, &searchkey, &searchkey_len) == FAILURE){
			return;
		}
		ZEND_FETCH_RESOURCE(intern,gnupg_object *, &res, -1, "ctx", le_gnupg);
	}
	if((intern->err = gpgme_op_keylist_start(intern->ctx, searchkey, 0)) != GPG_ERR_NO_ERROR){
		GNUPG_ERR("could not init keylist");
		return;
	}
	
	array_init(return_value);
	
	while(!(intern->err = gpgme_op_keylist_next(intern->ctx, &gpgme_key))){
		ALLOC_INIT_ZVAL		(subarr);
		array_init			(subarr);
		
		ALLOC_INIT_ZVAL		(subkeys);
		array_init			(subkeys);
		
		ALLOC_INIT_ZVAL		(userids);
		array_init			(userids);

		add_assoc_bool      (subarr,	"disabled",		gpgme_key->disabled		);
		add_assoc_bool      (subarr,	"expired",		gpgme_key->expired		);
		add_assoc_bool      (subarr,	"revoked",		gpgme_key->revoked		);
		add_assoc_bool      (subarr,	"is_secret",	gpgme_key->secret		);
		add_assoc_bool      (subarr,	"can_sign",		gpgme_key->can_sign		);
		add_assoc_bool      (subarr,	"can_encrypt",	gpgme_key->can_encrypt	);

		gpgme_userid	=	gpgme_key->uids;
		while(gpgme_userid){
			ALLOC_INIT_ZVAL		(userid);
			array_init			(userid);
			
			add_assoc_string    (userid,	"name",		gpgme_userid->name,		1);
			add_assoc_string	(userid,	"comment",	gpgme_userid->comment,	1);
			add_assoc_string    (userid,	"email",	gpgme_userid->email,	1);
			add_assoc_string    (userid,	"uid",		gpgme_userid->uid,		1);
			
			add_assoc_bool      (userid,	"revoked",	gpgme_userid->revoked	);
			add_assoc_bool      (userid,	"invalid",	gpgme_userid->invalid	);
			
			add_next_index_zval	(userids, userid);
			gpgme_userid	=	gpgme_userid->next;
		}
		
		add_assoc_zval			(subarr,	"uids",	userids);
		
		gpgme_subkey		=	gpgme_key->subkeys;
		while(gpgme_subkey){
			ALLOC_INIT_ZVAL		(subkey);
			array_init			(subkey);
			
			if(gpgme_subkey->fpr){
				add_assoc_string    (subkey,	"fingerprint",	gpgme_subkey->fpr,		1);
			}
			
			add_assoc_string    (subkey,	"keyid",		gpgme_subkey->keyid,		1);
			
			add_assoc_long	    (subkey,	"timestamp",	gpgme_subkey->timestamp		);
			add_assoc_long	    (subkey,	"expires",		gpgme_subkey->expires		);
			add_assoc_bool	    (subkey,	"is_secret",	gpgme_subkey->secret		);
			add_assoc_bool	    (subkey,	"invalid",		gpgme_subkey->invalid		);
			add_assoc_bool	    (subkey,	"can_encrypt",	gpgme_subkey->can_encrypt	);
			add_assoc_bool	    (subkey,	"can_sign",		gpgme_subkey->can_sign		);
			add_assoc_bool	    (subkey,	"disabled",		gpgme_subkey->disabled		);
			add_assoc_bool	    (subkey,	"expired",		gpgme_subkey->expired		);
			add_assoc_bool	    (subkey,	"revoked",		gpgme_subkey->revoked		);

			add_next_index_zval	(subkeys, subkey);
			gpgme_subkey	=	gpgme_subkey->next;
		}
		
		add_assoc_zval		(subarr,	"subkeys",	subkeys);

		add_next_index_zval	(return_value, subarr);
		gpgme_key_unref		(gpgme_key);
	}
	return;
}
/* }}} */

/* {{{ proto bool gnupg_addsignkey(string key) */
PHP_FUNCTION(gnupg_addsignkey){
    char    *key_id = NULL;
    int     key_id_len;
	char	*passphrase = NULL;
	int		passphrase_len;

    gpgme_key_t         gpgme_key;
	gpgme_subkey_t		gpgme_subkey;

    GNUPG_GETOBJ();

    if(this){
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s", &key_id, &key_id_len, &passphrase, &passphrase_len) == FAILURE){
            return;
        }
    }else{
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs|s", &res, &key_id, &key_id_len, &passphrase, &passphrase_len) == FAILURE){
            return;
        }
        ZEND_FETCH_RESOURCE(intern,gnupg_object *, &res, -1, "ctx", le_gnupg);
    }
    if((intern->err = gpgme_get_key(intern->ctx, key_id, &gpgme_key, 1)) != GPG_ERR_NO_ERROR){
        GNUPG_ERR("get_key failed");
		return;
    }
	if(passphrase){
		gpgme_subkey	=	gpgme_key->subkeys;
		while(gpgme_subkey){
			if(gpgme_subkey->can_sign == 1){
				zend_hash_add(intern->signkeys, (char *) gpgme_subkey->keyid, (uint) strlen(gpgme_subkey->keyid)+1, passphrase, (uint) passphrase_len+1, NULL);
			}	
			gpgme_subkey	=	gpgme_subkey->next;
		}
	}
    if((intern->err = gpgme_signers_add(intern->ctx, gpgme_key))!=GPG_ERR_NO_ERROR){
        GNUPG_ERR("could not add signer");
    }else{
		RETVAL_TRUE;
	}
	gpgme_key_unref(gpgme_key);
}
/* }}} */

/* {{{ proto bool gnupg_adddecryptkey(string key) */
PHP_FUNCTION(gnupg_adddecryptkey){
    char    *key_id = NULL;
    int     key_id_len;
    char    *passphrase = NULL;
    int     passphrase_len;

    gpgme_key_t         gpgme_key;
    gpgme_subkey_t      gpgme_subkey;

    GNUPG_GETOBJ();

    if(this){
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &key_id, &key_id_len, &passphrase, &passphrase_len) == FAILURE){
            return;
        }
    }else{
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rss", &res, &key_id, &key_id_len, &passphrase, &passphrase_len) == FAILURE){
            return;
        }
        ZEND_FETCH_RESOURCE(intern,gnupg_object *, &res, -1, "ctx", le_gnupg);
    }
    if((intern->err = gpgme_get_key(intern->ctx, key_id, &gpgme_key, 1)) != GPG_ERR_NO_ERROR){
        GNUPG_ERR("get_key failed");
		return;
    }
    gpgme_subkey    =   gpgme_key->subkeys;
    while(gpgme_subkey){
    	if(gpgme_subkey->secret == 1){
        	zend_hash_add(intern->decryptkeys, (char *) gpgme_subkey->keyid, (uint) strlen(gpgme_subkey->keyid)+1, passphrase, (uint) passphrase_len+1, NULL);
		}
        gpgme_subkey    =   gpgme_subkey->next;
	}
	gpgme_key_unref(gpgme_key);
    RETURN_TRUE;
}
/* }}} */

/* {{{ proto bool gnupg_addencryptkey(string key) */
PHP_FUNCTION(gnupg_addencryptkey){
    char    *key_id = NULL;
    int     key_id_len;

    gpgme_key_t gpgme_key = NULL;

    GNUPG_GETOBJ();

    if(this){
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &key_id, &key_id_len) == FAILURE){
            return;
        }
    }else{
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &res, &key_id, &key_id_len) == FAILURE){
            return;
        }
        ZEND_FETCH_RESOURCE(intern,gnupg_object *, &res, -1, "ctx", le_gnupg);
    }

    if((intern->err = gpgme_get_key(intern->ctx, key_id, &gpgme_key, 0)) != GPG_ERR_NO_ERROR){
        GNUPG_ERR("get_key failed");
		return;
    }
    intern->encryptkeys = erealloc(intern->encryptkeys, sizeof(intern->encryptkeys) * (intern->encrypt_size + 2));
	intern->encryptkeys[intern->encrypt_size] = gpgme_key;
	intern->encrypt_size++;
	intern->encryptkeys[intern->encrypt_size] = NULL;
	RETURN_TRUE;
}
/* }}} */

/* {{{ proto bool gnupg_clearsignerkeys(void)
 * removes all keys which are set for signing
 */
PHP_FUNCTION(gnupg_clearsignkeys){
	GNUPG_GETOBJ();	

	if(!this){
		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &res) == FAILURE){
            return;
        }
        ZEND_FETCH_RESOURCE(intern,gnupg_object *, &res, -1, "ctx", le_gnupg);
	}

	gpgme_signers_clear	(intern->ctx);
	zend_hash_clean(intern->signkeys);
	RETURN_TRUE;
}
/* }}} */

/* {{{ proto bool gnupg_clearencryptkeys(void)
 * removes all keys which are set for encryption
 */
PHP_FUNCTION(gnupg_clearencryptkeys){
	GNUPG_GETOBJ();

	if(!this){
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &res) == FAILURE){
            return;
        }
        ZEND_FETCH_RESOURCE(intern,gnupg_object *, &res, -1, "ctx", le_gnupg);
    }
	gnupg_free_encryptkeys(intern TSRMLS_CC);	

    RETURN_TRUE;
}
/* }}} */

/* {{{ proto bool gnupg_clearsignerkeys(void)
 * removes all keys which are set for signing
 */
PHP_FUNCTION(gnupg_cleardecryptkeys){
    GNUPG_GETOBJ();

    if(!this){
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &res) == FAILURE){
            return;
        }
        ZEND_FETCH_RESOURCE(intern,gnupg_object *, &res, -1, "ctx", le_gnupg);
    }

    zend_hash_clean(intern->decryptkeys);
    RETURN_TRUE;
}
/* }}} */

/* {{{ proto string gnupg_sign(string text)
 * signs the given test with the key, which was set with setsignerkey before
 * and returns the signed text
 * the signmode depends on gnupg_setsignmode
 */
PHP_FUNCTION(gnupg_sign){
    char    *value = NULL;
    int     value_len;

    char    *userret;
    size_t  ret_size;

    gpgme_data_t in, out;
	gpgme_sign_result_t result;

	GNUPG_GETOBJ();

	if(this){
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &value, &value_len) == FAILURE){
            return;
        }
    }else{
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &res, &value, &value_len) == FAILURE){
            return;
        }
        ZEND_FETCH_RESOURCE(intern,gnupg_object *, &res, -1, "ctx", le_gnupg);
    }

    gpgme_set_passphrase_cb (intern->ctx, (void*) passphrase_cb, intern);
    if((intern->err = gpgme_data_new_from_mem (&in, value, value_len, 0))!=GPG_ERR_NO_ERROR){
		GNUPG_ERR("could not create in-data buffer");
		return;
    }
    if((intern->err = gpgme_data_new(&out))!=GPG_ERR_NO_ERROR){
		GNUPG_ERR("could not create out-data buffer");
		gpgme_data_release(in);
		return;
    }
    if((intern->err = gpgme_op_sign(intern->ctx, in, out, intern->signmode))!=GPG_ERR_NO_ERROR){
		if(!intern->errortxt){
			GNUPG_ERR("data signing failed");
		}
		gpgme_data_release(in);
		gpgme_data_release(out);
		RETVAL_FALSE;
		return;
    }
	result		=	gpgme_op_sign_result (intern->ctx);
	if(result->invalid_signers){
		GNUPG_ERR("invalid signers found");
		gpgme_data_release(in);
        gpgme_data_release(out);
		return;
	}
	if(!result->signatures){
		GNUPG_ERR("no signature in result");
		gpgme_data_release(in);
        gpgme_data_release(out);
        return;
	}
    userret     =   gpgme_data_release_and_get_mem(out,&ret_size);
    if(ret_size < 1){
		RETVAL_FALSE;
    }else{
		RETVAL_STRINGL	(userret,ret_size,1);
	}
    gpgme_data_release  (in);
	free				(userret);
}

/* }}} */

/* {{{ proto string gnupg_encrypt(string text)
 * encrypts the given text with the key, which was set with setencryptkey before
 * and returns the encrypted text
 */
PHP_FUNCTION(gnupg_encrypt){
	char *value = NULL;
	int value_len;
	char *userret = NULL;
	size_t ret_size;

	gpgme_data_t in, out;
	gpgme_encrypt_result_t result;

	GNUPG_GETOBJ();	
	
	if(this){
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &value, &value_len) == FAILURE){
            return;
        }
    }else{
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &res, &value, &value_len) == FAILURE){
            return;
        }
        ZEND_FETCH_RESOURCE(intern,gnupg_object *, &res, -1, "ctx", le_gnupg);
    }
	if(!intern->encryptkeys){
		GNUPG_ERR("no key for encryption set");
		return;
	}
	if((intern->err = gpgme_data_new_from_mem (&in, value, value_len, 0))!=GPG_ERR_NO_ERROR){
		GNUPG_ERR("could no create in-data buffer");
		return;
	}
	if((intern->err = gpgme_data_new(&out))!=GPG_ERR_NO_ERROR){
		GNUPG_ERR("could not create out-data buffer");
		gpgme_data_release(in);
		return;
	}
	if((intern->err = gpgme_op_encrypt(intern->ctx, intern->encryptkeys, GPGME_ENCRYPT_ALWAYS_TRUST, in, out))!=GPG_ERR_NO_ERROR){
        GNUPG_ERR("encrypt failed");
		gpgme_data_release(in);
		gpgme_data_release(out);
		return;
    }
	result		=	gpgme_op_encrypt_result (intern->ctx);
	if (result->invalid_recipients){
		GNUPG_ERR("Invalid recipient encountered");
		gpgme_data_release(in);
        gpgme_data_release(out);
		return;
	}
	userret		=	gpgme_data_release_and_get_mem(out,&ret_size);
	gpgme_data_release	(in);
	RETVAL_STRINGL		(userret,ret_size,1);
	free				(userret);
	if(ret_size < 1){
		RETURN_FALSE;
	}
}
/* }}} */

/* {{{ proto string gnupg_encrypt_sign(string text)
 * encrypts and signs the given text with the keys, which weres set with setencryptkey and setsignkey before
 * and returns the encrypted text
 */
PHP_FUNCTION(gnupg_encryptsign){
    char *value = NULL;
    int value_len;
    char *userret = NULL;
    size_t ret_size;

    gpgme_data_t in, out;
	gpgme_encrypt_result_t result;
	gpgme_sign_result_t sign_result;

	GNUPG_GETOBJ();

	if(this){
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &value, &value_len) == FAILURE){
            return;
        }
    }else{
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &res, &value, &value_len) == FAILURE){
            return;
        }
        ZEND_FETCH_RESOURCE(intern,gnupg_object *, &res, -1, "ctx", le_gnupg);
    }

    if(!intern->encryptkeys){
		GNUPG_ERR("no key for encryption set");
		return;
    }
	gpgme_set_passphrase_cb (intern->ctx, (void*) passphrase_cb, intern);
    if((intern->err = gpgme_data_new_from_mem (&in, value, value_len, 0))!=GPG_ERR_NO_ERROR){
		GNUPG_ERR("could not create in-data buffer");
		return;
    }
    if((intern->err = gpgme_data_new(&out))!=GPG_ERR_NO_ERROR){
		GNUPG_ERR("could not create out-data buffer");
		gpgme_data_release(in);
		return;
    }
	if((intern->err = gpgme_op_encrypt_sign(intern->ctx, intern->encryptkeys, GPGME_ENCRYPT_ALWAYS_TRUST, in, out))!=GPG_ERR_NO_ERROR){
		if(!intern->errortxt){
			GNUPG_ERR("encrypt-sign failed");
		}
		gpgme_data_release(in);
		gpgme_data_release(out);
		RETVAL_FALSE;
		return;
	}

	result      =   gpgme_op_encrypt_result (intern->ctx);
    if (result->invalid_recipients){
        GNUPG_ERR("Invalid recipient encountered");
		gpgme_data_release(in);
        gpgme_data_release(out);
		return;
    }

	sign_result =	gpgme_op_sign_result (intern->ctx);
	if(sign_result->invalid_signers){
        GNUPG_ERR("invalid signers found");
		gpgme_data_release(in);
        gpgme_data_release(out);
		return;
    }
    if(!sign_result->signatures){
        GNUPG_ERR("could not find a signature");
		gpgme_data_release(in);
        gpgme_data_release(out);
        return;
    }
	
    userret     =   gpgme_data_release_and_get_mem(out,&ret_size);
    gpgme_data_release  (in);
	RETVAL_STRINGL		(userret,ret_size,1);
	free (userret);
	if(ret_size < 1){
        RETURN_FALSE;
    }
}
/* }}} */

/* {{{ proto array gnupg_verify(string text, string signature [, string &plaintext])
 * verifies the given clearsigned text and returns information about the result in an array
 */
PHP_FUNCTION(gnupg_verify){
    gpgme_data_t                gpgme_text, gpgme_sig;
    gpgme_verify_result_t       gpgme_result;

	zval	*signed_text	=	NULL; /* text without the signature, if its a detached one, or the text incl the sig */
	zval	*signature		=	NULL; /* signature, if its a detached one */
	zval	*plain_text		=	NULL; /* signed_text without the signature if its not a detached sig */

	char	*gpg_plain;
	size_t	gpg_plain_len;

	GNUPG_GETOBJ();

	if(this){
		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz|z", &signed_text, &signature, &plain_text) == FAILURE){
			return;
		}
    }else{
		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rzz|z", &res, &signed_text, &signature, &plain_text) == FAILURE){
            return;
        }
        ZEND_FETCH_RESOURCE(intern,gnupg_object *, &res, -1, "ctx", le_gnupg);
    }
	if(Z_STRVAL_P(signature)){		/* detached signature */
		/* setup signature-databuffer for gpgme */
	    if((intern->err = gpgme_data_new_from_mem (&gpgme_sig, Z_STRVAL_P(signature), Z_STRLEN_P(signature), 0))!=GPG_ERR_NO_ERROR){
       		GNUPG_ERR               ("could not create signature-databuffer");
	        return;
	    }
		/* and the text */
	    if((intern->err = gpgme_data_new_from_mem (&gpgme_text, Z_STRVAL_P(signed_text), Z_STRLEN_P(signed_text), 0))!=GPG_ERR_NO_ERROR){
	        GNUPG_ERR               ("could not create text-databuffer");
       		gpgme_data_release      (gpgme_sig);
	        gpgme_data_release      (gpgme_text);
       		return;
	    }
		/* now verify sig + text */
	    if((intern->err = gpgme_op_verify (intern->ctx, gpgme_sig, gpgme_text, NULL))!=GPG_ERR_NO_ERROR){
       		GNUPG_ERR               ("verify failed");
	        gpgme_data_release      (gpgme_sig);
	        gpgme_data_release      (gpgme_text);
	        return;
	    }
	}else{							/* clearsign or normal signature */
    	if((intern->err = gpgme_data_new_from_mem (&gpgme_sig, Z_STRVAL_P(signed_text), Z_STRLEN_P(signed_text), 0))!=GPG_ERR_NO_ERROR){
        	GNUPG_ERR               ("could not create signature-databuffer");
	        return;
    	}
		/* set a NULL databuffer for gpgme */
	    if((intern->err = gpgme_data_new_from_mem (&gpgme_text, NULL, 0, 0))!=GPG_ERR_NO_ERROR){
    	    GNUPG_ERR               ("could not create text-databuffer");
        	gpgme_data_release      (gpgme_sig);
	        gpgme_data_release      (gpgme_text);
    	    return;
	    }
		/* and verify the 'signature' */
    	if((intern->err = gpgme_op_verify (intern->ctx, gpgme_sig, NULL, gpgme_text))!=GPG_ERR_NO_ERROR){
        	GNUPG_ERR               ("verify failed");
	        gpgme_data_release      (gpgme_sig);
    	    gpgme_data_release      (gpgme_text);
        	return;
	    }
	}
	/* now get the result */
	gpgme_result            =   	gpgme_op_verify_result (intern->ctx);
    if(!gpgme_result->signatures){
        GNUPG_ERR               	("no signature found");
    }else{
		/* fetch all signatures in an array */
        gnupg_fetchsignatures   	(gpgme_result->signatures, return_value);
		/* get a 'plain' version of the text without a signature */
		gpg_plain			=		gpgme_data_release_and_get_mem(gpgme_text,&gpg_plain_len);
        if(gpg_plain && gpg_plain_len > 0 && plain_text){
            ZVAL_STRINGL        	(plain_text, gpg_plain,gpg_plain_len,1);
        }
        free                    	(gpg_plain);
    }
    gpgme_data_release          	(gpgme_sig);
}
/* }}} */

/* {{{ proto string gnupg_decrypt(string enctext)
 * decrypts the given enctext
 */
PHP_FUNCTION(gnupg_decrypt){
	char	*enctxt;
	int		enctxt_len;

	char    *userret;
    size_t  ret_size;

	gpgme_data_t			in, out;
	gpgme_decrypt_result_t	result;
	
	GNUPG_GETOBJ();  

	if(this){
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &enctxt, &enctxt_len) == FAILURE){
            return;
        }
    }else{
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &res, &enctxt, &enctxt_len) == FAILURE){
            return;
        }
        ZEND_FETCH_RESOURCE(intern,gnupg_object *, &res, -1, "ctx", le_gnupg);
    }

	gpgme_set_passphrase_cb (intern->ctx, (void*) passphrase_decrypt_cb, intern);
	
	if((intern->err = gpgme_data_new_from_mem (&in, enctxt, enctxt_len, 0))!=GPG_ERR_NO_ERROR){
		GNUPG_ERR("could not create in-data buffer");
	}
	if((intern->err = gpgme_data_new (&out))!=GPG_ERR_NO_ERROR){
		GNUPG_ERR("could not create out-data buffer");
		gpgme_data_release(in);
		return;
	}
	if((intern->err = gpgme_op_decrypt (intern->ctx, in, out))!=GPG_ERR_NO_ERROR){
		if(!intern->errortxt){
			GNUPG_ERR("decrypt failed");
		}
		gpgme_data_release(in);
		gpgme_data_release(out);
		RETVAL_FALSE;
		return;
	}
	result = gpgme_op_decrypt_result (intern->ctx);
	if (result->unsupported_algorithm){
		GNUPG_ERR("unsupported algorithm");
		gpgme_data_release(in);
		gpgme_data_release(out);
		return;
	}	
	userret             =   gpgme_data_release_and_get_mem(out,&ret_size);
	gpgme_data_release		(in);
	RETVAL_STRINGL			(userret,ret_size,1);
	free					(userret);
	if(ret_size < 1){
		RETVAL_FALSE;
	}
}
/* }}} */

/* {{{ proto string gnupg_decryptverify(string enctext, string &plaintext)
 * decrypts the given enctext
 */
PHP_FUNCTION(gnupg_decryptverify){
    char    *enctxt;
    int     enctxt_len;
	zval	*plaintext;

    char    *userret;
    size_t  ret_size;

    gpgme_data_t            in, out;
	gpgme_decrypt_result_t	decrypt_result;
	gpgme_verify_result_t	verify_result;

	GNUPG_GETOBJ();

	if(this){
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz", &enctxt, &enctxt_len, &plaintext) == FAILURE){
            return;
        }
    }else{
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rsz", &res, &enctxt, &enctxt_len, &plaintext) == FAILURE){
            return;
        }
        ZEND_FETCH_RESOURCE(intern,gnupg_object *, &res, -1, "ctx", le_gnupg);
    }

    gpgme_set_passphrase_cb (intern->ctx, (void*) passphrase_decrypt_cb, intern);

    if((intern->err = gpgme_data_new_from_mem (&in, enctxt, enctxt_len, 0))!=GPG_ERR_NO_ERROR){
		GNUPG_ERR("could not create in-data buffer");
    }
    if((intern->err = gpgme_data_new (&out))!=GPG_ERR_NO_ERROR){
		GNUPG_ERR("could not create out-data buffer");
		gpgme_data_release(in);
		return;
    }
    if((intern->err = gpgme_op_decrypt_verify (intern->ctx, in, out))!=GPG_ERR_NO_ERROR){
		if(!intern->errortxt){
			GNUPG_ERR("decrypt-verify failed");
		}
		gpgme_data_release(in);
		gpgme_data_release(out);
		RETVAL_FALSE;
		return;
    }
    userret             =   gpgme_data_release_and_get_mem(out,&ret_size);
	ZVAL_STRINGL			(plaintext,userret,ret_size,1);
	free					(userret);
	decrypt_result		=	gpgme_op_decrypt_result (intern->ctx);
	if (decrypt_result->unsupported_algorithm){
		GNUPG_ERR			("unsupported algorithm");
		gpgme_data_release(in);
        return;
	}
	verify_result       =   gpgme_op_verify_result (intern->ctx);
	if(!verify_result->signatures){
        GNUPG_ERR           ("no signature found");
		gpgme_data_release(in);
        return;
    }
	gnupg_fetchsignatures   (verify_result->signatures, return_value);
    gpgme_data_release      (in);
}
/* }}} */

/* {{{ proto string gnupg_export(string pattern)
 * exports the first public key which matches against the given pattern
 */
PHP_FUNCTION(gnupg_export){
	char	*searchkey = NULL;
    int     *searchkey_len;
	char	*userret;
    size_t	ret_size;

	gpgme_data_t  out;

	GNUPG_GETOBJ();

	if(this){
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &searchkey, &searchkey_len) == FAILURE){
            return;
        }
    }else{
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &res, &searchkey, &searchkey_len) == FAILURE){
            return;
        }
        ZEND_FETCH_RESOURCE(intern,gnupg_object *, &res, -1, "ctx", le_gnupg);
    }
	if((intern->err = gpgme_data_new (&out))!=GPG_ERR_NO_ERROR){
		GNUPG_ERR("could not create data buffer");
		return;
	}
	if((intern->err = gpgme_op_export (intern->ctx, searchkey, 0, out))!=GPG_ERR_NO_ERROR){
		GNUPG_ERR("export failed");
		gpgme_data_release(out);
		return;
	}
	userret             =   gpgme_data_release_and_get_mem(out,&ret_size);
	RETVAL_STRINGL          (userret,ret_size,1);
	if(ret_size < 1){
		RETVAL_FALSE;
	}
	free(userret);
}
/* }}} */

/* {{{ proto array gnupg_import(string key)
 * imports the given key and returns a status-array
*/
PHP_FUNCTION(gnupg_import){
	char			*importkey = NULL;
	int				importkey_len;

	gpgme_data_t	in;
	gpgme_import_result_t result;

	GNUPG_GETOBJ();

	if(this){
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &importkey, &importkey_len) == FAILURE){
            return;
        }
    }else{
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &res, &importkey, &importkey_len) == FAILURE){
            return;
        }
        ZEND_FETCH_RESOURCE(intern,gnupg_object *, &res, -1, "ctx", le_gnupg);
    }
	if((intern->err = gpgme_data_new_from_mem (&in, importkey, importkey_len, 0))!=GPG_ERR_NO_ERROR){
		GNUPG_ERR("could not create in-data buffer");
		return;
	}
	if((intern->err = gpgme_op_import(intern->ctx,in))!=GPG_ERR_NO_ERROR){
		GNUPG_ERR("import failed");
		gpgme_data_release(in);
		return;
	}
	gpgme_data_release(in);
	result = gpgme_op_import_result (intern->ctx);
	
	if(result->imports->result != GPG_ERR_NO_ERROR){
		RETURN_FALSE;
	}
	array_init				(return_value);
    add_assoc_long          (return_value,  "imported",     	result->imported);
	add_assoc_long			(return_value,	"unchanged",		result->unchanged);
	add_assoc_long			(return_value,	"newuserids",		result->new_user_ids);
	add_assoc_long			(return_value,	"newsubkeys",		result->new_sub_keys);
	add_assoc_long			(return_value,	"secretimported",	result->secret_imported);
	add_assoc_long			(return_value,	"secretunchanged",	result->secret_unchanged);
	add_assoc_long			(return_value,	"newsignatures",	result->new_signatures);
	add_assoc_long			(return_value,	"skippedkeys",		result->skipped_new_keys);
	if(result->imports && result->imports->fpr){
		add_assoc_string		(return_value,	"fingerprint",		result->imports->fpr,	1);
	}
}
/* }}} */

/* {{{ proto book gnupg_deletekey(string key)
*	deletes a key from the keyring
*/
PHP_FUNCTION(gnupg_deletekey){
	char	*key;
	int		key_len;
	long	allow_secret = 0;

	gpgme_key_t	gpgme_key;

	GNUPG_GETOBJ();

	if(this){
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &key, &key_len, &allow_secret) == FAILURE){
            return;
        }
    }else{
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs|l", &res, &key, &key_len, &allow_secret) == FAILURE){
            return;
        }
        ZEND_FETCH_RESOURCE(intern,gnupg_object *, &res, -1, "ctx", le_gnupg);
    }

	if((intern->err = gpgme_get_key(intern->ctx, key, &gpgme_key, 0)) != GPG_ERR_NO_ERROR){
        GNUPG_ERR("get_key failed");
		return;
    }
	if((intern->err = gpgme_op_delete(intern->ctx,gpgme_key,allow_secret))!=GPG_ERR_NO_ERROR){
		GNUPG_ERR("delete failed");
		RETVAL_FALSE;
	}else{
		RETVAL_TRUE;
	}
	gpgme_key_unref(gpgme_key);
}
/* }}} */

/* {{{ proto array gnupg_gettrustlist(string pattern)
* searching for trust items which match PATTERN
*/
PHP_FUNCTION(gnupg_gettrustlist){
	char *pattern;
	int	pattern_len;
	zval *sub_arr;

	gpgme_trust_item_t item;

	GNUPG_GETOBJ();

	if(this){
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &pattern, &pattern_len) == FAILURE){
            return;
        }
    }else{
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &res, &pattern, &pattern_len) == FAILURE){
            return;
        }
        ZEND_FETCH_RESOURCE(intern,gnupg_object *, &res, -1, "ctx", le_gnupg);
    }
	if((intern->err = gpgme_op_trustlist_start (intern->ctx, pattern, 0))!=GPG_ERR_NO_ERROR){
		GNUPG_ERR("could not start trustlist");
		return;
	}
	array_init(return_value);
	while (!(intern->err = gpgme_op_trustlist_next (intern->ctx, &item))){
		ALLOC_INIT_ZVAL 	    (sub_arr);
        array_init          	(sub_arr);

		add_assoc_long			(sub_arr,	"level",		item->level			);
		add_assoc_long      	(sub_arr,	"type",			item->type			);
		add_assoc_string		(sub_arr,	"keyid",		item->keyid,		1);
		add_assoc_string    	(sub_arr,   "ownertrust",	item->owner_trust,	1);
		add_assoc_string    	(sub_arr,   "validity",		item->validity,		1);
		add_assoc_string    	(sub_arr,   "name",			item->name,			1);
		gpgme_trust_item_unref	(item);
		add_next_index_zval		(return_value, sub_arr);		
	}
}
/* }}} */

/* {{{ proto array gnupg_listsignatures(string keyid) */
PHP_FUNCTION(gnupg_listsignatures){
	char	*keyid;
	int	keyid_len;

	zval	*sub_arr;
	zval	*sig_arr;

	gpgme_key_t		gpgme_key;
	gpgme_user_id_t	gpgme_userid;
	gpgme_key_sig_t	gpgme_signature;

	GNUPG_GETOBJ();

	if(this){
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &keyid, &keyid_len) == FAILURE){
            return;
        }
    }else{
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &res, &keyid, &keyid_len) == FAILURE){
            return;
        }
        ZEND_FETCH_RESOURCE(intern,gnupg_object *, &res, -1, "ctx", le_gnupg);
    }
	if((intern->err = gpgme_set_keylist_mode(intern->ctx,GPGME_KEYLIST_MODE_SIGS))!=GPG_ERR_NO_ERROR){
		GNUPG_ERR("could not switch to sigmode");
		return;
	}
	if((intern->err = gpgme_get_key(intern->ctx, keyid, &gpgme_key, 0)) != GPG_ERR_NO_ERROR){
        GNUPG_ERR("get_key failed. given key not unique?");
		return;
    }
	if(!gpgme_key->uids){
		GNUPG_ERR("no uids found");
		gpgme_key_unref(gpgme_key);
		return;
	}
	array_init(return_value);
	gpgme_userid	=	gpgme_key->uids;
	while(gpgme_userid){
		ALLOC_INIT_ZVAL         (sub_arr);
        array_init              (sub_arr);
		gpgme_signature = gpgme_userid->signatures;
		while(gpgme_signature){
			ALLOC_INIT_ZVAL		(sig_arr);
	        array_init			(sig_arr);

			add_assoc_string	(sig_arr,	"uid",		gpgme_signature->uid,		1);				
			add_assoc_string    (sig_arr,   "name",     gpgme_signature->name,      1);
			add_assoc_string    (sig_arr,   "email",    gpgme_signature->email,     1);
			add_assoc_string    (sig_arr,   "comment",  gpgme_signature->comment,   1);
			add_assoc_long		(sig_arr,	"expires",	gpgme_signature->expires	);
			add_assoc_bool		(sig_arr,	"revoked",	gpgme_signature->revoked	);
			add_assoc_bool      (sig_arr,   "expired",	gpgme_signature->expired	);
			add_assoc_bool      (sig_arr,   "invalid",	gpgme_signature->invalid	);
			add_assoc_zval		(sub_arr,gpgme_signature->keyid,sig_arr);
			gpgme_signature	=	gpgme_signature->next;
		}
		add_assoc_zval		(return_value,gpgme_userid->uid,sub_arr);
		gpgme_userid	=	gpgme_userid->next;
	}
	gpgme_key_unref(gpgme_key);
}
/* }}} */

/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim600: noet sw=4 ts=4 fdm=marker
 * vim<600: noet sw=4 ts=4
 */
