<?php

/**
 * LICENSE:
 * 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, see <http://www.gnu.org/licenses/>.
 *
 *
 * @package		Bright Game Panel V2
 * @version		0.1
 * @category	Systems Administration
 * @author		warhawk3407 <warhawk3407@gmail.com> @NOSPAM
 * @copyright	Copyleft 2015, Nikita Rousseau
 * @license		GNU General Public License version 3.0 (GPLv3)
 * @link		http://www.bgpanel.net/
 */

if ( !class_exists('Core_Abstract_Controller')) {
	trigger_error('Controller_Box -> BGP_Controller is missing !');
}

/**
 * Admin Box Controller
 */

class Core__Controller_Box extends Core_Abstract_Controller {

	function __construct( )	{
	
		// Call parent constructor
		parent::__construct( basename(__DIR__) );
	}

    /**
     * @api {post} /box Creates a new box.
     * @author Nikita Rousseau
     * @apiVersion v1
     * @apiName PostBox
     * @apiGroup Box
     *
     * @apiDescription Creates a new dedicated server for Bright Game Panel.
     *
     * @apiParam {String} name
     * @apiParam {String} os
     * @apiParam {String} ip
     * @apiParam {String} port
     * @apiParam {String} login
     * @apiParam {String} [password]
     * @apiParam {String} [privatekey]
     * @apiParam {String} [userPath]
     * @apiParam {String} [steamPath]
     * @apiParam {String} [notes]
     *
     * @apiSuccess {String} id The Box-ID of the Box.
     * @param $name
     * @param $os
     * @param $ip
     * @param $port
     * @param $login
     * @param string $password
     * @param string $privatekey
     * @param string $userPath
     * @param string $steamPath
     * @param string $notes
     * @return array
     */
	function postBox( $name, $os, $ip, $port, $login, $password = '', $privatekey = 'bgp_rsa', $userPath = '', $steamPath = '', $notes = '' )
	{
		$args = array (
			'name' 			=> $name,
			'os' 			=> $os,
			'ip' 			=> $ip,
			'port' 			=> $port,
			'login' 		=> $login,
			'password' 		=> $password,
			'privatekey' 	=> $privatekey,
			'userPath' 		=> $userPath,
			'steamPath' 	=> $steamPath,
			'notes' 		=> $notes
		);

		$errors			= array();  	// array to hold validation errors
		$data 			= array(); 		// array to pass back data
		
		$dbh = Core_Database_Service::getDBH();		// Get Database Handle
		
		// validate the variables ======================================================

		$v = new Valitron\Validator( $args );

		$rules = [
				'required' => [
					['name'],
					['os'],
					['ip'],
					['port'],
					['login']
				],
				'regex' => [
					['name', "/^([-a-z0-9_ -])+$/i"],
					['login', "/^([-a-z0-9_-])+$/i"],
					['privatekey', "/^([-a-z0-9_.-])+$/i"]
				],
				'integer' => [
					['os'],
					['port']
				],
				'ip' => [
					['ip']
				]
			];

		$labels = array(
				'name' 		=> T_('Remote Machine Name'),
				'os' 		=> T_('Operating System'),
				'ip' 		=> T_('IP Address'),
				'port'		=> T_('Port'),
				'login' 	=> T_('Login')
			);

		$v->rules( $rules );
		$v->labels( $labels );

		$v->validate();

		$errors = $v->errors();

		// validate the variables phase 2 ==============================================

		if (empty($errors))
		{
			// Verify OS ID

			try {
				$sth = $dbh->prepare("
					SELECT operating_system
					FROM os
					WHERE
						os_id = :os_id
					;");

				$sth->bindParam( ':os_id', $args['os'] );

				$sth->execute();

				$result = $sth->fetchAll( PDO::FETCH_ASSOC );
			}
			catch (PDOException $e) {
				echo $e->getMessage().' in '.$e->getFile().' on line '.$e->getLine();
				die();
			}

			if (empty($result[0])) {
				$errors['os'] = 'Bad Identifier';
			}

			// Verify Communication

			$socket = @fsockopen( $args['ip'], $args['port'], $errno, $errstr, 3 );

			if ( $socket === FALSE ) {

				$errors['com'] = "Unable to connect to " . $args['ip'] . " on port " . $args['port'] . ". " . utf8_encode($errstr) . " ( $errno )";
				unset($socket);
			}
			else {
				unset($socket);

				$ssh = new Net_SSH2( $args['ip'], $args['port'] );

				$key = new Crypt_RSA();

				$sshIsLoggedIn = TRUE;

				// Private Key Auth
				if (empty( $args['password'] )) {

					if (!file_exists( RSA_KEYS_DIR . '/' . $args['privatekey'] )) {

						$errors['privatekey'] = 'RSA Key doesn\'t exists';
						$sshIsLoggedIn = FALSE;
					}
					else {
						$key->loadKey(file_get_contents( RSA_KEYS_DIR . '/' . $args['privatekey'] ));

						if ( !$ssh->login( $args['login'], $key) ) {

							$errors['com'] = 'Login failed with this login / RSA private key';
							$sshIsLoggedIn = FALSE;
						}
					}
				}
				// Normal Auth
				else {
					if ( !$ssh->login( $args['login'], $args['password'] ) )
					{

						$errors['com'] = 'Login failed with this login / password';
						$sshIsLoggedIn = FALSE;
					}
				}

				if ($sshIsLoggedIn !== FALSE) {
					// Verify Remote Paths		

					if (!empty($args['userPath'])) {

						if ( boolval(trim( $ssh->exec('test -d '. escapeshellcmd($args['userPath']) . " && echo '1' || echo '0'") )) === FALSE ) {

							$errors['remoteUserHome'] = 'Invalid path. Must be an absolute or full path';
						}
					}

					if (!empty($args['steamPath'])) {

						if ( boolval(trim( $ssh->exec('test -f '. escapeshellcmd($args['steamPath']) . " && echo '1' || echo '0'") )) === FALSE ) {

							$errors['steamcmd'] = 'SteamCMD not found. Must be an absolute or full path';
						}
					}
				}

				$ssh->disconnect();
			}
		}

		// Apply =======================================================================

		if (empty($errors))
		{
			//
			// Database update
			//

			// Vars Init

			if (empty($args['userPath'])) {
				$home = "~";
				$args['userPath'] = $home;
			} else {
				$home = escapeshellcmd(bgp_normalize_path($args['userPath']));
				$args['userPath'] = $home;
			}

			$config = parse_ini_file( CONF_SECRET_INI );

			// BOX

			try {
				$sth = $dbh->prepare("
					INSERT INTO box
					SET
						os_id 			= :os,
						name 			= :name,
						steam_lib_path 	= :steamcmd,
						notes 			= :notes
					;");

				$sth->bindParam( ':os', $args['os'] );
				$sth->bindParam( ':name', $args['name'] );
				$sth->bindParam( ':steamcmd', $args['steamPath'] );
				$sth->bindParam( ':notes', $args['notes'] );

				$sth->execute();

				$box_id = $dbh->lastInsertId();
			}
			catch (PDOException $e) {
				echo $e->getMessage().' in '.$e->getFile().' on line '.$e->getLine();
				die();
			}

			// IP

			try {
				$sth = $dbh->prepare("
					INSERT INTO box_ip
					SET
						box_id = :box_id,
						ip = :ip,
						is_default = 1
					;");

				$sth->bindParam( ':box_id', $box_id );
				$sth->bindParam( ':ip', $args['ip'] );

				$sth->execute();
			}
			catch (PDOException $e) {
				echo $e->getMessage().' in '.$e->getFile().' on line '.$e->getLine();
				die();
			}

			// CREDENTIALS

			$isUsingRSAKeyExchange = TRUE; // By default, we use the SSH authentication with RSA keypair

			// Try to append our public key to authorized_keys
			// If using normal auth

			$ssh = new Net_SSH2( $args['ip'], $args['port'] );

			// Normal Auth
			if (!empty( $args['password'] )) {

				// Phase 1
				// Connect to the remote host

				$ssh->login( $args['login'], $args['password'] );

				$remote_keys = $ssh->exec( 'cat ' . $home . '/.ssh/authorized_keys' );

				// Check if the public key already exists

				if (strpos($remote_keys, file_get_contents( RSA_PUBLIC_KEY_FILE )) === FALSE) {

					// Otherwise, append it

					$ssh->exec( "echo '" . file_get_contents( RSA_PUBLIC_KEY_FILE ) . "' >> " . $home . "/.ssh/authorized_keys" );
				}

				// Phase 2
				// Verify that the public key is allowed on the remote host

				$remote_keys = $ssh->exec( 'cat ' . $home . '/.ssh/authorized_keys' );

				$ssh->disconnect();

				if (strpos($remote_keys, file_get_contents( RSA_PUBLIC_KEY_FILE )) === FALSE)
				{
					// authorized_keys is not writable
					// Use compatibility mode
					// Store the password in DB

					$isUsingRSAKeyExchange = FALSE;
				}
				else
				{
					// Phase 3
					// Try to connect with our private key on the remote host

					$ssh = new Net_SSH2( $args['ip'], $args['port'] );

					$key = new Crypt_RSA();
					$key->loadKey( file_get_contents( RSA_PRIVATE_KEY_FILE ) );

					if (!$ssh->login( $args['login'], $key )) {

						// Authentication failed
						// Use compatibility mode
						// Store the password in DB

						$isUsingRSAKeyExchange = FALSE;
					}

					$ssh->disconnect();
				}
			}

			// SSH CREDENTIALS

			$cipher = new Crypt_AES(CRYPT_AES_MODE_ECB);
			$cipher->setKeyLength(256);
			$cipher->setKey( $config['APP_SSH_KEY'] );

			if ($isUsingRSAKeyExchange)
			{
				try {
					$sth = $dbh->prepare("
						INSERT INTO box_credential
						SET
							login = :login,
							privatekey = :privatekey,
							remote_user_home = :home,
							com_protocol = 'ssh2',
							com_port = :com_port
						;");

					$login = $cipher->encrypt($args['login']);

					$sth->bindParam( ':login',  $login );
					$sth->bindParam( ':privatekey', $args['privatekey'] );
					$sth->bindParam( ':home', $args['userPath'] );
					$sth->bindParam( ':com_port', $args['port'] );

					$sth->execute();

					$credential_id = $dbh->lastInsertId();
				}
				catch (PDOException $e) {
					echo $e->getMessage().' in '.$e->getFile().' on line '.$e->getLine();
					die();
				}
			}
			else
			{
				try {
					$sth = $dbh->prepare("
						INSERT INTO box_credential
						SET
							login = :login,
							password = :password,
							privatekey = :privatekey,
							remote_user_home = :home,
							com_protocol = 'ssh2',
							com_port = :com_port
						;");

					$login = $cipher->encrypt($args['login']);
					$password = $cipher->encrypt($args['password']);

					$sth->bindParam( ':login', $login );
					$sth->bindParam( ':password', $password );
					$sth->bindParam( ':privatekey', $args['privatekey'] );
					$sth->bindParam( ':home', $args['userPath'] );
					$sth->bindParam( ':com_port', $args['port'] );

					$sth->execute();

					$credential_id = $dbh->lastInsertId();
				}
				catch (PDOException $e) {
					echo $e->getMessage().' in '.$e->getFile().' on line '.$e->getLine();
					die();
				}
			}

			// UPDATE BOX

			try {
				$sth = $dbh->prepare( "
					UPDATE box
					SET
						box_credential_id = :box_credential_id
					WHERE box_id = :box_id
					;" );

				$sth->bindParam( ':box_credential_id', $credential_id );
				$sth->bindParam( ':box_id', $box_id );

				$sth->execute();
			}
			catch (PDOException $e) {
				echo $e->getMessage().' in '.$e->getFile().' on line '.$e->getLine();
				die();
			}
		}

		// return a response and log ===================================================

		$logger = self::getLogger();

		$data['errors'] = $errors;

		if (!empty($data['errors'])) {

			$data['success'] = false;

			$logger->info('Failed to add box.');
		} else {

			$data['success'] = true;

			$logger->info('Box added.');
		}

		return array(
			'response' => 'application/json',
			'data' => json_encode($data)
		);
	}

    /**
     * @api {delete} /box/:id Deletes a box.
     * @author Nikita Rousseau
     * @apiVersion v1
     * @apiName DeleteBox
     * @apiGroup Box
     *
     * @apiDescription Removes a dedicated server from Bright Game Panel.
     *
     * @apiParam {String} box_id The Box-ID identifier.
     *
     * @param $box_id
     * @return array
     */
	function deleteBox( $box_id )
	{
        // TODO : implement required method
        return array();
	}
}
