/* firewalladmin.c  --  Functions for managing firewall profiles
 *
 *  GPLv2 only - Copyright (C) 2009 - 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/firewalladmin.c
 * @author David Sommerseth <dazo@users.sourceforge.net>
 * @date   2009-03-28
 *
 * @brief  Functions for managing firewall profiles
 *
 */

#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>

#include "../sqlite.h"

#define FMAP_OVPNACCESSES
#include "../fieldmapping.h"


/**
 * Internal function.  Queries the database for a list of user-certificate links
 *
 * @param ctx      eurephiaCTX
 * @param fmap     eDBfieldMap containing the search criteria
 *
 * @return Returns a valid eurephia XML document on success, otherwise NULL
 */
xmlDoc *fwadmin_search(eurephiaCTX *ctx, eDBfieldMap *fmap) {
        dbresult *res = NULL;
        xmlDoc *doc = NULL;
        xmlNode *root_n = NULL, *rec_n = NULL, *acg_n = NULL, *acc_n = NULL, *tmp_n = NULL;
        eDBfieldMap *fptr = NULL;
        int last_acp = -1, i = 0;

        // Add table alias on the certid, to avoid SQL error
        for( fptr = fmap; fptr != NULL; fptr = fptr->next) {
                switch( fptr->field_id ) {
                case FIELD_CERTID:
                        fptr->table_alias = strdup("c");
                default:
                        break;
                }
        }

        // Query the database for accesses
        res = sqlite_query_mapped(ctx, SQL_SELECT,
                                  "SELECT access_descr, fw_profile, accessprofile, "
                                  "       uid, username, "
                                  "       uac.certid, common_name, organisation, "
                                  "       email, lower(digest), locdt(c.registered), uicid "
                                  "  FROM openvpn_accesses"
                                  "  LEFT JOIN openvpn_usercerts uac USING (accessprofile)"
                                  "  LEFT JOIN openvpn_users USING (uid)"
                                  "  LEFT JOIN openvpn_certificates c ON (uac.certid = c.certid)",
                                  NULL, fmap, "accessprofile, uid, c.certid");
        if( sqlite_query_status(res) != dbSUCCESS ) {
                eurephia_log(ctx, LOG_ERROR, 0, "Error querying the database for firewall profiles");
                sqlite_log_error(ctx, res);
                sqlite_free_results(res);
                return NULL;
        }

        eurephiaXML_CreateDoc(ctx, 1, "firewall_profiles", &doc, &root_n);
        xmlNewProp(root_n, (xmlChar *) "mode", (xmlChar *) "profiles");

        for( i = 0; i < sqlite_get_numtuples(res); i++ ) {
                xmlChar *tmp = NULL;
                if( last_acp != atoi_nullsafe(sqlite_get_value(res, i, 2)) ) {
                        // Create a new block element when we get a new uid
                        rec_n = xmlNewChild(root_n, NULL, (xmlChar *) "profile", NULL);
                        sqlite_xml_value(rec_n, XML_ATTR, "accessprofile", res, i, 2);
                        sqlite_xml_value(rec_n, XML_NODE, "description", res, i, 0);
                        sqlite_xml_value(rec_n, XML_NODE, "firewall_destination", res, i, 1);
                        acg_n = xmlNewChild(rec_n, NULL, (xmlChar *) "granted_accesses", NULL);

                        last_acp = atoi_nullsafe(sqlite_get_value(res, i, 2));
                }

                // Only continue populating acc_n tags if we have some access info available
                if( sqlite_get_value(res, i, 11) == NULL ) {
                        continue;
                }

                acc_n = xmlNewChild(acg_n, NULL, (xmlChar *) "access", NULL);
                sqlite_xml_value(acc_n, XML_ATTR, "uicid", res, i, 11);
                tmp_n = sqlite_xml_value(acc_n, XML_NODE, "username", res, i, 4);
                sqlite_xml_value(tmp_n, XML_ATTR, "uid", res, i, 3);

                tmp_n = xmlNewChild(acc_n, NULL, (xmlChar *) "certificate", NULL);
                // Only populate tags with certificate info if we have certificate info available
                if( sqlite_xml_value(tmp_n, XML_ATTR, "certid", res, i, 5) ) {
                        sqlite_xml_value(tmp_n, XML_ATTR, "registered", res, i, 10);

                        // OpenVPN uses underscore as default value for "unsafe" characters
                        // in X509 fields.  Replace with space for better readability.
                        tmp = (xmlChar *)sqlite_get_value(res, i, 6);
                        xmlReplaceChars(tmp, '_', ' ');
                        xmlNewChild(tmp_n, NULL, (xmlChar *) "common_name", tmp);

                        tmp = (xmlChar *)sqlite_get_value(res, i, 7);
                        xmlReplaceChars(tmp, '_', ' ');
                        xmlNewChild(tmp_n, NULL, (xmlChar *) "organisation", tmp);

                        sqlite_xml_value(tmp_n, XML_NODE, "email", res, i, 8);
                        sqlite_xml_value(tmp_n, XML_NODE, "digest", res, i, 9);
                }
        }
        sqlite_free_results(res);
        return doc;
}


/**
 * Internal function.  Registers a new firewall profile
 *
 * @param ctx   eurephiaCTX
 * @param fmap  eDBfieldMap containing information about the new profile
 *
 * @return Returns an eurephia ResultMsg XML document, with success message or an error message
 */
xmlDoc *fwadmin_add(eurephiaCTX *ctx, eDBfieldMap *fmap) {
        dbresult *res = NULL;
        xmlDoc *ret = NULL;

        // Check if we have the needed fields, and only the needed fields
        if( eDBmappingFieldsPresent(fmap) != (FIELD_DESCR | FIELD_FWPROFILE) ) {
                return eurephiaXML_ResultMsg(ctx, exmlERROR, NULL,
                                             "Adding firewall profile only accepts "
                                             "description and firewall profile fields");
        }

        res = sqlite_query_mapped(ctx, SQL_INSERT, "INSERT INTO openvpn_accesses", fmap, NULL, NULL);
        if( sqlite_query_status(res) != dbSUCCESS ) {
                xmlNode *err_n = NULL;

                eurephia_log(ctx, LOG_FATAL, 0, "Could not register the new firewall profile");
                err_n = sqlite_log_error_xml(ctx, res);
                ret = eurephiaXML_ResultMsg(ctx, exmlERROR, err_n,
                                            "Could not register the new firewall profile");
                xmlFreeNode(err_n);
        } else {
                ret = eurephiaXML_ResultMsg(ctx, exmlRESULT, NULL,
                                            "Firewall profile registered with id %i",
                                            res->last_insert_id);
        }
        sqlite_free_results(res);
        return ret;
}


/**
 * Internal function.  Deletes firewall profile(s) from the database
 *
 * @param ctx   eurephiaCTX
 * @param fmap  eDBfieldMap containing information about the profile(s) to be deleted
 *
 * @return Returns an eurephia ResultMsg XML document, with success message or an error message
 */
xmlDoc *fwadmin_delete(eurephiaCTX *ctx, eDBfieldMap *fmap) {
        dbresult *res = NULL;
        xmlDoc *ret = NULL;
        xmlNode *err_n = NULL;
        long int fields;

        // Check if we have the needed fields, and only the needed fields
        fields = eDBmappingFieldsPresent(fmap);
        if( !(fields & FIELD_FWPROFILE) && !(fields & FIELD_RECID) ) {
                return eurephiaXML_ResultMsg(ctx, exmlERROR, NULL,
                                             "Deleting firewall profile only accepts "
                                             "firewall profile and destination fields");
        }

        // Find the accessprofile ID based on the fieldmap
        res = sqlite_query_mapped(ctx, SQL_SELECT, "SELECT DISTINCT accessprofile FROM openvpn_accesses",
                                  NULL, fmap, NULL);
        if( sqlite_query_status(res) != dbSUCCESS ) {
                eurephia_log(ctx, LOG_FATAL, 0, "Could not delete the firewall profile (1)");
                err_n = sqlite_log_error_xml(ctx, res);
                ret = eurephiaXML_ResultMsg(ctx, exmlERROR, err_n, "Could not delete the firewall profile");
                xmlFreeNode(err_n);
                goto exit;
        }

        // Delete all references to this access profile in openvpn_usercerts
        if( sqlite_get_numtuples(res) > 0 ) {
                dbresult *dres = NULL;
                int i = 0;

                for( i = 0; i < sqlite_get_numtuples(res); i++ ) {
                        dres = sqlite_query(ctx,
                                            "DELETE FROM openvpn_usercerts "
                                            "      WHERE accessprofile = %q",
                                            sqlite_get_value(res, i, 0));
                        if( sqlite_query_status(dres) != dbSUCCESS ) {
                                eurephia_log(ctx, LOG_FATAL, 0, "Could not delete the firewall profile (2)");
                                err_n = sqlite_log_error_xml(ctx, res);
                                ret = eurephiaXML_ResultMsg(ctx, exmlERROR, err_n,
                                                            "Could not delete the firewall profile");
                                sqlite_free_results(dres);
                                xmlFreeNode(err_n);
                                goto exit;
                        }
                        sqlite_free_results(dres);
                }
        }

        // Delete requested access profiles from openvpn_accesses
        res = sqlite_query_mapped(ctx, SQL_DELETE, "DELETE FROM openvpn_accesses", NULL, fmap, NULL);
        if( sqlite_query_status(res) != dbSUCCESS ) {
                eurephia_log(ctx, LOG_FATAL, 0, "Could not delete the firewall profile");
                err_n = sqlite_log_error_xml(ctx, res);
                ret = eurephiaXML_ResultMsg(ctx, exmlERROR, err_n, "Could not delete the firewall profile");
                xmlFreeNode(err_n);
        } else {
                ret = eurephiaXML_ResultMsg(ctx, exmlRESULT, NULL, "Firewall profile deleted");
        }
 exit:
        sqlite_free_results(res);
        return ret;
}


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

        DEBUG(ctx, 20, "Function call: eDBadminFirewallProfiles(ctx, {xmlDoc})");
        assert( (ctx != NULL) && (xmlqry != 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, xmlqry, "firewall_profiles", 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");
        }
        fmap = eDBxmlMapping(ctx, tbl_sqlite_openvpnaccesses, NULL, fieldmap_n);

        if( strcmp(mode, "search") == 0 ) {
                resxml = fwadmin_search(ctx, fmap);
        } else if( strcmp(mode, "add") == 0 ) {
                resxml = fwadmin_add(ctx, fmap);
        } else if( strcmp(mode, "delete") == 0 ) {
                resxml = fwadmin_delete(ctx, fmap);
        } else {
                eurephia_log(ctx, LOG_ERROR, 0, "FirewallProfiles - Unknown mode: '%s'", mode);
                resxml = eurephiaXML_ResultMsg(ctx, exmlERROR, NULL, "Unknown mode '%s'", mode);
        }
        eDBfreeMapping(fmap);
        return resxml;
}
