//
// anyRemote
// a wi-fi or bluetooth remote for your PC.
//
// Copyright (C) 2006-2016 Mikhail Fedotov <anyremote@mail.ru>
//
// 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; either version 3 of the License, or
// (at your option) any later version.
//
// 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 St, Fifth Floor, Boston, MA  02110-1301  USA
//

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/types.h>

#include "common.h"
#include "list.h"
#include "utils.h"
#include "conf.h"
#include "str.h"
#include "dispatcher.h"
#include "peer.h"

#define ALLOWED_FILE "/.anyRemote/allowed_hosts"

extern char tmp[MAXMAXLEN];
extern int  remoteOn;

boolean_t usePassword = BOOL_NO;
boolean_t allowedOnly = BOOL_NO;

SingleList * _allowedPeers = NULL;

static void destroyPeer(void* ptr) 
{
    string_t * v = (string_t *) ptr;
    stringFree(v, BOOL_YES);
}

static void dropTable()
{
    DEBUG2("Security : dropTable");
    listSingleFullFree(_allowedPeers, destroyPeer);
    _allowedPeers = NULL;
}

static void readTable()
{   
    dropTable();
    DEBUG2("Security : readTable");
    
    FILE *fp;
    struct stat buf;

    char *h = getenv("HOME");
    if (!h) {
	    WARNING2("Security issue: $HOME variable not accessible, no connection will be accepted")
        return;
    }

    char* resfile = (char*) calloc(strlen(h)+strlen(ALLOWED_FILE)+1,1);

    strcpy(resfile, h);
    strcat(resfile, ALLOWED_FILE);

    if(stat(resfile, &buf) == -1) {
	    WARNING2("Security issue: File %s is absent, no connection will be accepted", resfile)
        free(resfile);
        return;
    }

    long fLen = buf.st_size;
    DEBUG2("readTable file size >%ld<", fLen);

    fp=fopen(resfile,"r");

    if (fp == NULL) {
	    WARNING2("Security issue: Can not open file %s, no connection will be accepted", resfile)
        free(resfile);
        return;
    }
    free(resfile);

    char * fBuffer = (char*) calloc(fLen+1,1);
    
    fread(fBuffer, sizeof(char), fLen, fp);
    
    fclose(fp);

    // separate by \n
    char* bStr = strtok(fBuffer,"\n");
    while (bStr != NULL) {
    
        DEBUG2("readTable add peer %s", bStr);
	
	    string_t * v = stringNew(bStr);
	    _allowedPeers = listSingleAppend(_allowedPeers, v);

	    bStr = strtok(NULL,"\n");
    }
    
    free(fBuffer);
}

static boolean_t checkTable(const char* peer)
{
    if (peer) {
	    SingleList* list = _allowedPeers;
	    while (list) {
	        string_t * v = (string_t *) list->data;
            if (strcmp(v->str,peer) == 0) {
        	    return BOOL_YES;
            }
            list = listSingleNext(list);
	    }
    }
    return BOOL_NO;
}

void setAllowedOnly(const char* value)
{
    DEBUG2("setAllowedOnly %s", value);
    allowedOnly = boolValue(value);
    
    if (allowedOnly) {	// re-read firewall table
        readTable();
    } else {		// drop firewall table
        dropTable();
    }
}

boolean_t isAllowed(const char* peer)
{
    return (allowedOnly ? checkTable(peer) : BOOL_YES);
}

void  setUsePassword(boolean_t use)
{
    DEBUG2("setUsePassword %d", use);
    usePassword = use;
}

boolean_t getUsePassword(void)
{
    return usePassword;
}

char* getStoredPass()
{
    FILE *fp;
    struct stat buf;

    char *h = getenv("HOME");
    if (!h) {
        return NULL;
    }

    char* resfile = (char*) calloc(strlen(h)+21,1);

    strcpy(resfile, h);
    strcat(resfile, "/.anyRemote/password");

    if(stat(resfile, &buf) == -1) {
        free(resfile);
        return NULL;
    }

    long fLen = buf.st_size;
    DEBUG2("getStoredPass >%ld<", fLen);

    fp=fopen(resfile,"r");
    free(resfile);

    if (fp == NULL) {
        return NULL;
    }

    char * fBuffer = (char*) calloc(fLen+1,1);
    #ifdef __cplusplus
    size_t dummy = 
    #endif
        fread(fBuffer, sizeof(char), fLen, fp);
    
    fclose(fp);

    // strip \n from the end
    int plen = strlen(fBuffer) - 1;
    while (plen > 0 && *(fBuffer+plen) == '\n') {
        *(fBuffer+plen) = '\0';
        plen--;
    }

    return fBuffer;
}

boolean_t checkPassword (const char* value)
{
    DEBUG2("checkPassword %s", (value ? value : "NULL"));
    
    char* pass = getStoredPass();
    if (!pass) {
        return BOOL_YES;
    }
    
    if (memcmp(value,   DEF_MSG, 4) == 0 &&
        memcmp(value+4, "_PASSWORD_(,", 12) == 0) {	    // Got verification response from Java client

        int plen = strlen(pass);
        if (memcmp(value+16, pass, plen) == 0) {     // _PASSWORD_(,<password here>)
            logger(L_INF,"[DS]: Password OK");
            free(pass);

            return BOOL_YES;
        }
    }    
    return BOOL_NO;
}

int verifyPassword(int fd)
{
    DEBUG2("[DS]: Do password verification on descriptor %d", fd);

    const char* passCmd = "Get(password);";

    int n = write(fd,passCmd,strlen(passCmd));
    if (n < 0) {
    	logger(L_ERR, "[DS]: Error on password verification (writing to socket)");
    	return EXIT_STOP;
    }

    char buf[MAXCMDLEN];
    n = readPeer(fd, buf, MAXCMDLEN);
    if (n <= 0) {  // EOF or error
        logger(L_ERR, "[DS]: Error on password verification (reading from socket)");
        return EXIT_STOP;
    }
    buf[n] = '\0';

    if (!checkPassword(buf)) {
        logger(L_INF,"[DS]: Verification failed. Incorrect password.");
	    return EXIT_NOK;
    }
    logger(L_DBG,"[DS]: Password verification OK");
    return EXIT_OK;
}
