/* client_session.c  --  Handles eurephia session in admin clients
 *
 *  GPLv2 only - Copyright (C) 2008 - 2015
 *               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   client_session.c
 * @author David Sommerseth <dazo@eurephia.org>
 * @date   2008-12-01
 *
 * @brief  Functions for working on eurephiaSESSIONs outside the openvpn plug-in
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <assert.h>

#include <eurephia_nullsafe.h>
#include <eurephia_context.h>
#include <eurephiadb_session_struct.h>
#include <eurephiadb_session_common.h>
#include <eurephiadb_driver.h>
#include <eurephia_log.h>
#include <randstr.h>
#include <sha512.h>

#include "client_config.h"

/**
 * Get a file name to a session file.  This file can be found in either EUREPHIADM_SESSION
 * environment variable, in the users ~/.eurephia or the directory defined by the EUREPHIA_DIR
 * environment variable.
 *
 * @return Returns a full path to the session file on success, otherwise NULL.
 */
char *get_session_file() {
        char *fname = NULL;

        fname = get_config_filename("EUREPHIADM_SESSION","/session");
        if( fname == NULL ) {
                // If even default ~/.eurephia dir failed, create one in /tmp
                snprintf(fname, 1024, "/tmp/.eurepia-%i.sess", getuid());
                return fname;
        }

        return fname;
}


/**
 * Reads the session key and does a quick validation of it.
 *
 * @param ctx  eurephiaCTX
 *
 * @return Returns a string (char *) containing the session key on success, otherwise NULL.
 */
char *read_session_file(eurephiaCTX *ctx) {
        char *sesskey = NULL, *fname = NULL;
        FILE *sfp = NULL;

        fname = get_session_file();
        assert(fname != NULL);

        if( (sfp = fopen(fname, "r")) == NULL ) {
                return NULL;
        }

        sesskey = (char *) malloc_nullsafe(ctx, 256);
        assert( sesskey != NULL );
        if( (fgets(sesskey, 254, sfp) == NULL) || (strlen_nullsafe(sesskey) < 64) ) {
                eurephia_log(ctx, LOG_PANIC, 0,
                             "Could not read session file (%s).  Session value too short", fname);
                return NULL;
        }
        fclose(sfp);
        return sesskey;
}

/**
 * Writes a session key to disk, to keep session logged in over a longer time.
 *
 * @param ctx  eurephiaCTX
 * @param sess eurephiaSESSION with session info for the current logged in user.
 *
 * @return Returns 1 on success, otherwise 0.
 */
int write_session_file(eurephiaCTX *ctx, eurephiaSESSION *sess) {
        struct stat fchk;
        char *fname = NULL;
        FILE *sfp = NULL;

        assert((ctx != NULL) && (sess != NULL) && (sess->sessionkey != NULL));

        fname = get_session_file();
        assert(fname != NULL);

        if( stat(fname, &fchk) != -1 ) {
                // session file exists, do not update it
                return 1;
        }

        if( (sfp = fopen(fname, "w")) == NULL ) {
                eurephia_log(ctx, LOG_PANIC, 0, "Could not create session file (%s).  Operation aborted.", fname);
                return 0;
        }
        fprintf(sfp, "%s", sess->sessionkey);
        fclose(sfp);
        return 1;
}


/**
 * Removes the session file, used when logging out or the user is automatically logged out.
 *
 * @param ctx eurephiaCTX
 */
void remove_session_file(eurephiaCTX *ctx) {
        const char *fname = NULL;

        fname = get_session_file();
        assert(fname != NULL);
        if( (access(fname, F_OK) != -1) && (unlink(fname) == -1) ) {
                eurephia_log(ctx, LOG_ERROR, 0,
                             "Could not remove session file (%s). Following operations might fail", fname);
        };
}

/**
 * Create a eurephiaSESSION.  If session key is given, it will try to load that session back.
 * If no session key is given, it will create a brand new user session.
 *
 * @param ctx     eurephiaCTX
 * @param sesskey session key, used for loading existing sessions back.
 *
 * @return Returns a eurephiaSESSION pointer to a session, either new or old.  On failure, NULL is returned.
 */
eurephiaSESSION *create_session(eurephiaCTX *ctx, const char *sesskey) {
        eurephiaSESSION *new_sess = NULL;
        int loop = 0, uniqchk = 0;
        char *randdata = NULL;
        unsigned char sha_res[SHA512_HASH_SIZE+2];
        SHA512Context sha;

        new_sess = (eurephiaSESSION *) malloc_nullsafe(ctx, sizeof(eurephiaSESSION) + 2);
        assert(new_sess != NULL);

        if( sesskey == NULL ) {

                // Get data for a unique session key
                randdata = (char *) malloc_nullsafe(ctx, 514);
                assert(randdata != NULL);

                do {
                        char *ptr = NULL;
                        int i = 0;

                        if( !eurephia_randstring(ctx, randdata, 512) ) {
                                eurephia_log(ctx, LOG_FATAL, 0,
                                             "Could not generate enough random data for session");
                                free_nullsafe(ctx, randdata);
                                free_nullsafe(ctx, new_sess);
                                return NULL;
                        }

                        memset(&sha, 0, sizeof(SHA512Context));
                        memset(&sha_res, 0, SHA512_HASH_SIZE+2);

                        free_nullsafe(ctx, new_sess->sessionkey);
                        new_sess->sessionkey = (char *) malloc_nullsafe(ctx, (SHA512_HASH_SIZE*2) + 3);
                        assert(new_sess->sessionkey != NULL);

                        SHA512Init(&sha);
                        SHA512Update(&sha, randdata, 512);
                        SHA512Final(&sha, sha_res);

                        ptr = new_sess->sessionkey;
                        for( i = 0; i < SHA512_HASH_SIZE; i++ ) {
                                sprintf(ptr, "%02x", sha_res[i]);
                                ptr += 2;
                        }
                        memset(&sha, 0, sizeof(SHA512Context));
                        memset(&sha_res, 0, sizeof(sha_res));

                        loop++;
                        uniqchk = eDBcheck_sessionkey_uniqueness(ctx, new_sess->sessionkey);
                        memset(randdata, 0, 514);
                } while( (uniqchk == 0) && (loop < 11) );
                free_nullsafe(ctx, randdata);

                if( uniqchk == 0 ) {
                        eurephia_log(ctx, LOG_FATAL, 0,
                                     "Did not manage to create a unique session key after %i attempts."
                                     "  Aborting.", loop-1);
                        free_nullsafe(ctx, new_sess->sessionkey);
                        free_nullsafe(ctx, new_sess);
                        return NULL;
                }
        } else {
                new_sess->sessionkey = strdup(sesskey);
                new_sess->sessvals = eDBload_sessiondata(ctx, new_sess->sessionkey);
        }
        //  Return new session
        return new_sess;
}
