/* eurephia_init.c  --  program which initialises the eurephia database.
 *                      It will add a administrator account, setting
 *                      a password for it and set other needed
 *                      configuration settings.
 *
 *  GPLv2 only - Copyright (C) 2009 - 2019
 *               David Sommerseth <dazo@eurephia.org>
 *
 *  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   eurephia_init.c
 * @author David Sommerseth <dazo@users.sourceforge.net>
 * @date   2009-03-22
 *
 * @brief  A little console based wizard helping configuring new eurephia databases.
 *
 */

#include <stdio.h>
#include <string.h>
#include <libgen.h>
#include <assert.h>

#ifdef HAVE_LIBXML2
#include <libxml/tree.h>
#include <libxml/xpath.h>
#include <libxml/xmlstring.h>
#endif

#include <eurephia_nullsafe.h>
#include <eurephia_context.h>
#include <eurephiadb.h>
#include <eurephiadb_driver.h>
#include <eurephia_values.h>
#include <eurephia_xml.h>

#define MODULE "eurephia_init"  /**< Set the module name to eurephia_init */
#include <client_context.h>
#include <client_config.h>
#include <argparser.h>
#include <get_console_input.h>


// Found in benchmark.c
int benchmark(int *min, int *max, int thr_min, int thr_max);


/**
 * Prints program version information to stdout
 *
 * @param fprg String containing full path to this binary (argv[0])
 *
 * @return Returns the basename value of the binary
 */
char *print_version(char *fprg) {
        char *prg = basename(fprg);

        fprintf(stdout, "%s (v%s) - eurephia initialisation utility\n"
                "Copyright (C) 2009-2012  David Sommerseth <dazo@users.sourceforge.net>\n",
                prg, EUREPHIAVERSION);
        return prg;
}


/**
 * Prints a help screen for all arguments eurephia_init can process
 *
 * @param fprg String containing full path to this binary (argv[0])
 */
void print_help(char *fprg) {
        print_version(fprg);

        printf("\n  This utility is only supposed to be used when installing eurephia.  The\n"
               "  purpose is to initialise the database eurephia will use, by setting up\n"
               "  some standard configuration values and create an administrator account\n\n");

        printf("  Valid arguments:\n"
               "    -V | --version                 : Show version information\n"
               "    -h | --help                    : This help screen\n"
               "    -l | --log <filename>          : Log file for debugging\n"
               "    -L | --log-level <log level>   : Sets the log level\n"
               "    -N | --hash-threshold-min <ms> : Benchmarking parameter, see below. Default 95ms\n"
               "    -M | --hash-threshold-max <ms> : Benchmarking parameter, see below. Default 200ms\n"
               "    -D | --database-driver <path>  : Full path to the database driver\n"
               "    -d | --database-args <args>    : Required database arguments for the driver\n\n");

        printf("* Benchmarking\n"
               "  During the initialisation this utility will benchmark the CPU power by doing\n"
               "  multiple SHA512 hash calculations.  This is to determinate the optimal rounds\n"
               "  the password hashes should use on the current computer.  To make it more difficult\n"
               "  to brute force passwords, eurephia implements dynamic password hash rounds, based\n"
               "  on a random number within in a given range.  This benchmark will suggest an optimal\n"
               "  range.  To do this, two limits are defined, the shortest time and the longest time\n"
               "  to be used for calculating a hash.  The default values are 95ms and 200ms.\n"
               "\n"
               "  If you want to modify those thresholds, you can do so with the --hash-threshold-min\n"
               "  and --hash-threshold-max options.  By increasing these numbers, you will allow the\n"
               "  number of rounds to be increased.\n\n");
}

/**
 * Establishes a connection to the defined database.  The eurephiaCTX will contain the database connection.
 *
 * @param ctx  eurephiaCTX
 * @param cfg  eurephiaVALUES stack containing the database parameters
 *
 * @return Returns 1 on success, otherwise 0.
 */
int eurephia_ConnectDB(eurephiaCTX *ctx, eurephiaVALUES *cfg) {
        char *delims = " ";
        char *cp = NULL;
        const char *dbargv[MAX_ARGUMENTS];
        int dbargc = 0;
        char *argstr = NULL;

        argstr = eGet_value(cfg, "database_params");
        if( (argstr == NULL) || (strlen(argstr) < 1) ) {
                eurephia_log(ctx, LOG_FATAL, 0, "No database connection string given");
                return 0;
        }

        // Split up argstr into separate arguments
        cp = strdup(argstr);
        assert(cp != NULL);

        dbargc = 0;
        dbargv[dbargc] = strtok(cp, delims);
        while( (dbargv[dbargc] != NULL) && (dbargc <= MAX_ARGUMENTS) ) {
                dbargv[++dbargc] = strtok(NULL, delims);
        }

        if( !eDBconnect(ctx, dbargc, dbargv) ) {
                eurephia_log(ctx, LOG_PANIC, 0, "Could not connect to the database");
                eDBlink_close(ctx);
                return 0;
        }
        free_nullsafe(ctx, cp);

        return 1;
}


/**
 * Internal eurephia_init function.  Sets a configuration parameter in the database.
 *
 * @param ctx eurephiaCTX
 * @param key String containing the key name
 * @param val String containing the value of the key
 *
 * @return Returns 1 on success, otherwise 0.
 */
static int config_set(eurephiaCTX *ctx, const char *key, const char *val) {
        xmlDoc *cfgxml = NULL, *resxml = NULL;
        xmlNode *cfg_n = NULL;
        eurephiaRESULT *res = NULL;
        int ret = 0;

        eurephiaXML_CreateDoc(ctx, 1, "configuration", &cfgxml, &cfg_n);
        assert( (cfgxml != NULL) && (cfg_n != NULL) );

        cfg_n = xmlNewChild(cfg_n, NULL, (xmlChar *) "set", (xmlChar *) val);
        xmlNewProp(cfg_n, (xmlChar *) "key", (xmlChar *) key);
        resxml = eDBadminConfiguration(ctx, cfgxml);
        xmlFreeDoc(cfgxml);
        if( resxml == NULL ) {
                fprintf(stderr, "%s: Error updating the configuration.\n", MODULE);
                return 0;
        }

        res = eurephiaXML_ParseResultMsg(ctx, resxml);
        if( res == NULL ) {
                fprintf(stderr, "%s: Error updating the configuration. No results returned.\n",
                        MODULE);
                xmlFreeDoc(resxml);
                return 0;
        }

        if( res->resultType == exmlERROR ) {
                fprintf(stderr, "%s: %s\n", MODULE, res->message);
                ret = 0;
        } else {
                ret = 1;
        }
        free_nullsafe(ctx, res);
        xmlFreeDoc(resxml);
        return ret;
}

/**
 * Generate a suggestion for a ~/.eurephia/eurephiadm.cfg file
 *
 * @param fp   A file pointer where to write this suggestion
 * @param cfg  eurephiaVALUES point to the configuration given via the command line.
 *
 */
static void write_eurephiadmcfg_file(FILE *fp, eurephiaVALUES *cfg) {
        fprintf(fp, "# Database settings\n");
        fprintf(fp, "database_driver = %s\n", eGet_value(cfg, "database_driver"));
        fprintf(fp, "database_params = %s\n", eGet_value(cfg, "database_params"));
        fprintf(fp, "\n# Disabling logging\n");
        fprintf(fp, "log_level = 0\n");
        fprintf(fp, "log = none:\n");
}
/**
 * Guides the user through setting up the first eurephia administrator account.
 *
 * @param ctx eurephiaCTX
 *
 * @return Returns 1 on success, otherwise 0.
 */
int setup_admin_account(eurephiaCTX *ctx) {
        xmlDoc *xmldoc = NULL, *resxml = NULL;
        xmlNode *node = NULL, *node2 = NULL;
        int uid = 0, i;
        char uname[66], pwd1[66], pwd2[66];

        printf("------------------------------------------------------------------------------\n");
        printf("  eurephia :: ADMINISTRATOR ACCOUNT\n");
        printf("------------------------------------------------------------------------------\n\n");
        printf("Checking database for user accounts ... ");

        eurephiaXML_CreateDoc(ctx, 1, "UserAccount", &xmldoc, &node);
        assert( (xmldoc != NULL) && (node != NULL) );
        xmlNewProp(node, (xmlChar *) "mode", (xmlChar *) "view");

        node = xmlNewChild(node, NULL, (xmlChar *) "fieldMapping", NULL);
        assert( node != NULL );
        xmlNewProp(node, (xmlChar *) "table", (xmlChar *) "users");

        resxml = eDBadminUserAccount(ctx, xmldoc);
        node = eurephiaXML_getRoot(ctx, resxml, "UserAccount", 1);
        if( node == NULL ) {
                fprintf(stderr, "Could not retrieve valid data\n");
                xmlFreeDoc(xmldoc);
                xmlFreeDoc(resxml);
                return 0;
        }

        node = xmlFindNode(node, "Account");
        if( (node != NULL) ) {
                printf("User accounts found, aborting.  eurephia is already initialised\n");
                xmlFreeDoc(xmldoc);
                xmlFreeDoc(resxml);
                return 0;
        }
        xmlFreeDoc(xmldoc); xmldoc = NULL;
        xmlFreeDoc(resxml); resxml = NULL;
        printf("None found. Good!\n");

        get_console_input(uname, 64, "Admin username:   ", 0);
        if( strlen_nullsafe(uname) < 4 ) {
                fprintf(stderr, "Username is too short.  Minimum 4 characters\n");
                return 0;
        }
        get_console_input(pwd1, 64, "Password:         ", 1);
        if( strlen_nullsafe(pwd1) < 5 ) {
                fprintf(stderr, "Password is too short\n");
                return 0;
        }
        get_console_input(pwd2, 64, "Confirm password: ", 1);
        if( strcmp(pwd1, pwd2) != 0 ) {
                fprintf(stderr, "Passwords do not match\n");
                return 0;
        }
        memset(pwd2, 0, 66);

        eurephiaXML_CreateDoc(ctx, 1, "UserAccount", &xmldoc, &node);
        xmlNewProp(node, (xmlChar *) "mode", (xmlChar *) "add");
        node = xmlNewChild(node, NULL, (xmlChar *) "fieldMapping", NULL);
        xmlNewProp(node, (xmlChar *) "table", (xmlChar *) "users");

        xmlNewChild(node, NULL, (xmlChar *) "username", (xmlChar *) uname);
        node2 = xmlNewChild(node, NULL, (xmlChar *) "password", (xmlChar *) pwd1);
        xmlNewProp(node2, (xmlChar *) "pwhash", (xmlChar *) "none");

        // Add the user
        resxml = eDBadminUserAccount(ctx, xmldoc);
        memset(pwd1, 0, 66);
        xmlFreeDoc(xmldoc);

        if( !eurephiaXML_IsResultMsg(ctx, resxml) ) {
                fprintf(stderr, "Failed to register user\n");
                if( resxml ) {
                        xmlFreeDoc(resxml);
                }
                return 0;
        } else {
                eurephiaRESULT *res = NULL;

                res = eurephiaXML_ParseResultMsg(ctx, resxml);
                switch( res->resultType ) {
                case exmlERROR:
                        fprintf(stderr, "** ERROR ** %s\n", res->message);
                        uid = 0;
                        break;

                case exmlRESULT:
                        fprintf(stdout, "%s\n", res->message);
                        node = xmlFindNode(res->details, "UserAccount");
                        uid = atoi_nullsafe(xmlGetAttrValue(node->properties, "uid"));
                        break;
                }
                xmlFreeDoc(resxml);
                free_nullsafe(ctx, res);
                
                if( uid < 1 ) {
                        fprintf(stderr, "Failed to register user\n");
                        return 0;
                }
        }

        // Grant all available access levels to the admin account
        static char *grants[] = { "config", "useradmin", "certadmin", "fwprofiles",
                                  "attempts", "blacklist", NULL };

        printf("Granting access to user account:");

        eurephiaXML_CreateDoc(ctx, 1, "admin_access", &xmldoc, &node);
        xmlNewProp(node, (xmlChar *) "mode", (xmlChar *) "grant");

        node = xmlNewChild(node, NULL, (xmlChar *) "fieldMapping", NULL);
        xmlNewProp(node, (xmlChar *) "table", (xmlChar *) "eurephia_adminaccess");

        snprintf(uname, 64, "%i", uid); // borrow uname variable for uid int -> string
        xmlNewChild(node, NULL, (xmlChar *) "uid", (xmlChar *) uname);
        xmlNewChild(node, NULL, (xmlChar *) "interface", (xmlChar *) "C");
        node2 = xmlNewChild(node, NULL, (xmlChar *) "accesslevel", (xmlChar *)"");

        for( i = 0; grants[i] != NULL; i++ ) {
                xmlNode *new_n = NULL;

                new_n = xmlNewChild(node, NULL, (xmlChar *) "accesslevel", (xmlChar *)grants[i]);
                xmlReplaceNode(node2, new_n);
                node2 = new_n;
                printf(" %s", grants[i]);
                if( !eDBadminAccessLevel(ctx, xmldoc) ) {
                        fprintf(stderr, "\n** ERROR: Could not grant %s access.  Initialisation failed\n", 
                                grants[i]);
                        xmlFreeDoc(xmldoc);
                        return 0;
                }
        }
        xmlFreeDoc(xmldoc);
        node = NULL; node2 = NULL;
        printf("\n");


        // Activate the user account
        printf("Activating the user account ... ");
        eurephiaXML_CreateDoc(ctx, 1, "UserAccount", &xmldoc, &node);
        xmlNewProp(node, (xmlChar *) "mode", (xmlChar *) "update");
        xmlNewProp(node, (xmlChar *) "uid", (xmlChar *) uname); // uid should still be in uname as string

        // Add fieldMapping - to correctly map eurephia fields into the database fields
        node2 = xmlNewChild(node, NULL, (xmlChar *) "fieldMapping", NULL);
        xmlNewProp(node2, (xmlChar *) "table", (xmlChar *) "users");
        xmlNewChild(node2, NULL, (xmlChar *) "activated", (xmlChar *) "CURRENT_TIMESTAMP");

        resxml = eDBadminUserAccount(ctx, xmldoc);
        if( !eurephiaXML_IsResultMsg(ctx, resxml) ) {
                printf("FAILED\n");
                xmlFreeDoc(xmldoc);
                xmlFreeDoc(resxml);
                return 0;
        } else {
                eurephiaRESULT *res = NULL;

                res = eurephiaXML_ParseResultMsg(ctx, resxml);
                switch( res->resultType ) {
                case exmlERROR:
                        fprintf(stderr, "** ERROR ** %s\n", res->message);
                        break;

                case exmlRESULT:
                        break;
                }
                xmlFreeDoc(resxml);
                free_nullsafe(ctx, res);
        }

        

        printf("Done");
        xmlFreeDoc(xmldoc);

        printf("\n==============================================================================\n\n");
        return 1;
}


/**
 *  Guides the user through setting up hashing algorithm parameters.  This function will try to find
 *  the ideal boundaries for hashing rounds, based on how quick the computer can calculate hashes.
 *
 * @param ctx eurephiaCTX
 * @param hash_thr_min Minimum time it should take to calculate a SHA512 hash (in ms)
 * @param hash_thr_max Maximum time it should take to calculate a SHA512 hash (in ms)
 *
 * @return Returns 1 on success, otherwise 0.
 */
int setup_password_params(eurephiaCTX *ctx,  const int hash_thr_min, const int hash_thr_max) {
        int rounds_min = 0, rounds_max = 0;
        char buffer[22], prompt[80], value[22];

        memset(&buffer, 0, 22);

        printf("------------------------------------------------------------------------------\n");
        printf("  eurephia :: PASSWORD PARAMETERS\n");
        printf("------------------------------------------------------------------------------\n\n");

        get_console_input(buffer, 10, "Salt length for password hashes [32] ", 0);
        memset(&value, 0, 22);
        snprintf(value, 20, "%i", (atoi_nullsafe(buffer) > 0 ? atoi_nullsafe(buffer) : 32));
        if( !config_set(ctx, "passwordhash_salt_length", value) ) {
                fprintf(stderr, "Failed to set configuration settings in database\n");
                return 0;
        }

        printf("\n** Preparing for SHA512 performance benchmark.\n");
        printf("Aiming for minimum hashing calculation time: %ims\n", hash_thr_min);
        printf("Aiming for maximum hashing calculation time: %ims\n", hash_thr_max);
        if( !benchmark(&rounds_min, &rounds_max, hash_thr_min, hash_thr_max) ) {
                fprintf(stderr, "Failed to meassure SHA512 hash performance\n");
                return 0;
        }
        printf("\n");
        snprintf(prompt, 78, "Minimum hashing rounds: [%i] ", rounds_min);
        get_console_input(buffer, 20, prompt, 0);
        memset(&value, 0, 22);
        snprintf(value, 20, "%i", (atoi_nullsafe(buffer) > 1 ? atoi_nullsafe(buffer) : rounds_min));
        if( !config_set(ctx, "passwordhash_rounds_min", value) ) {
                fprintf(stderr, "Failed to set configuration settings in database\n");
                return 0;
        }

        snprintf(prompt, 78, "Maximum hashing rounds: [%i] ", rounds_max);
        get_console_input(buffer, 20, prompt, 0);
        memset(&value, 0, 22);
        snprintf(value, 20, "%i", (atoi_nullsafe(buffer) > 1 ? atoi_nullsafe(buffer) : rounds_max));
        if( !config_set(ctx, "passwordhash_rounds_max", value) ) {
                fprintf(stderr, "Failed to set configuration settings in database\n");
                return 0;
        }

        printf("\n==============================================================================\n\n");

        return 1;
}


/**
 * Guides the user through setting up attempts limits
 *
 * @param ctx eurephiaCTX
 *
 * @return Returns 1 on success, otherwise 0.
 */
int setup_attempt_limits(eurephiaCTX *ctx) {
        char buffer[22], value[22];
        memset(&buffer, 0, 22);

        printf("------------------------------------------------------------------------------\n");
        printf("  eurephia :: ATTEMPTS LIMITS\n");
        printf("------------------------------------------------------------------------------\n\n");
        printf("These parameters here will decide when eurephia should block access, based on\n"
               "how many registered failed attempts.  Normally, you should be strict regarding\n"
               "number of attempts on usernames, less strict on certificates and even less on\n"
               "IP addresses.  Remember that the user might connect via a proxy or a firewall\n"
               "with NAT enabled.\n\n");

        get_console_input(buffer, 10, "How many failed attempts will you allow per user name? [3]", 0);
        memset(&value, 0, 22);
        snprintf(value, 20, "%i", (atoi_nullsafe(buffer) > 0 ? atoi_nullsafe(buffer) : 3));
        if( !config_set(ctx, "allow_username_attempts", value) ) {
                fprintf(stderr, "Failed to set configuration settings in database\n");
                return 0;
        }

        get_console_input(buffer, 10, "How many failed attempts will you allow per certificate? [5]", 0);
        memset(&value, 0, 22);
        snprintf(value, 20, "%i", (atoi_nullsafe(buffer) > 0 ? atoi_nullsafe(buffer) : 5));
        if( !config_set(ctx, "allow_cert_attempts", value) ) {
                fprintf(stderr, "Failed to set configuration settings in database\n");
                return 0;
        }

        get_console_input(buffer, 10, "How many failed attempts will you allow per IP address? [10]", 0);
        memset(&value, 0, 22);
        snprintf(value, 20, "%i", (atoi_nullsafe(buffer) > 0 ? atoi_nullsafe(buffer) : 10));
        if( !config_set(ctx, "allow_ipaddr_attempts", value) ) {
                fprintf(stderr, "Failed to set configuration settings in database\n");
                return 0;
        }
        printf("\n==============================================================================\n\n");
        return 1;
}


/**
 * Guides the user through setting up session specific parameters, mainly for the admin utilities
 *
 * @param ctx eurephiaCTX
 * @param cfg Configuration arguments, given via the command line
 *
 * @return Returns 1 on success, otherwise 0.
 */
int setup_session_params(eurephiaCTX *ctx, eurephiaVALUES *cfg) {
        char buffer[22], value[22];
        char *fname = get_config_filename(NULL, "eurephiadm.cfg");
        memset(&buffer, 0, 22);

        printf("------------------------------------------------------------------------------\n");
        printf("  eurephia :: SESSION PARAMETERS\n");
        printf("------------------------------------------------------------------------------\n\n");

        get_console_input(buffer, 10,
                          "eurephiadmin: How many minutes before a session is auto logged out: [10]", 0);
        memset(&value, 0, 22);
        snprintf(value, 20, "%i", (atoi_nullsafe(buffer) > 0 ? atoi_nullsafe(buffer) : 10));
        if( !config_set(ctx, "eurephiadmin_autologout", value) ) {
                fprintf(stderr, "Failed to set configuration settings in database\n");
                return 0;
        }

        if( fname != NULL ) {
                printf("\nGenerating %s\n", fname);
                printf("---------------------------------------------------------------\n");
                write_eurephiadmcfg_file(stdout, cfg);
                printf("---------------------------------------------------------------\n");
                memset(&buffer, 0, 22);
                get_console_input(buffer, 8, "Do you want this to be written to disk? [Yes]", 0);
                if(  (buffer[0] == '\n') || (buffer[0] == 'y') || (buffer[0] == 'Y') ) {
                        FILE *f;

                        if( (f = fopen(fname, "w")) == NULL ) {
                                fprintf(stderr, "Failed to create %s\n", fname);
                                return 0;
                        }
                        write_eurephiadmcfg_file(f, cfg);
                        fclose(f);
                        printf("Wrote %s\n", fname);
                } else {
                        printf("Configuration file was not written to disk\n");
                }
        } else {
                printf("\n** ERROR ** Could not generate eurephiadm.cfg file due to file system restrictions.\n");
                printf("            Skipping this part.\n");
        }
        printf("\n==============================================================================\n\n");

        return 1;
}


#ifdef FW_IPTABLES
/**
 * Guides the user through setting up iptables firewall support, if enabled at compile time.
 *
 * @param ctx eurephiaCTX
 *
 * @return Returns 1 on success, otherwise 0.
 */
int setup_iptables(eurephiaCTX *ctx) {
        char buffer[1026], value[1026], prompt[2048];
        memset(&buffer, 0, 1026);
        memset(&value, 0, 1026);

        printf("------------------------------------------------------------------------------\n");
        printf("  eurephia :: FIREWALL SUPPORT - iptables\n");
        printf("------------------------------------------------------------------------------\n\n");

        get_console_input(buffer, 8, "Do you want to load the iptables module? [No]", 0);
        if( (strlen_nullsafe(buffer) > 0) && ((buffer[0] != 'y') && (buffer[0] != 'Y')) ) {
                printf("\niptables firewall support is not configured\n");
                goto ipt_done;
        }

        snprintf(value, 1024, "%s/efw-iptables.so", PLUGINDIR);
        snprintf(prompt, 2048, "\nFull path to the efw-iptables.so library:\n[%s]", value);
        get_console_input(buffer, 1024, prompt, 0);
        if( !config_set(ctx, "firewall_interface", (strlen_nullsafe(buffer) > 1 ? buffer : value)) ) {
                fprintf(stderr, "Failed to set configuration settings in database\n");
                return 0;
        }

        snprintf(value, 1024, "/sbin/iptables");
        snprintf(prompt, 2048, "\nFull path to the iptables command: [%s]", value);
        get_console_input(buffer, 1024, prompt, 0);
        if( !config_set(ctx, "firewall_command", (strlen_nullsafe(buffer) > 1 ? buffer : value)) ) {
                fprintf(stderr, "Failed to set configuration settings in database\n");
                return 0;
        }

        snprintf(value, 1024, "vpn_users");
        snprintf(prompt, 2048, "\nWhich iptables chain should eurephia use? [%s]", value);
        get_console_input(buffer, 1024, prompt, 0);
        if( !config_set(ctx, "firewall_destination", (strlen_nullsafe(buffer) > 1 ? buffer : value)) ) {
                fprintf(stderr, "Failed to set configuration settings in database\n");
                return 0;
        }

        get_console_input(buffer, 8, "\nDo you want you eurephia to block blacklisted IP addresses\n"
                          "in iptables too? [No]", 0);
        if( (strlen_nullsafe(buffer) < 1) || (buffer[0] == 'y') || (buffer[0] == 'Y') ) {
                snprintf(value, 1024, "vpn_blacklist");
                snprintf(prompt, 2048, "\nWhich iptables chain should eurephia use for"
                         "\nblacklisted IP addresses? [%s]", value);
                get_console_input(buffer, 1024, prompt, 0);
                if( !config_set(ctx, "firewall_blacklist_destination",
                                       (strlen_nullsafe(buffer) > 1 ? buffer : value)) ) {
                        fprintf(stderr, "Failed to set configuration settings in database\n");
                        return 0;
                }

                memset(&value, 0, 1024);
                snprintf(prompt, 2048, "\nWhich iptables chain should eurephia send blacklisted"
                         "\nIP addresses to (iptables '-j' argument) ? [DROP]");
                get_console_input(buffer, 1024, prompt, 0);
                if( (strlen_nullsafe(buffer) > 1)
                    && !config_set(ctx, "firewall_blacklist_send_to", buffer) ) {
                        fprintf(stderr, "Failed to set configuration settings in database\n");
                        return 0;
                }
        }
 ipt_done:
        printf("\n==============================================================================\n\n");

        return 1;
}
#endif


/**
 * Main function of eurephia_init
 *
 * @param argc
 * @param argv
 *
 * @return Returns 0 on success, otherwise a positive integer on failure.
 */
int main(int argc, char **argv) {
        // Default hash calculation thresholds for benchmarking
        int hash_thr_min = 95;   // 95ms
        int hash_thr_max = 200;  // 200ms
        int argi = 0;
        eurephiaVALUES *cfg = NULL;
        eurephiaCTX *ctx = NULL;
        int rc = 0;

        static e_options argopts[] = {
                {"--version", "-V", 0},
                {"--help", "-h", 0},
                {"--log", "-l", 1},
                {"--log-level", "-L", 1},
                {"--hash-threshold-min", "-N", 1},
                {"--hash-threshold-max", "-M", 1},
                {"--database-driver", "-D", 1},
                {"--database-args", "-d", 1},
                {NULL, NULL, 0}
        };


        cfg = eCreate_value_space(NULL, 30);
        for( argi = 1; argi < argc; argi++ ) {
                switch( eurephia_getopt(&argi, argc, argv, argopts) ) {
                case 'V':
                        print_version(argv[0]);
                        rc = 0;
                        goto exit;

                case 'h':
                        print_help(argv[0]);
                        rc = 0;
                        goto exit;

                case 'l':
                        eAdd_value(NULL, cfg, "log", optargs[0]);
                        break;

                case 'L':
                        eAdd_value(NULL, cfg, "log_level", optargs[0]);
                        break;

                case 'D':
                        eAdd_value(NULL, cfg, "database_driver", optargs[0]);
                        break;

                case 'd':
                        eAdd_value(NULL, cfg, "database_params", optargs[0]);
                        break;

                case 'N':
                        hash_thr_min = atoi_nullsafe(optargs[0]);
                        break;

                case 'M':
                        hash_thr_max = atoi_nullsafe(optargs[0]);
                        break;

                default:
                        rc = 1;
                        goto exit;
                }
        }

        if( eGet_value(cfg, "database_driver") == NULL ) {
                fprintf(stderr, "Missing required database driver (--database-driver)\n");
                rc = 2;
                goto exit;

        }

        if( eGet_value(cfg, "database_params") == NULL ) {
                fprintf(stderr, "Missing required database driver parameters (--database-args)\n");
                rc = 2;
                goto exit;

        }

        ctx = eurephiaCTX_init("eurephia_init", NULL, 0, cfg);
        if( ctx == NULL ) {
                fprintf(stderr, "Failed to initialise an eurephia context.\n");
                rc = 3;
                goto exit;
        }

        if( !eurephia_ConnectDB(ctx, cfg) ) {
                fprintf(stderr, "Failed to access the database.\n");
                rc = 4;
                goto exit;
        }

        if( !setup_password_params(ctx, hash_thr_min, hash_thr_max) ) {
                rc = 11;
                goto exit;
        }

        if( !setup_admin_account(ctx) ) {
                rc = 12;
                goto exit;
        }

        if( !setup_session_params(ctx, cfg) ) {
                rc = 13;
                goto exit;
        }

        if( !setup_attempt_limits(ctx) ) {
                rc = 14;
                goto exit;
        }

#ifdef FW_IPTABLES
        if( !setup_iptables(ctx) ){
                rc = 15;
                goto exit;
        }
#endif

        printf("\neurephia is now configured.  For further changes, please use the eurephiadm utility.\n\n");
 exit:
        eFree_values(ctx, cfg);
        eurephiaCTX_destroy(ctx);
        return rc;
}
