<?php
/*
  Copyright (C) 2013 Grégory Soutadé
  
  This file is part of gPass.
  
  gPass 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.
  
  gPass 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 gPass.  If not, see <http://www.gnu.org/licenses/>.
*/

/*
  login is stored as :
  @@url;login
  
  Password is salted (3 random characters) and encrypted

  All is encrypted with AES256 and key : sha256(master key)
 */
$MAX_ENTRY_LEN = 512;
$USERS_PATH = "./users/";

function get_mkey_hash($mkey)
{
    return bin2hex(hash("sha256", $mkey, true));
}

function open_crypto($mkey)
{
    if (!isset($_SESSION['td']))
    {
        $td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, '');

        if ($td == false)
            die("Unable to open mcrypt");

        $ret = mcrypt_generic_init($td, hex2bin($mkey), '0000000000000000');

        if ($ret < 0)
        {
            echo "<div class=\"error\">Unable to set key $ret</div>";
            return null;
        }

        $_SESSION['td'] = $td;
    }
    else
        $td = $_SESSION['td'];

    return $td;
}

function decrypt($mkey, $val, $salted)
{
    $td = open_crypto($mkey);

    if ($td == null) return;

    $val = mdecrypt_generic($td, hex2bin($val));

    // Remove 0 added by encrypt
    $val = str_replace("\0", '', $val);

    // Remove salt
    if ($salted)
        $val = substr($val, 0, strlen($val)-3);
    
    return $val;
}

function encrypt($mkey, $val, $salted)
{
    global $MAX_ENTRY_LEN;

    $td = open_crypto($mkey);

    if ($td == null) return;

    if ($salted)
    {
        $val .= dechex(rand(256,4095)); //between 0x100 and 0xfff
    }

    $val = mcrypt_generic($td, $val);

    if (strlen($val) > $MAX_ENTRY_LEN)
    {
        echo "<div class=\"error\">Value to encrypt is too long</div>";
        return null;
    }
    
    return bin2hex($val);
}

// From http://php.net/manual/en/function.copy.php
function recurse_copy($src,$dst) {
    $dir = opendir($src);
    if ($dir == FALSE) return FALSE;
    if (!@mkdir($dst)) return FALSE;
    while(false !== ( $file = readdir($dir)) ) {
        if (( $file != '.' ) && ( $file != '..' )) {
            if ( is_dir($src . '/' . $file) ) {
                return recurse_copy($src . '/' . $file,$dst . '/' . $file);
            }
            else {
                copy($src . '/' . $file,$dst . '/' . $file);
            }
        }
    }
    closedir($dir);
    return TRUE;
} 

function create_user($user)
{
    global $USERS_PATH;

    if (strpos($user, "..") || strpos($user, "/") || $user[0] == "." || $user[0] == "_")
    {
        echo "<div class=\"error\">Invalid user</div>";
    }
    else
    {
        $user = $USERS_PATH . $user;

        if (file_exists($user))
        {
            echo "<div class=\"error\">User already exists</div>";
        }
        else
        {
            if (!recurse_copy("./ref", $user))
            {
                echo "<div class=\"error\">Cannot create user $user</div>";
            }
            else
            {
                return true;
            }
        }
    }

    return false;
}

function load_database($user)
{
    global $USERS_PATH;

    try {
        $db = new SQLite3($USERS_PATH . "$user/gpass.bdd", SQLITE3_OPEN_READWRITE);
    }
    catch(Exception $e)
    {
        echo "<div class=\"error\">Unable to load database for user $user !</div>";
        return null;
    }

    // New access need to reset crypto
    unset($_SESSION['td']);

    return $db;
}

function add_entry($user, $mkey, $url, $login, $password)
{
    $db = load_database($user);

    if ($db == null) return false;

    $password = encrypt($mkey, trim($password), true);
    $login = encrypt($mkey, "@@" . trim($url) . ";" . trim($login), false);

    if ($password == null || $login == null)
        return false;

    $count = $db->querySingle("SELECT COUNT(*) FROM gpass WHERE login='" . $login . "'");

    if ($count != 0)
    {
        echo "<div class=\"error\">Entry already exists</div>";
        return false;
    }

    $result = $db->query("INSERT INTO gpass ('login', 'password') VALUES ('" . $login . "', '" . $password . "')");

    return true;
}

function delete_entry($user, $login)
{
    $db = load_database($user);

    if ($db == null) return false;

    $db->query("DELETE FROM gpass WHERE login='" . $login . "'");

    return true;
}

function update_entry($user, $mkey, $old_login, $url, $login, $password)
{
    if (delete_entry($user, $old_login))
        return add_entry($user, $mkey, $url, $login, $password);

    return false;
}

function list_entries($user)
{
    $db = load_database($user);

    if ($db == null) return;

    $result = $db->query("SELECT * FROM gpass");

    while (($row = $result->fetchArray()))
    {
        echo $row['login'] . ";" . $row['password'] . "\n";
    }
}

?>