/*  eurephia-template.c --  Main driver for eurephia authentication plugin for OpenVPN
 *
 *
 *     // THIS IS A TEMPLATE FOR A DATABASE DRIVER - IT WILL NOT COMPILE  //
 *
 *     Go through the code and you will find examples for SQL queries and
 *     hints how to implement the database specific code
 *
 *     This template is based on the sqlite/eurephia-sqlite.c
 *
 *
 *
 *  GPLv2 only - Copyright (C) 2008  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.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>

#ifndef
#define DRIVER_MODE
#endif
#include <eurephiadb_driver.h>
#include <eurephia_nullsafe.h>
#include <eurephia_log.h>
#include <eurephia_values.h>
#include <eurephiadb_session.h>
#include <passwd.h>


#define DRIVERVERSION "1.0"
#ifndef DRIVERAPIVERSION
# define DRIVERAPIVERSION 1   // Set this value to 2 to support admin utilities
#endif


// Mapping table - mapping attempt types from .... to database table fields
typedef struct {
        char *colname;
        char *allow_cfg;
        char *descr;
} eDBattempt_types_t;
               

static const eDBattempt_types_t eDBattempt_types[] = {
        {NULL, NULL, NULL},
        {"remoteip\0", "allow_ipaddr_attempts\0", "IP Address\0"},
        {"digest\0", "allow_cert_attempts\0", "Certificate\0"},
        {"username\0", "allow_username_attempts\0", "Username\0"},
        {NULL, NULL, NULL}
};

/*
 *   Driver info
 */

const char *eDB_DriverVersion() {
        /* WORK TO DO --- UPDATE INFO */
        return "eurephiadb-template (v"DRIVERVERSION")  David Sommerseth 2008 (C) GPLv2";
}


int eDB_DriverAPIVersion() {
        return DRIVERAPIVERSION;
}


/*
 *   local functions
 */


// Function for simplifying update of openvpn_blacklist
void update_attempts(eurephiaCTX *ctx, const char *blid) {

        if( blid != NULL ) {
                /* WORK TO DO -- DO SQL:
                             "UPDATE openvpn_blacklist "
                             "   SET last_accessed = CURRENT_TIMESTAMP WHERE blid = %s", blid
                */
                if( /* SQL COMMAND FAILED */ ) {
                        eurephia_log(ctx, LOG_CRITICAL, 0,
                                     "Could not update openvpn_blacklist.last_accessed for blid=%s", blid);
                }
                /* FREE SQL RESULT */
        }
}

/*
 *   Public driver functions
 */

// Connect to the database ... connection is stored in the eurephiaCTX context
int eDBconnect(eurephiaCTX *ctx, const int argc, const char **argv)
{
        eDBconn *dbc = NULL;
        int rc;

        /* WORK TO DO -- Parse arguments 
         * 
         *    use what ever suitable approach to parse arguments sent to the database module.
         *
         *    The arguments being recieved is everything after the '--' (double dash)
         *    in the 'plugin' configuration line for OpenVPN.
         *
         *    plugin eurephia-auth.so "-i my_db_module.so -L 3 -- myparam1 myparam2 myparam3"
         *    
         *    In this example the following parameters will be available here:
         *
         *    argc = 3
         *    argv[0] = myparam1
         *    argv[1] = myparam2
         *    argv[2] = myparam3
         *
         *    You are free to use whatever parameter syntax you would like to use.
         */

        DEBUG(ctx, 20, "Function call: eDBconnect(ctx, %i, '...')", argc, dbame);

        // Connect to the database
        dbc = (eDBconn *) malloc_nullsafe(ctx, sizeof(eDBconn)+2);
        if( !dbc ) {
                return 0;
        }
        dbc->dbname = strdup(argv[1]);

        eurephia_log(ctx, LOG_INFO, 1, "Opening database '%s'", dbc->dbname);

        /* WORK TO DO -- Open a database connection, and save the handle in dbc->handle */

        if( /* IF CONNECTION FAILED */ ) {
                eurephia_log(ctx, LOG_PANIC, 0, "Could not open database '%s'", dbc->dbname);
                free_nullsafe(dbc->dbname);
                free_nullsafe(dbc);
                return 0;
        }

        dbc->config = NULL;
        ctx->dbc = dbc;

        // Load configuration parameters into memory
        eurephia_log(ctx, LOG_INFO, 1, "Reading config from database (openvpn_config)");

        /* WORK TO DO - DO SQL:
                        "SELECT datakey, dataval FROM openvpn_config"
        */
        if( /* IF WE GOT SOME RECORDS */ ) {
                int i = 0;
                eurephiaVALUES *cfg = NULL;
                
                cfg = eCreate_value_space(ctx, 11);
                if( cfg == NULL ) {
                        eurephia_log(ctx, LOG_FATAL, 0, "Could not allocate memory for config variables");
                        /* FREE SQL RESULT */
                        return 0;
                }

                for( /* LOOP THROUGH ALL RECORDS, AND ADD THEM TO THE cfg VARIABLE */ ) {
                        eAdd_value(ctx, cfg, 
                                   /* GET datakey FROM SQL RESULT */, 
                                   /* GET dataval FROM SQL RESULT */);
                }
                /* FREE SQL RESULT */
                ctx->dbc->config = cfg;
        } 
        return 1;
}

// Disconnect from the database
void eDBdisconnect(eurephiaCTX *ctx) 
{
        eDBconn *dbc = NULL;

        DEBUG(ctx, 20, "Function call: eDBdisconnect(ctx)");

        if( ctx->dbc == NULL ) {
                eurephia_log(ctx, LOG_WARNING, 0, "Database not open, cannot close database.");
                return;
        }

        dbc = ctx->dbc;
        eurephia_log(ctx, LOG_INFO, 1, "Closing database '%s'", dbc->dbname);

        /* WORK TO DO:  Close database connection using dbc->dbhandle */


        free_nullsafe(dbc->dbname);
        dbc->dbhandle = NULL;

        // Free up config memory
        eFree_values(ctx, dbc->config);
        free_nullsafe(dbc);
        ctx->dbc = NULL;
}


// Authenticate certificate ... returns certid (certificate ID) on success, 
// 0 if not found or -1 if certificate is blacklisted
int eDBauth_TLS(eurephiaCTX *ctx, const char *org, const char *cname, const char *email, 
                const char *digest, const char *depth)
{
        int certid = 0;
        char *blid = NULL;

        DEBUG(ctx, 20, "Function call: eDBauth_TLS(ctx, '%s', '%s', '%s', '%s', %s)",
                     org, cname, email, digest, depth);

        // Check if certificate is valid, and not too many attempts has been tried with the given certificate
        /* WORK TO DO --  DO SQL:
                           "SELECT cert.certid, blid "
                           "  FROM openvpn_certificates cert"
                           "  LEFT JOIN openvpn_blacklist bl USING(digest)"
                           " WHERE organisation='%q' AND common_name='%q' "
                           "      AND email='%q' AND depth='%q' AND cert.digest='%q'%c",
                           org, cname, email, depth, digest
        */

        if( /* IF WE GOT A RESULT */ ) {
                certid = atoi_nullsafe(/* GET cert.certid FROM SQL RESULT */);
                blid   = atoi_nullsafe(/* GET blid FROM SQL RESULT */);
                /* FREE SQL RESULT */

                // Check if the certificate is blacklisted or not.  blid != NULL when blacklisted
                if( blid != NULL ) {
                        // If the certificate or IP is blacklisted, update status and deny access.
                        eurephia_log(ctx, LOG_WARNING, 0,
                                     "Attempt with BLACKLISTED certificate (certid %i)", certid);
                        update_attempts(ctx, blid);
                        certid = -1;
                }
                free_nullsafe(blid);
        } else {
                eurephia_log(ctx, LOG_FATAL, 0, "Could not look up certificate information");
        }

        DEBUG(ctx, 20, "Result function call: eDBauth_TLS(ctx, '%s', '%s', '%s', '%s', %s) - %i",
              org, cname, email, digest, depth, certid);

        return certid;
}

// Authenticate user, using username, password and certid as authentication parameters
// returns -1 if authentication fails.  Returns 0 if user account is not found.
int eDBauth_user(eurephiaCTX *ctx, const int certid, const char *username, const char *passwd) 
{
        char *crpwd = NULL, *activated = NULL, *deactivated = NULL, *blid_uname = NULL, *blid_cert;
        int uicid = 0, uid = 0, pwdok = 0;

        DEBUG(ctx, 20, "Function call: eDBauth_user(ctx, %i, '%s','xxxxxxxx')", certid, username);


        // Generate SHA512 hash of password, used for password auth
        crpwd = passwdhash(passwd);

        /* WORK TO DO -- DO SQL:
                           "SELECT uicid, ou.uid, activated, deactivated, bl1.blid, bl2.blid,  "
                           "       (password = '%s') AS pwdok"
                           "  FROM openvpn_users ou"
                           "  JOIN openvpn_usercerts uc USING(uid) "
                           "  LEFT JOIN openvpn_blacklist bl1 ON( ou.username = bl1.username) "
                           "  LEFT JOIN (SELECT blid, certid "
                           "               FROM openvpn_certificates "
                           "               JOIN openvpn_blacklist USING(digest)) bl2 ON(uc.certid = bl2.certid)"
                           " WHERE uc.certid = '%i' AND ou.username = '%q'",
                           crpwd, certid, username
        */
        free_nullsafe(crpwd);
        if( /* IF NO RESULT WAS RETURNED */ ) {
                eurephia_log(ctx, LOG_FATAL, 0,
                             "Could not look up user in database (certid %i, username '%s'", certid, username);
                return 0;
        }

        if( /* CHECK THAT WE ONLY GOT 1 RECORD*/ ) {
                uid         = atoi_nullsafe(/* RETRIEVE uid FROM SQL*/);
                activated   = ;/* GET activated FIELD FROM SQL RESULT */
                deactivated = ;/* GET deactivated FIELD FROM SQL RESULT */
                blid_uname  = ;/* GET bl1.blid FIELD FROM SQL RESULT */
                blid_cert   = ;/* GET bl2.blid FIELD FROM SQL RESULT */
                pwdok       = atoi_nullsafe(/* GET pwdok FIELD FROM SQL RESULT */);

                if( blid_uname != NULL ) {
                        eurephia_log(ctx, LOG_WARNING, 0,
                                     "User account is BLACKLISTED (uid: %i, %s)",
                                     uid, username);
                        uicid = -1;
                } else if( blid_cert != NULL ) {
                        eurephia_log(ctx, LOG_WARNING, 0,
                                     "User account linked with a BLACKLISTED certificate "
                                     "(uid: %i, %s) - certid: %s",
                                     uid, username, certid);
                        uicid = -1;
                } else if( activated == NULL ) {
                        eurephia_log(ctx, LOG_WARNING, 0,
                                     "User account is not activated (uid: %i, %s)",
                                     uid, username);
                        uicid = -1;
                } else if( deactivated != NULL ) {
                        eurephia_log(ctx, LOG_WARNING, 0,
                                     "User account is deactivated (uid: %i, %s)",
                                     uid, username);
                        uicid = -1;
                } else if( pwdok != 1 ) {
                        eurephia_log(ctx, LOG_WARNING, 0,
                                     "Authentication failed for user '%s'. Wrong password.",
                                     username);
                        uicid = -1;
                
                } else {
                        uicid = atoi_nullsafe(/*GET uicid FIELD FROM SQL RESULT */);
                }
        } else {
                eurephia_log(ctx, LOG_WARNING, 0, "Authentication failed for user '%s'.  "
                             "Could not find user or user-certificate link.", username);
                uicid = 0;
        }
        /* FREE SQL RESULT */

        DEBUG(ctx, 20, "Result function call: eDBauth_user(ctx, %i, '%s','xxxxxxxx') - %i",
              certid, username, uicid);

        return uicid;
}

// Retrieve the user id from openvpn_usercerts, based on certid and username
int eDBget_uid(eurephiaCTX *ctx, const int certid, const char *username)  
{
        int ret = 0;

        DEBUG(ctx, 20, "Function call: eDBget_uid(ctx, %i, '%s')", certid, username);

        /* WORK TO DO -- DO SQL:
                           "SELECT uid "
                           "  FROM openvpn_usercerts "
                           "  JOIN openvpn_users USING (uid) "
                           " WHERE certid = '%i' AND username = '%q'",
                           certid, username
        */
        if( /* IF NO RESULT FOUND */ || /* OR WE GOT MORE THAN 1 RECORD */ ) {
                eurephia_log(ctx, LOG_FATAL, 0, "Could not lookup userid for user '%s'", username);
                ret = -1;
        } else {
                ret = atoi_nullsafe(/* GET uid FIELD FROM SQL RESULT */);
        }
        /* FREE SQL RESULT */

        return ret;
}


// If function returns true(1) this control marks it as blacklisted
int eDBblacklist_check(eurephiaCTX *ctx, const int type, const char *val)
{
        int atpexceed = -1, blacklisted = 0;
        char *atpid = NULL, *blid = NULL;

        DEBUG(ctx, 20, "Function call: eDBblacklist_check(ctx, '%s', '%s')",
              eDBattempt_types[type].descr, val);

        /* WORK TO DO -- DO SQL:
                       "SELECT blid FROM openvpn_blacklist WHERE %s = '%q'", 
                       eDBattempt_types[type].colname, val
        */
        if( /* IF RECORDS WAS FOUND */ ) {
                blid = strdup_nullsafe(/* GET blid FIELD FROM SQL RESULT */);
                /* FREE SQL RESULT */

                if( blid != NULL ) { // If we found a blacklisted record
                        eurephia_log(ctx, LOG_WARNING, 0, "Attempt from blacklisted %s: %s",
                                     eDBattempt_types[type].descr, val);
                        blacklisted = 1; // [type] is blacklisted
                }
                // Update attempt information
                update_attempts(ctx, blid);
        } else {
                eurephia_log(ctx, LOG_FATAL, 0, "Querying openvpn_blacklist for blacklisted %s failed",
                             eDBattempt_types[type].descr);
        }

        if( blacklisted == 0 ) {
                // Check if this [type] has been attempted earlier - if it has reaced the maximum 
                // attempt limit, blacklist it

                /* WORK TO DO -- DO SQL:
                         "SELECT atpid, attempts >= %q FROM openvpn_attempts WHERE %s = '%q'",
                         eGet_value(ctx->dbc->config, eDBattempt_types[type].allow_cfg),
                         eDBattempt_types[type].colname, val
                */
                if( /* IF WE FOUND RECORDS */ ) {
                        atpid = strdup_nullsafe(/* GET atpid FROM SQL RESULT */);
                        atpexceed = atoi_nullsafe(/* GET "attempts >= %q" FROM SQL RESULT */);
                        /* FREE SQL RESULT */
                        
                        // If [type] has reached attempt limit and it is not black listed, black list it
                        if( (atpexceed > 0) && (blid == NULL) ) {
                                eurephia_log(ctx, LOG_WARNING, 0,
                                             "%s got BLACKLISTED due to too many failed attempts: %s",
                                             eDBattempt_types[type].descr, val);
                                /* WORK TO DO -- DO SQL 
                                         "INSERT INTO openvpn_blacklist (%s) VALUES ('%q')", 
                                          eDBattempt_types[type].colname, val
                                */
                                if( /* IF SQL QUERY FAILED */ ) {
                                        eurephia_log(ctx, LOG_CRITICAL, 0,
                                                     "Could not blacklist %s (%s)",
                                                     eDBattempt_types[type].descr, val);
                                }
                                /* FREE SQL RESULT */
                                blacklisted = 1; // [type] is blacklisted
                        }
                        free_nullsafe(atpid);
                } else {
                        eurephia_log(ctx, LOG_CRITICAL, 0,
                                     "Querying openvpn_attempts for blacklisted %s failed",
                                     eDBattempt_types[type].descr);
                }
                free_nullsafe(atpr);
        }
        free_nullsafe(blid);

        DEBUG(ctx, 20, "Result - function call: eDBblacklist_check(ctx, '%s', '%s') - %i",
              eDBattempt_types[type].descr, val, blacklisted);

        return blacklisted;
}

// Register a failed attempt of authentication or IP address has been tried to many times
void eDBregister_attempt(eurephiaCTX *ctx, int type, int mode, const char *value) 
{
        char *id = NULL, *atmpt_block = NULL, *blid = NULL;
        int attempts = 0;

        DEBUG(ctx, 20, "Function call: eDBregister_attempt(ctx, %s, %s, '%s')",
              eDBattempt_types[type].colname,
              (mode == ATTEMPT_RESET ? "ATTEMPT_RESET" : "ATTEMPT_REGISTER"),
              value);

        //
        //  openvpn_attempts
        //
        /* WORK TO DO -- DO SQL 
                           "SELECT atpid, attempts > %s, blid, attempts "
                           "  FROM openvpn_attempts "
                           "  LEFT JOIN openvpn_blacklist USING(%s)"
                           " WHERE %s = '%q'",
                           eGet_value(ctx->dbc->config, eDBattempt_types[type].allow_cfg),
                           eDBattempt_types[type].colname,
                           eDBattempt_types[type].colname, value
        */
        if( /* IF SQL QUERY FAILED */ ) {
                eurephia_log(ctx, LOG_FATAL, 0, "Could not look up atpid in openvpn_attempts");
                return;
        }

        attempts = atoi_nullsafe(/* GET attempts FROM SQL RESULT */);
        // If we are asked to reset the attempt counter and we do not find any attempts, exit here
        if( (mode == ATTEMPT_RESET) && (/* IF NO RECORDS WHERE FOUND OR attempts == 0 */) ) {
                /* FREE SQL RESULT */
                return;
        }
        id = strdup_nullsafe(/* GET atpid FROM SQL RESULT */);
        atmpt_block = strdup_nullsafe(/* GET "attempts > %s" FROM SQL RESULT */);
        blid = strdup_nullsafe(/* GET blid FROM SQL RESULT */);

        /* FREE SQL RESULT */


        if( (id == NULL) && (mode == ATTEMPT_REGISTER) ) {
                // Only insert record when we are in registering mode
        
                /* WORK TO DO -- DO SQL:
                          "INSERT INTO openvpn_attempts (%s, attempts) VALUES ('%q', 1)",
                          eDBattempt_types[type].colname, value);
                         
                    Result check comes later ...
                */
        } else if( id != NULL ) {
                // if a attempt record exists, update it according to mode
                switch( mode ) {
                case ATTEMPT_RESET:
                        /* WORK TO DO -- DO SQL:
                                           "UPDATE openvpn_attempts "
                                           "   SET attempts = 0 "
                                           " WHERE atpid = '%q'", 
                                           id
                           Result check comes later ...
                        */
                        break;
                default:
                        /* WORK TO DO -- DO SQL:
                                           "UPDATE openvpn_attempts "
                                           "   SET last_attempt = CURRENT_TIMESTAMP, attempts = attempts + 1"
                                           " WHERE atpid = '%q'", 
                                           id
                           Result check comes later ...
                        */
                        break;
                }
        }
        if( /* IF SQL QUERY FAILED */ ) {
                eurephia_log(ctx, LOG_CRITICAL, 0, 
                             "Could not update openvpn_attempts for %s = %s",
                             eDBattempt_types[type].colname, value);
        }
        /* FREE SQL RESULT */

        //  If attempts have exceeded attempt limit, blacklist it immediately if not already registered
        if( (mode == ATTEMPT_REGISTER) 
            && (blid == NULL) && (atmpt_block != NULL) && (atoi_nullsafe(atmpt_block) > 0) ) {
                eurephia_log(ctx, LOG_WARNING, 0, "Blacklisting %s due to too many attempts: %s",
                             eDBattempt_types[type].descr, value);

                /* WORK TO DO -- DO SQL:
                           "INSERT INTO openvpn_blacklist (%s) VALUES ('%q')",
                           eDBattempt_types[type].colname, value
                */
                if( /* IF SQL FAILED */ ) {
                        eurephia_log(ctx, LOG_CRITICAL, 0,
                                     "Could not blacklist %s: %s",
                                     eDBattempt_types[type].descr, value);
                }
                /* FREE SQL RESULT */
        }
        free_nullsafe(id);
        free_nullsafe(atmpt_block);
        free_nullsafe(blid);
}


// Register a successful authentication
int eDBregister_login(eurephiaCTX *ctx, eurephiaSESSION *skey, const int certid, const int uid, 
                      const char *proto, const char *remipaddr, const char *remport, 
                      const char *vpnipaddr, const char *vpnipmask) 
{
        DEBUG(ctx, 20, "Function call: eDBregister_login(ctx, '%s', %i, %i, '%s','%s','%s','%s','%s')",
              skey->sessionkey, certid, uid, proto, remipaddr, remport, vpnipaddr, vpnipmask);
        
        if( skey->sessionstatus != SESSION_NEW ) {
                eurephia_log(ctx, LOG_ERROR, 5, "Not a new session, will not register it again");
                return 1;
        }

        /* WORK TO DO -- DO SQL:
                           "INSERT INTO openvpn_lastlog (uid, certid, "
                           "                             protocol, remotehost, remoteport,"
                           "                             vpnipaddr, vpnipmask,"
                           "                             sessionstatus, sessionkey, login) "
                           "VALUES (%i, %i, '%q','%q','%q','%q','%q', 1,'%q', CURRENT_TIMESTAMP)",
                           uid, certid, proto, remipaddr, remport, vpnipaddr, vpnipmask, skey->sessionkey
        */
        if( /* IF SQL QUERY FAILED */ ) {
                eurephia_log(ctx, LOG_FATAL, 0, "Could not insert new session into openvpn_lastlog");
                return 0;
        }
        /* FREE SQL RESULT */
        skey->sessionstatus = SESSION_REGISTERED;
        return 1;
}

// Register the MAC address of the VPN adapter of the user.
int eDBregister_vpnmacaddr(eurephiaCTX *ctx, eurephiaSESSION *session, const char *macaddr)
{

        DEBUG(ctx, 20, "Function call: eDBregister_vpnmacaddr(ctx, '%s', '%s')",
              session->sessionkey, macaddr);

        if( macaddr == NULL ) {
                eurephia_log(ctx, LOG_FATAL, 0, "No MAC address was given to save");
                return 0;
        }

        // Register MAC address into history table
        /* WORK TO DO -- DO SQL:
                     "INSERT INTO openvpn_macaddr_history (sessionkey, macaddr) VALUES ('%q','%q')",
                     session->sessionkey, macaddr
        */
        if( /* IF SQL QUERY FAILED */ ) {
                eurephia_log(ctx, LOG_FATAL, 0, "Failed to log new MAC address for session");
                return 0;
        }
        /* FREE SQL RESULT */

        // Update lastlog to reflect last used MAC address for the session
        /* WORK TO DO -- DO SQL:
                           "UPDATE openvpn_lastlog SET sessionstatus = 2, macaddr = '%q' "
                           " WHERE sessionkey = '%q' AND sessionstatus = 1", macaddr, session->sessionkey);
        */
        if( /* IF SQL QUERY FAILED */ ) {
                eurephia_log(ctx, LOG_FATAL, 0, "Could not update lastlog with new MAC address for session");
                return 0;
        }
        /* FREE SQL RESULT */

        // Save the MAC address in the session values register - needed for the destroy session
        if( eDBset_session_value(ctx, session, "macaddr", macaddr) == 0 ) {
                eurephia_log(ctx, LOG_FATAL, 0, "Could not save MAC address into session variables");
                return 0;
        }

        return 1;
}


// Register the user as logged out
int eDBregister_logout(eurephiaCTX *ctx, eurephiaSESSION *skey,
                       const char *bytes_sent, const char *bytes_received, const char *duration)
{
        DEBUG(ctx, 20, "Function call: eDBregister_logout(ctx, '%s', %s, %s)",
              skey->sessionkey, bytes_sent, bytes_received);

        /* WORK TO DO -- DO SQL:
                           "UPDATE openvpn_lastlog "
                           "   SET sessionstatus = 3, logout = CURRENT_TIMESTAMP, "
                           "       bytes_sent = '%i', bytes_received = '%i', session_duration = '%i' "
                           " WHERE sessionkey = '%q' AND sessionstatus = 2",
                           atoi_nullsafe(bytes_sent), atoi_nullsafe(bytes_received),
                           atoi_nullsafe(duration), skey->sessionke
        */
        if( /* IF SQL QUERY FAILED */ ) {
                eurephia_log(ctx, LOG_FATAL, 0,
                             "Could not update lastlog with logout information (%s)",
                             skey->sessionkey);
                return 0;
        }
        /* FREE SQL RESULT */
        skey->sessionstatus = SESSION_LOGGEDOUT;
        return 1;
}


// Retrieve a session key from openvpn_sessionkeys if it is a current session.  Session seed is used
// as criteria
char *eDBget_sessionkey_seed(eurephiaCTX *ctx, const char *sessionseed) {
        char *skey = NULL;

        DEBUG(ctx, 20, "eDBget_sessionkey(ctx, '%s')", sessionseed);

        if( sessionseed == NULL ) {
                eurephia_log(ctx, LOG_FATAL, 1,
                             "eDBget_sessionkey: No session seed given - cannot locate sessionkey");
                return NULL;
        }

        /* WORK TO DO -- DO SQL:
                          "SELECT sessionkey "
                           "  FROM openvpn_sessionkeys "
                           "  JOIN openvpn_lastlog USING (sessionkey)"
                           " WHERE sessionstatus IN (1,2)"
                           "       AND sessionseed = '%q'",
                           sessionseed
        */
        if( /* IF SQL QUERY FAILED */ ) {
                eurephia_log(ctx, LOG_FATAL, 0,
                             "Could not retrieve sessionkey from openvpn_sessionkeys (%s)",
                             sessionseed);
                return NULL;
        } 
        if( /* CHECK THAT WE ONLY GOT 1 RECORD */ ) {
                skey = strdup_nullsafe(/* GET sessionkey FIELD FROM SQL RESULT */);
        } else {
                skey = NULL;
        }
        /* FREE SQL RESULT */
        return skey;
}

char *eDBget_sessionkey_macaddr(eurephiaCTX *ctx, const char *macaddr) {
        char *skey = NULL;

        // Find sessionkey from MAC address
        /* WORK TO DO -- DO SQL:
                           "SELECT sessionkey "
                           "  FROM openvpn_sessions "
                           "  JOIN openvpn_lastlog USING (sessionkey)"
                           " WHERE sessionstatus = 3 "
                           "       AND datakey = 'macaddr' "
                           "       AND dataval = '%q'",
                           macaddr
        */
        if( /* IF SQL QUERY FAILED */ ) {
                eurephia_log(ctx, LOG_FATAL, 0,
                             "Could not remove session from database (MAC addr: %s)", macaddr);
                return 0;
        }
        skey = strdup_nullsafe(/* GET sessionkey FROM SQL RESULT */);
        /* FREE SQL RESULT */

        return skey;
}


// Function returns true(1) if session key is unique
int eDBcheck_sessionkey_uniqueness(eurephiaCTX *ctx, const char *seskey) {
        int uniq = 0;

        DEBUG(ctx, 20, "eDBcheck_sessionkey_uniqueness(ctx, '%s')", seskey);
        if( seskey == NULL ) {
                eurephia_log(ctx, LOG_FATAL, 1,
                             "eDBcheck_sessionkey_uniqness: Invalid session key given");
                return 0;
        }
        
        /* WORK TO DO -- DO SQL: 
               "SELECT count(sessionkey) = 0 FROM openvpn_lastlog WHERE sessionkey = '%q'", 
               seskey
        */
        if( /* IF SQL QUERY FAILED */ ) {
                eurephia_log(ctx, LOG_FATAL, 0,
                             "eDBcheck_sessionkey_uniqness: Could not check uniqueness of sessionkey");
                return 0;
        }
        uniq = atoi_nullsafe(/* GET "count(sessionkey) == 0" (as integer bool - 1 == true) FROM SQL RESULT */);
        /* FREE SQL RESULT */

        return uniq;
}

// register a link between a short-term session seed and a long-term session key
int eDBregister_sessionkey(eurephiaCTX *ctx, const char *seed, const char *seskey) {

        DEBUG(ctx, 20, "eDBregister_sessionkey(ctx, '%s', '%s')", seed, seskey);
        if( (seed == NULL) || (seskey == NULL) ) {
                eurephia_log(ctx, LOG_FATAL, 1,
                             "eDBregister_sessionkey: Invalid session seed or session key given");
                return 0;
        }

        /* WORK TO DO -- DO SQL:
                     "INSERT INTO openvpn_sessionkeys (sessionseed, sessionkey) VALUES('%q','%q')",
                     seed, seskey
        */
        if( /* IF SQL QUERY FAILED */ ) {
                eurephia_log(ctx, LOG_FATAL, 0,
                             "eDBregister_sessionkey: Error registering sessionkey into openvpn_sessionkeys");
                return 0;
        }
        /* FREE SQL RESULT */
        return 1;
}

// remove a session seed/session key link from openvpn_sessionkeys
int eDBremove_sessionkey(eurephiaCTX *ctx, const char *seskey) {

        DEBUG(ctx, 20, "eDBremove_sessionkey(ctx, '%s')", seskey);
        if( seskey == NULL ) {
                eurephia_log(ctx, LOG_FATAL, 1,
                             "eDBremove_sessionkey: Invalid session key given");
                return 0;
        }

        /* WORK TO DO -- DO SQL:
                 "DELETE FROM openvpn_sessionkeys WHERE sessionkey = '%q'", 
                 seskey
        */
        if( /* IF SQL QUERY FAILED */ ) {
                eurephia_log(ctx, LOG_FATAL, 0,
                             "eDBremove_sessionkey: Error removing sessionkey from openvpn_sessionkeys");
                return 0;
        }
        /* FREE SQL RESULT */
        return 1;
}

// Load session values stored in the database into a eurephiaVALUES struct (session values)
eurephiaVALUES *eDBload_sessiondata(eurephiaCTX *ctx, const char *sesskey) {
        eurephiaVALUES *sessvals = NULL;
        int i;

        if( (ctx == NULL) || (sesskey == NULL) ) {
                return NULL;
        }

        DEBUG(ctx, 20, "Function call: eDBload_sessiondata(ctx, '%s')", sesskey);

        sessvals = eCreate_value_space(ctx, 10);

        /* WORK TO DO -- DO SQL: 
                   "SELECT datakey, dataval FROM openvpn_sessions WHERE sessionkey = '%q'", 
                   sesskey
        */
        if( /* IF WE GOT RECORDS IN THE QUERY */ ) {
                for( /* LOOP THROUGH ALL RECORDS */ ) {
                        eAdd_value(ctx, sessvals,
                                   /* GET datakey FROM SQL RESULT */, 
                                   /* GET dataval FROM SQL RESULT */);
                }
                /* FREE SQL RESULT */
        } else {
                eurephia_log(ctx, LOG_CRITICAL, 0,
                             "Could not load session values for session '%s'", sesskey);
                
        }
        return sessvals;
}


// Store a new, update or delete a sessionvalue in the database
int eDBstore_session_value(eurephiaCTX *ctx, eurephiaSESSION *session, int mode, const char *key, const char *val) 
{

        if( session == NULL ) {
                DEBUG(ctx, 20,
                      "Function call failed to eDBstore_session_value(ctx, ...): Non-existing session key");
                return 0;
        }

        DEBUG(ctx, 20, "Function call: eDBstore_session_value(ctx, '%s', %i, '%s', '%s')",
              session->sessionkey, mode, key, val);

        switch( mode ) {
        case SESSVAL_NEW:
                /* WORK TO DO -- DO SQL:
                                   "INSERT INTO openvpn_sessions (sessionkey, datakey, dataval) "
                                   "VALUES ('%q','%q','%q')", 
                                   session->sessionkey, key, val
                */
                if( /* IF SQL QUERY FAILED */ ) {
                        eurephia_log(ctx, LOG_FATAL, 0,
                                     "Could not register new session variable into database: [%s] %s = %s",
                                     session->sessionkey, key, val);
                        return 0;
                }
                break;

        case SESSVAL_UPDATE:
                /* WORK TO DO -- DO SQL:
                                   "UPDATE openvpn_sessions SET dataval = '%q' "
                                   " WHERE sessionkey = '%q' AND datakey = '%q'", 
                                   val, session->sessionkey, key
                */
                if( /* IF SQL QUERY FAILED */ ) {
                        eurephia_log(ctx, LOG_FATAL, 0,
                                     "Could not update session variable: [%s] %s = %s ",
                                     session->sessionkey, key, val);
                        return 0;
                }
                break;

        case SESSVAL_DELETE:
                /* WORK TO DO -- DO SQL:
                             "DELETE FROM openvpn_sessions WHERE sessionkey = '%q' AND datakey = '%q'",
                             session->sessionkey, key
                */
                if( /* IF SQL QUERY FAILED */ ) {
                        eurephia_log(ctx, LOG_FATAL, 0,
                                     "Could not delete session variable: [%s] %s",
                                     session->sessionkey, key);
                        return 0;
                }
                break;

        default:
                eurephia_log(ctx, LOG_FATAL, 0,
                             "Unknown eDBstore_session_value mode '%i'", mode);
                return 0;
        }
        /* FREE SQL RESULT */
        return 1;
}


// Delete session information from openvpn_sessions and update openvpn_lastlog with status
int eDBdestroy_session(eurephiaCTX *ctx, eurephiaSESSION *session) {
        
        DEBUG(ctx, 20, "Function call: eDBdestroy_session(ctx, '%s')", session->sessionkey);

        if( (session == NULL) || (session->sessionkey == NULL) ) {
                eurephia_log(ctx, LOG_WARNING, 1, "No active session given to be destroyed");
                return 1;
        }

        // Update session status
        /* WORK TO DO -- SQL 
                           "UPDATE openvpn_lastlog "
                           "   SET sessionstatus = 4, session_deleted = CURRENT_TIMESTAMP "
                           " WHERE sessionkey = '%q' AND sessionstatus = 3", 
                           session->sessionkey
        */
        if( /* IF SQL QUERY FAILED */ ) {
                eurephia_log(ctx, LOG_FATAL, 0,
                             "Could not update session status in lastlog (%s))", session->sessionkey);
                return 0;
        }
        /* FREE SQL RESULT */

        // Delete session variables
        /* WORK TO DO -- DO SQL:
                     "DELETE FROM openvpn_sessions WHERE sessionkey = '%q'", 
                     session->sessionkey
        */
        if( /* IF SQL QUERY FAILED */ ) {
                eurephia_log(ctx, LOG_FATAL, 0,
                             "Could not delete session variables (%s))", session->sessionkey);
                return 0;
        }
        /* FREE SQL RESULT */

        // Remove the sessionkey from openvpn_sessions
        if( eDBremove_sessionkey(ctx, session->sessionkey) == 0 ) {
                return 0;
        }
        return 1;
}


char *eDBget_firewall_profile(eurephiaCTX *ctx, eurephiaSESSION *session) 
{
        char *ret = NULL;

        DEBUG(ctx, 20, "Function call: eDBget_firewall_profile(ctx, {session}'%s')",
              session->sessionkey);

        /* WORK TO DO -- DO SQL:
                           "SELECT fw_profile "
                           "  FROM openvpn_lastlog "
                           "  JOIN openvpn_usercerts USING(certid, uid)"
                           "  JOIN openvpn_accesses USING(accessprofile)"
                           " WHERE sessionkey = '%q'", 
                           session->sessionkey
        */
        if( /* IF SQL QUERY FAILED */ ) {
                eurephia_log(ctx, LOG_FATAL, 0,
                             "Could not retrieve firewall profile for session '%s'",
                             session->sessionkey);
                return NULL;
        }
        ret = strdup_nullsafe(/* GET fw_profile FROM SQL RESULT */);
        /* FREE SQL RESULT */
        return ret;
}

eurephiaVALUES *eDBget_blacklisted_ip(eurephiaCTX *ctx) {
        eurephiaVALUES *ret = NULL;

        DEBUG(ctx, 20, "Function call: eDBget_blacklisted_ip(ctx)");

        /* WORK TO DO -- DO SQL:
                  SELECT remoteip FROM openvpn_blacklist WHERE remoteip IS NOT NULL
        */
        if( /* IF SQL QUERY FAILED */ ) {
                eurephia_log(ctx, LOG_FATAL, 0,
                             "Could not retrieve blacklisted IP addresses from the database");
		return NULL;
	}
        ret = eCreate_value_space(ctx, 21);
        for( /* LOOP THROUGH ALL RECORDS */ ) {
                eAdd_value(ctx, ret, NULL, /* GET remoteip FROM SQL RESULT */);
        }
        /* FREE SQL RESULT */

        return ret;
}


#if DRIVERAPIVERSION > 1
/*
 *  API Version 2 functions
 *
 */

#endif
