/* certificates.c  --  Certificate management
 *
 *  GPLv2 only - Copyright (C) 2008 - 2012
 *               David Sommerseth <dazo@users.sourceforge.net>
 *
 *  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; version 2
 *  of the License.
 *
 *  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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 */

/**
 * @file   sqlite/administration/certificates.c
 * @author David Sommerseth <dazo@users.sourceforge.net>
 * @date   2009-09-13
 *
 * @brief  Certificate management functions
 *
 */

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

#include <libxml/tree.h>

#include <sqlite3.h>

#include <eurephia_nullsafe.h>
#include <eurephia_context.h>
#include <eurephia_log.h>
#include <eurephia_xml.h>
#include <eurephia_values.h>
#include <eurephiadb_session_struct.h>
#include <eurephiadb_mapping.h>
#include <passwd.h>

#ifndef DRIVER_MODE
#define DRIVER_MODE
#endif
#include <eurephiadb_driver.h>

#include "../sqlite.h"

#define FMAP_CERTS              /**< fieldmapping.h: Include declaration of tbl_sqlite_certs */
#include "../fieldmapping.h"


/**
 * Internal function.  Retrieves info about one or more certificates
 *
 * @param ctx      eurephiaCTX
 * @param srch_map eDBfieldMap defining the query
 * @param sortkeys String containing the sort order of the fields
 *
 * @return Returns a valid xmlDoc containing the query,
 *         or a eurephia XML document with an error message.
 */
static xmlDoc *certificate_list(eurephiaCTX *ctx, eDBfieldMap *srch_map, const char *sortkeys) {
	xmlDoc *certlist = NULL;
	xmlNode *cert_n = NULL, *tmp_n = NULL;
        eDBfieldMap *ptr = NULL;
        dbresult *res = NULL;
        xmlChar tmp[2050];
        int i;

        DEBUG(ctx, 21, "Function call: certificates_list(ctx, fieldMap, '%s')", sortkeys);
        assert( (ctx != NULL) && (srch_map != NULL) );

        // Replace spaces with underscore in common name and
        // in organisation fields, to comply with OpenVPN standards
        for( ptr = srch_map; ptr != NULL; ptr = ptr->next ) {
                if( ptr->field_id & (FIELD_CNAME | FIELD_ORG) ) {
                        xmlReplaceChars((xmlChar *) ptr->value, ' ', '_');
                }
        }

        res = sqlite_query_mapped(ctx, SQL_SELECT,
                                  "SELECT depth, lower(digest), common_name, organisation, email, "
				  "       locdt(registered), certid"
                                  "  FROM openvpn_certificates", NULL, srch_map, sortkeys);
        if( sqlite_query_status(res) != dbSUCCESS ) {
                eurephia_log(ctx, LOG_ERROR, 0, "Could not query the certificate table");
                tmp_n = sqlite_log_error_xml(ctx, res);
                certlist = eurephiaXML_ResultMsg(ctx, exmlERROR, tmp_n,
					     "Could not query the database for certificate info");
                xmlFreeNode(tmp_n);
                goto exit;
        }

        memset(&tmp, 0, 2050);
        eurephiaXML_CreateDoc(ctx, 1, "certificates", &certlist, &cert_n);
        xmlStrPrintf(tmp, 64, "%ld", sqlite_get_numtuples(res));
        xmlNewProp(cert_n, (xmlChar *) "certificates", (xmlChar *) tmp);

        for( i = 0; i < sqlite_get_numtuples(res); i++ ) {
                tmp_n = xmlNewChild(cert_n, NULL, (xmlChar *) "certificate", NULL);

                sqlite_xml_value(tmp_n, XML_ATTR, "certid", res, i, 6);
                sqlite_xml_value(tmp_n, XML_ATTR, "depth", res, i, 0);
                sqlite_xml_value(tmp_n, XML_ATTR, "registered", res, i, 5);
                sqlite_xml_value(tmp_n, XML_NODE, "digest", res, i, 1);

                xmlStrPrintf(tmp, 2048, "%.2048s", sqlite_get_value(res, i, 2));
                xmlReplaceChars(tmp, '_', ' ');
                xmlNewChild(tmp_n, NULL, (xmlChar *) "common_name", tmp);

                xmlStrPrintf(tmp, 2048, "%.2048s", sqlite_get_value(res, i, 3));
                xmlReplaceChars(tmp, '_', ' ');
                xmlNewChild(tmp_n, NULL, (xmlChar *) "organisation", tmp);

                sqlite_xml_value(tmp_n, XML_NODE, "email", res, i, 4);
        }

 exit:
        sqlite_free_results(res);
        return certlist;
}


/**
 * Internal function.  Registers a new certificate and saves it in the database
 *
 * @param ctx         eurephiaCTX
 * @param crtinf_map  eDBfieldMap containing certificate info to be registered
 *
 * @return Returns an eurephia ResultMsg XML document with a result string.  On fatal errors,
 *         NULL is returned
 */
static xmlDoc *certificate_add(eurephiaCTX *ctx, eDBfieldMap *crtinf_map) {
	xmlDoc *res_d = NULL;
        xmlNode *info_n = NULL;
        eDBfieldMap *ptr = NULL;
        dbresult *res = NULL;

        DEBUG(ctx, 21, "Function call: certificate_add(ctx, xmlDoc)");
        assert( (ctx != NULL) && (crtinf_map != NULL) );

        if( (ctx->context_type != ECTX_ADMIN_CONSOLE) && (ctx->context_type != ECTX_ADMIN_WEB) ) {
                eurephia_log(ctx, LOG_CRITICAL, 0,
                             "eurephia admin function call attempted with wrong context type");
                return NULL;
        }

        // Replace spaces with underscore in common name and
        // in organisation fields, to comply with OpenVPN standards
        for( ptr = crtinf_map; ptr != NULL; ptr = ptr->next ) {
                if( ptr->field_id & (FIELD_CNAME | FIELD_ORG) ) {
                        xmlReplaceChars((xmlChar *) ptr->value, ' ', '_');
                }
        }

        // Register the certificate
        res = sqlite_query_mapped(ctx, SQL_INSERT, "INSERT INTO openvpn_certificates",
                                  crtinf_map, NULL, NULL);
        if( sqlite_query_status(res) != dbSUCCESS ) {
                eurephia_log(ctx, LOG_FATAL, 0, "Could not register the certificate");
                info_n = sqlite_log_error_xml(ctx, res);
                res_d = eurephiaXML_ResultMsg(ctx, exmlERROR, info_n, "Could not register the certificate");
                xmlFreeNode(info_n);
        } else {
                xmlChar *certid = malloc_nullsafe(ctx, 34);
                assert( certid != NULL );

                // Prepare an information tag/node with the certid value
                xmlStrPrintf(certid, 32, "%lld", res->last_insert_id);
                info_n = xmlNewNode(NULL, (xmlChar *)"certificate");
                xmlNewProp(info_n, (xmlChar *) "certid", certid);

                res_d = eurephiaXML_ResultMsg(ctx, exmlRESULT, info_n,
                                              "Certificate registered (certid %ld)", res->last_insert_id);
                free_nullsafe(ctx, certid);
                xmlFreeNode(info_n);
        }
        sqlite_free_results(res);

        return res_d;
}


/**
 * Internal function.  Deletes one or more certificates from the database
 *
 * @param ctx         eurephiaCTX
 * @param crtinf_map  eDBfieldMap with information about certificate(s) to delete
 *
 * @return Returns an eurephia ResultMsg XML document with a result string.  On fatal errors,
 *         NULL is returned
 */
static xmlDoc *certificate_delete(eurephiaCTX *ctx, eDBfieldMap *crtinf_map) {
        xmlDoc *res_d = NULL;
        eDBfieldMap *ptr = NULL;
        dbresult *res = NULL;

        DEBUG(ctx, 21, "Function call: certificate_delete(ctx, xmlDoc)");
        assert( (ctx != NULL) && (crtinf_map != NULL) );

        if( (ctx->context_type != ECTX_ADMIN_CONSOLE) && (ctx->context_type != ECTX_ADMIN_WEB) ) {
                eurephia_log(ctx, LOG_CRITICAL, 0,
                             "eurephia admin function call attempted with wrong context type");
                return NULL;
        }

        // Replace spaces with underscore in common name and
        // in organisation fields, to comply with OpenVPN standards
        for( ptr = crtinf_map; ptr != NULL; ptr = ptr->next ) {
                if( ptr->field_id & (FIELD_CNAME | FIELD_ORG) ) {
                        xmlReplaceChars((xmlChar *) ptr->value, ' ', '_');
                }
        }

        // Register the certificate
        res = sqlite_query_mapped(ctx, SQL_DELETE, "DELETE FROM openvpn_certificates",
                                  NULL, crtinf_map, NULL);
        if( sqlite_query_status(res) != dbSUCCESS ) {
                xmlNode *err_n = NULL;

                eurephia_log(ctx, LOG_FATAL, 0, "Could not complete the delete certificate request");
                err_n = sqlite_log_error_xml(ctx, res);
                res_d = eurephiaXML_ResultMsg(ctx, exmlERROR, err_n, "Could not delete the certificate(s)");
                xmlFreeNode(err_n);
        } else {
                res_d = eurephiaXML_ResultMsg(ctx, exmlRESULT, NULL, "%i %s deleted",
                                              sqlite_get_affected_rows(res),
                                              (sqlite_get_affected_rows(res) != 1
                                               ? "certificates":"certificate")
                                              );
        }
        sqlite_free_results(res);

        return res_d;
}

/**
 * @copydoc eDBadminCertificate()
 */
xmlDoc *eDBadminCertificate(eurephiaCTX *ctx, xmlDoc *qryxml) {
        eDBfieldMap *fmap = NULL;
        char *mode = NULL;
        xmlDoc *resxml = NULL;
        xmlNode *root_n = NULL, *fieldmap_n = NULL;

        DEBUG(ctx, 20, "Function call: eDBadminCertificate(ctx, {xmlDoc})");
        assert( (ctx != NULL) && (qryxml != NULL) );

        if( (ctx->context_type != ECTX_ADMIN_CONSOLE) && (ctx->context_type != ECTX_ADMIN_WEB) ) {
                eurephia_log(ctx, LOG_CRITICAL, 0,
                             "eurephia admin function call attempted with wrong context type");
                return NULL;
        }

        root_n = eurephiaXML_getRoot(ctx, qryxml, "certificates", 1);
        if( root_n == NULL ) {
                eurephia_log(ctx, LOG_CRITICAL, 0, "Invalid XML input.");
                return NULL;
        }
        mode = xmlGetAttrValue(root_n->properties, "mode");
        if( mode == NULL ) {
                eurephia_log(ctx, LOG_ERROR, 0, "Missing mode attribute");
                return NULL;
        }

        fieldmap_n = xmlFindNode(root_n, "fieldMapping");
        if( fieldmap_n == NULL ) {
                eurephia_log(ctx, LOG_ERROR, 0, "Missing fieldMapping");
                return NULL;
        }
        fmap = eDBxmlMapping(ctx, tbl_sqlite_certs, NULL, fieldmap_n);

        if( strcmp(mode, "list") == 0 ) {
                char *sortkeys = xmlGetNodeContent(root_n, "sortkeys");
                resxml = certificate_list(ctx, fmap, eDBmkSortKeyString(fmap, sortkeys));
        } else if( strcmp(mode, "register") == 0 ) {
                resxml = certificate_add(ctx, fmap);
        } else if( strcmp(mode, "delete") == 0 ) {
                resxml = certificate_delete(ctx, fmap);
        } else {
                eurephia_log(ctx, LOG_ERROR, 0, "Certificates - Unknown mode: '%s'", mode);
                resxml = eurephiaXML_ResultMsg(ctx, exmlERROR, NULL, "Unknown mode '%s'", mode);
        }
        eDBfreeMapping(fmap);
        return resxml;
}
