/*
 * Copyright 
 *   2008 axYus - www.axyus.com
 *   2008 L.Meckert - laurent.meckert@axyus.com
 * 
 * This file is part of XEMELIOS.
 * 
 * XEMELIOS 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.
 * 
 * XEMELIOS 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 XEMELIOS; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
/** 
 * XML Security Library example: Verifying a file signed with X509 certificate
 *
 * Verifies a file signed with X509 certificate. 
 * 
 * This example was developed and tested with OpenSSL crypto library. The 
 * certificates management policies for another crypto library may break it.
 *
 * 
 * 
 * This is free software; see Copyright file in the source
 * distribution for preciese wording.
 * 
 * Copyright (C) 2002-2003 Aleksey Sanin <aleksey@aleksey.com>
 */

#include <jni.h>
#include "fr_gouv_finances_cp_xemelios_plugin_signature_LXJNISignatureVerifier.h"
/*#include "fr_gouv_finances_cp_xemelios_plugin_signature_LXJNISignatureReporter.h"*/

#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include <libxml/tree.h>
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>
#include <libxml/parserInternals.h>
#include <libxml/c14n.h>

#ifndef XMLSEC_NO_XSLT
#include <libxslt/xslt.h>
#endif /* XMLSEC_NO_XSLT */


#include <xmlsec/xmlsec.h>
#include <xmlsec/xmltree.h>
#include <xmlsec/xmldsig.h>
#include <xmlsec/crypto.h>

#include <xmlsec/parser.h>

char* sgn="ABCDEFG";
xmlBufferPtr xbuf = NULL;
static xmlNodePtr node = NULL;
static int libsInitialized = 0;
static int validationMode = 0;




typedef struct _xmlSecAppXmlData				xmlSecAppXmlData,
   *xmlSecAppXmlDataPtr;
struct _xmlSecAppXmlData {
   xmlDocPtr	doc;
   xmlDtdPtr	dtd;
   xmlNodePtr	startNode;
   int valid;
};

static xmlSecAppXmlDataPtr	xmlSecAppXmlDataCreate		(const char* filename,
                                                                 const char* dtdfilename,
								 const xmlChar* defStartNodeName,
								 const xmlChar* defStartNodeNs);
static void			xmlSecAppXmlDataDestroy		(xmlSecAppXmlDataPtr data);


void myprintf( FILE * f, char * fmt, ...) {
   //  fprintf(stderr, "Hello from myprintf\n");
}

xmlDocPtr
xmlSecParseFileNowarnings(const char *filename) {
    xmlDocPtr ret;
    xmlParserCtxtPtr ctxt;
    char *directory = NULL;
    
 
    xmlInitParser();
    ctxt = xmlCreateFileParserCtxt(filename);
    if (ctxt == NULL) {
	return(NULL);
    }

    /* todo: set directories from current doc? */    
    if ((ctxt->directory == NULL) && (directory == NULL))
        directory = xmlParserGetDirectory(filename);
    if ((ctxt->directory == NULL) && (directory != NULL))
        ctxt->directory = (char *) xmlStrdup((xmlChar *) directory);

    /* required for c14n! */
    ctxt->loadsubset = XML_DETECT_IDS | XML_COMPLETE_ATTRS | XML_PARSE_NOERROR |  XML_PARSE_NOWARNING; 
    ctxt->replaceEntities = 1;
    fprintf(stderr, "start xmlParseDocument\n");
    fflush(stderr);
    xmlParseDocument(ctxt);
  fprintf(stderr, "done xmlParseDocument\n");
    fflush(stderr);
    if(ctxt->wellFormed) { 
	ret = ctxt->myDoc;
    } else {
       ret = NULL;
       xmlFreeDoc(ctxt->myDoc);
       ctxt->myDoc = NULL;
    }
    xmlFreeParserCtxt(ctxt);    
    return(ret);
    
}


static int 
xmlSecAppPrepareKeyInfoReadCtx(xmlSecKeyInfoCtxPtr keyInfoCtx) {
   keyInfoCtx->certsVerificationDepth = 10; 
   keyInfoCtx->flags |= XMLSEC_KEYINFO_FLAGS_X509DATA_SKIP_STRICT_CHECKS;
   return 0;
}
static int
xmlSecAppPrepareDSigCtx(xmlSecDSigCtxPtr dsigCtx) {
   if(dsigCtx == NULL) {
      fprintf(stderr, "Error: dsig context is null\n");
      return(-1);
   }

   /* set key info params */
   if(xmlSecAppPrepareKeyInfoReadCtx(&(dsigCtx->keyInfoReadCtx)) < 0) {
      fprintf(stderr, "Error: failed to prepare key info context\n");
      return(-1);
   }


   return(0);
}


static xmlSecAppXmlDataPtr data = NULL;

static xmlSecAppXmlDataPtr 
xmlSecAppXmlDataCreate(const char* filename, const char* dtdfilename, const xmlChar* defStartNodeName, const xmlChar* defStartNodeNs) {
   //   xmlSecAppCmdLineValuePtr value;
   
   xmlNodePtr cur = NULL;
        
   if(filename == NULL) {
      fprintf(stderr, "Error: xml filename is null\n");
      return(NULL);
   }
    
   /* create object */
   data = (xmlSecAppXmlDataPtr) xmlMalloc(sizeof(xmlSecAppXmlData));
   if(data == NULL) {
      fprintf(stderr, "Error: failed to create xml data\n");
      return(NULL);
   }
   memset(data, 0, sizeof(xmlSecAppXmlData));
    
   /* parse doc */
   data->doc = xmlSecParseFileNowarnings(filename);
   if(data->doc == NULL) {
      fprintf(stderr, "Error: failed to parse xml file \"%s\"\n", 
              filename);
      xmlSecAppXmlDataDestroy(data);
      return(NULL);    
   }
    
   /* load dtd and set default attrs and ids */
   if(dtdfilename != NULL && *dtdfilename != 0) {
      xmlValidCtxt ctx;
       
      data->dtd = xmlParseDTD(NULL, BAD_CAST dtdfilename);
      if(data->dtd == NULL) {
         fprintf(stderr, "Error: failed to parse dtd file \"%s\"\n", 
                 dtdfilename);
         xmlSecAppXmlDataDestroy(data);
         return(NULL);    
      }

      memset(&ctx, 0, sizeof(ctx)); 


      // Detourner Affichage des erreurs de validation
    if (1) {
        ctx.userData = (void *) stderr;
        ctx.error = (xmlValidityErrorFunc) myprintf;
        ctx.warning = (xmlValidityWarningFunc) myprintf;
    }


      xmlValidateDtd(&ctx, data->doc, data->dtd);
      data->valid = ctx.valid;

   }

   cur = xmlDocGetRootElement(data->doc);
   if(cur == NULL) {
      fprintf(stderr, "Error: failed to get root element\n"); 
      xmlSecAppXmlDataDestroy(data);
      return(NULL);    
   }
    
   if(defStartNodeName != NULL) {
      data->startNode = xmlSecFindNode(cur, defStartNodeName, defStartNodeNs);
      if(data->startNode == NULL) {
         fprintf(stderr, "Error: failed to find default node with name=\"%s\"\n", 
                 defStartNodeName);
         fflush(stderr);
         xmlSecAppXmlDataDestroy(data);
         return(NULL);    
      }
   } else {
      data->startNode = cur;
   }
    
   return(data);
}



static void 
xmlSecAppXmlDataDestroy(xmlSecAppXmlDataPtr data) {
   if(data == NULL) {
      fprintf(stderr, "Error: xml data is null\n");
      return;
   }
   if(data->dtd != NULL) {
      xmlFreeDtd(data->dtd);
   }
   if(data->doc != NULL) {
      xmlFreeDoc(data->doc);
   }
   memset(data, 0, sizeof(xmlSecAppXmlData));
   xmlFree(data);    
}



/*xmlSecKeysMngrPtr load_trusted_certs(char** files, int files_size); */

xmlSecKeysMngrPtr initialiseKMgr();
int verify_file(xmlSecKeysMngrPtr mngr, const char* xml_file);

/*


*/
xmlSecKeysMngrPtr 
initialiseKMgr() {
   xmlSecKeysMngrPtr mngr;
   
   /* create and initialize keys manager, we use a simple list based
    * keys manager, implement your own xmlSecKeysStore klass if you need
    * something more sophisticated 
    */
   mngr = xmlSecKeysMngrCreate();
   if(mngr == NULL) {
      fprintf(stderr, "Error: failed to create keys manager.\n");
      return(NULL);
   }
   if(xmlSecCryptoAppDefaultKeysMngrInit(mngr) < 0) {
      fprintf(stderr, "Error: failed to initialize keys manager.\n");
      xmlSecKeysMngrDestroy(mngr);
      return(NULL);
   }  
   return mngr;
}



static int 
setStatusElement(int oldstatus, int newelement) 
{
   return oldstatus | newelement;
}

static int 
unsetStatusElement(int oldstatus, int newelement) 
{
   return oldstatus & ~newelement;
}

/** 
 * verify_file:
 * @mngr:		the pointer to keys manager.
 * @xml_file:		the signed XML file name.
 *
 * Verifie la signature signature in xml_file.
 *
 * Return : int de type bitmask. cf Signatureverifier.java
 */
int 
verify_file(xmlSecKeysMngrPtr gKeysMngr, const char* xml_file,   const char * dtdfile) {
   xmlSecAppXmlDataPtr data = NULL;
   xmlSecDSigCtx dsigCtx;
   
   int res = 0;
   int nbytes= 0;
   assert(mngr);
   assert(xml_file);

 
   if(xmlSecDSigCtxInitialize(&dsigCtx, gKeysMngr) < 0) {
      fprintf(stderr, "Error: dsig context initialization failed\n");
      fflush(stderr);
      return(-1);
   }
   if(xmlSecAppPrepareDSigCtx(&dsigCtx) < 0) {
      fprintf(stderr, "Error: dsig context preparation failed\n");
      fflush(stderr);
      goto done;
   }  

   data = xmlSecAppXmlDataCreate(xml_file, dtdfile,
                                 xmlSecNodeSignature, xmlSecDSigNs);
   if(data == NULL) {
      fprintf(stderr, "Error: failed to load document \"%s\"\n", xml_file);
      fflush(stderr);
      goto done;
   }


   // POUR DEBUG  nbytes =  xmlC14NDocSave( doc,
   //                 NULL,  //xmlNodeSetPtr nodes, 
   // 		0, //			 int exclusive, 
   // 		NULL, //			 xmlChar ** inclusive_ns_prefixes, 
   // 		1, //			 int with_comments, 
   // 		"C:\\outlibxml.xml", //			 const char * filename, 
   //                 0);  //		 int compression)



   fprintf(stderr, "OK.\n");   
   fflush(stderr);
   res = setStatusElement(res, fr_gouv_finances_cp_xemelios_plugin_signature_LXJNISignatureVerifier_PARSEOK);
   if(validationMode && data->valid) {
      res = setStatusElement(res, fr_gouv_finances_cp_xemelios_plugin_signature_LXJNISignatureVerifier_SCHEMAOK);
   }
   /* find start node */
   node = xmlSecFindNode(xmlDocGetRootElement(data->doc), xmlSecNodeSignature, xmlSecDSigNs);
   if(node == NULL) {
      fprintf(stderr, "Error: start node not found in \"%s\"\n", xml_file);
      fflush(stderr);
      goto done;	
   }
   res = setStatusElement(res,fr_gouv_finances_cp_xemelios_plugin_signature_LXJNISignatureVerifier_SIGNATUREFOUND) ; 
  

   fprintf(stderr, "Verify signature .\n");   
   /* Verify signature */
   if(xmlSecDSigCtxVerify(&dsigCtx, data->startNode) < 0) {
      fprintf(stderr,"Error: signature verify\n");
      fflush(stderr);
      goto done;
   }
   fprintf(stderr, "OK.\n");     
   fflush(stderr);
    
   /* print verification result to stdout */
   if(dsigCtx.status == xmlSecDSigStatusSucceeded) {
      res = setStatusElement(res,fr_gouv_finances_cp_xemelios_plugin_signature_LXJNISignatureVerifier_DIGESTVALID);
      fprintf(stderr, "Signature is OK\n");
      fflush(stderr);
   } else {
      fprintf(stderr, "Signature is INVALID\n");
      fflush(stderr);
   }    


 done:    

   fprintf(stderr,"exiting  verify ..\n");
   fflush(stderr);
   return(res);
}





int 
doVerify(const char * xmlFile, const char * dtdFile) {
   xmlSecKeysMngrPtr mngr;
   int res ;

   /* create keys manager and load trusted certificates */
   /*   mngr = load_trusted_certs(&(argv[2]), argc - 2);*/

   fprintf(stderr, "initialiseKMgr.\n");
   mngr = initialiseKMgr();
   if(mngr == NULL) {
      return(-1);
   }
   fprintf(stderr, "OK.\n");  

   fprintf(stderr, "verify_file.\n");  
   /* verify file */
   if((res = verify_file(mngr, xmlFile, dtdFile)) <= 0) {
      xmlSecKeysMngrDestroy(mngr);	
      return(res);
   }
   fprintf(stderr, "OK.\n");        
    
   /* destroy keys manager */
   xmlSecKeysMngrDestroy(mngr);
    
    
   return(res);
}


int 
doReport(const char * xmlFile) {
   //TODO 
    
    
   return(0);
}



int doInitializeLibs()
{
   /* Init libxml and libxslt libraries */
   fprintf(stderr, "xmlInitParser.\n");
   xmlInitParser();
   fprintf(stderr, "OK.\n");
   LIBXML_TEST_VERSION
      xmlLoadExtDtdDefaultValue = XML_DETECT_IDS | XML_COMPLETE_ATTRS;
   xmlSubstituteEntitiesDefault(1);
#ifndef XMLSEC_NO_XSLT
   xmlIndentTreeOutput = 1; 
#endif /* XMLSEC_NO_XSLT */
   fprintf(stderr, "Init xmlsec library.\n");      	
   /* Init xmlsec library */
   if(xmlSecInit() < 0) {
      fprintf(stderr, "Error: xmlsec initialization failed.\n");
      return(-1);
   }
   fprintf(stderr, "OK.\n");

   /* Check loaded library version */
   if(xmlSecCheckVersion() != 1) {
      fprintf(stderr, "Error: loaded xmlsec library version is not compatible.\n");
      return(-1);
   }



   /* Load default crypto engine if we are supporting dynamic
    * loading for xmlsec-crypto libraries. Use the crypto library
    * name ("openssl", "nss", etc.) to load corresponding 
    * xmlsec-crypto library.
    */
#ifdef XMLSEC_CRYPTO_DYNAMIC_LOADING
   fprintf(stderr, "xmlSecCryptoDLLoadLibrary.\n");      	
   if(xmlSecCryptoDLLoadLibrary(BAD_CAST XMLSEC_CRYPTO) < 0) {
      fprintf(stderr, "Error: unable to load default xmlsec-crypto library. Make sure\n"
              "that you have it installed and check shared libraries path\n"
              "(LD_LIBRARY_PATH) envornment variable.\n");
      return(-1);	
   }
   fprintf(stderr, "OK.\n");
#endif /* XMLSEC_CRYPTO_DYNAMIC_LOADING */



   /* Init crypto library */
   if(xmlSecCryptoAppInit(NULL) < 0) {
      fprintf(stderr, "Error: crypto initialization failed.\n");
      return(-1);
   }

   /* Init xmlsec-crypto library */
   if(xmlSecCryptoInit() < 0) {
      fprintf(stderr, "Error: xmlsec-crypto initialization failed.\n");
      return(-1);
   }
   libsInitialized=1;
   return 0;
}


JNIEXPORT jint JNICALL Java_fr_gouv_finances_cp_xemelios_plugin_signature_LXJNISignatureVerifier_nativeInitialize(JNIEnv *jnienv, jobject jobject ) 
{
   if(!libsInitialized)
      {
         return doInitializeLibs();
      }

   return 0;
 
}


int doTerminateLibs()
{

 
   /* Shutdown xmlsec-crypto library */
   xmlSecCryptoShutdown();
    
   /* Shutdown crypto library */
   xmlSecCryptoAppShutdown();
    
   /* Shutdown xmlsec library */
   xmlSecShutdown();

   /* Shutdown libxslt/libxml */
#ifndef XMLSEC_NO_XSLT
   xsltCleanupGlobals();            
#endif /* XMLSEC_NO_XSLT */
   xmlCleanupParser();

   fprintf(stderr," Done xmlCleanupParser\n; ");
   libsInitialized=0;

   return 0;
}


JNIEXPORT void JNICALL Java_fr_gouv_finances_cp_xemelios_plugin_signature_LXJNISignatureVerifier_nativeTerminate(JNIEnv *jnienv, jobject jobject ) 
{

   /* cleanup */

   xmlBufferFree(xbuf);
   xbuf = NULL;
   fprintf(stderr,"cleaning..\n");
   fflush(stderr);
   // DANGER   xmlSecDSigCtxFinalize(&dsigCtx);
   if(data != NULL) {
      xmlSecAppXmlDataDestroy(data);
   }

   // Si on ne compte plus du tout se servir de la librairie
   // appeler doTerminateLibs();

}

JNIEXPORT jint JNICALL Java_fr_gouv_finances_cp_xemelios_plugin_signature_LXJNISignatureVerifier_testFromJava(JNIEnv *jnienv, jobject jobject, jstring jstr, jstring dtdjstr ) 
{
   /* Copie les String java en C*/ 
   const char *filename = jnienv->GetStringUTFChars( jstr, 0); 
   const char *dtdfilename = jnienv->GetStringUTFChars( dtdjstr, 0); 
   int 	ret = doVerify(filename,dtdfilename );
   //Libere la chaine C fabriquee plus haut on n'en a plus besoin
   jnienv->ReleaseStringUTFChars( jstr, filename); 
   jnienv->ReleaseStringUTFChars( dtdjstr, dtdfilename); 
   return ret;
}


JNIEXPORT jstring JNICALL Java_fr_gouv_finances_cp_xemelios_plugin_signature_LXJNISignatureVerifier_getSignature(JNIEnv *jnienv, jobject jobject ) 
{
   jstring ret = NULL;
   /*fprintf(stderr, "node content is %s\n", xmlNodeGetContent	(node));*/

   if(node != NULL) {
      if( xbuf == NULL) {
         xbuf = xmlBufferCreate();
      }

      xmlDocPtr nd =	xmlNewDoc((const xmlChar *)"1.0");
      xmlNodePtr nn =    	xmlDocCopyNode( node, 
                                                nd, 
                                                1);
      xmlReconciliateNs(data->doc,  nn);
 
      xmlNodeDump(xbuf,
                  nd,
                  nn, 0, 0);
    
      // sgn = (char *)xmlBufferContent(xbuf);
      if(xbuf != NULL) {
         ret = jnienv->NewStringUTF( (char *)xmlBufferContent(xbuf)); 
      }
   }
   return ret;
}

JNIEXPORT jint JNICALL Java_fr_gouv_finances_cp_xemelios_plugin_signature_LXJNISignatureVerifier_nativeDoSchemaValidation
(JNIEnv *jnienv , jobject jobject , jint doValidate) {
   if(doValidate != 0 ) {
      validationMode = XML_PARSE_DTDLOAD|XML_PARSE_DTDVALID|XML_PARSE_PEDANTIC;
   } else {
      validationMode = XML_PARSE_NOWARNING;
   }
   return 0;
}

JNIEXPORT jint JNICALL Java_fr_gouv_finances_cp_xemelios_plugin_signature_LXJNISignatureReporter_nativereportSignature
(JNIEnv *jnienv , jobject jobj, jstring jstr ){
   /* Copie la String java en C*/ 
   const char *filename = jnienv->GetStringUTFChars( jstr, 0); 
   int 	ret = doReport(filename);
   //Libere la chaine C fabriquee plus haut on n'en a plus besoin
   jnienv->ReleaseStringUTFChars( jstr, filename); 
   return ret;
}



// dummy main pour linker
int main(int argc, char **argv)
{

   doVerify(argv[1], argv[2]);
   return 0;
}
