<?php
//
// New REST API utils and classes for Nagios Fusion
// Copyright (c) 2018 Nagios Enterprises, LLC. All rights reserved.
//

//
// ACTUAL API SECTION
//

class API extends NagiosAPI
{
    protected $user;

    public function __construct($request, $origin)
    {
        parent::__construct($request);

        $apikey = new APIKey();

        // List of nodes that do not need API key
        $no_api_key = array('authenticate', 'license');

        // Do not need an API key to use the authenticate method
        $nodes = explode('/', $request, 2);
        if (in_array($nodes[0], $no_api_key)) {
            return;
        }

        if (!array_key_exists('apikey', $this->request)) {
            throw new Exception(_('No API Key provided'));
        } else if (!$apikey->verify_key($this->request, $this->method, $origin, $reason)) {
            throw new Exception($reason);
        }
    }

    protected function convert_to_output_type($data)
    {
        return json_encode($data);
    }

    /**
     * Used for the api/v1/authenticate token generation
     */
    protected function authenticate($verb, $args)
    {
        switch ($this->method)
        {
            case 'POST':
                return $this->authenticate_user();
                break;

            default:
                return array('error' => _('You can only use POST with authenticate.'));
                break;
        }
    }

    /**
     * Used as an endpoint for the V2 license API to poke Fusion and make it do "things"
     */
    protected function license($verb, $args)
    {
        if (is_v2_license()) {
            switch ($this->method)
            {
                case 'POST':
                    switch ($verb) {

                        case 'checkin':
                            
                            // Rate limit the checkin function to once every 5 seconds
                            $lcc = get_option('last_api_v2_license_checkin', 0);
                            if ($lcc > time() - 5) {
                                return array('error' => _('Slow down. You must wait before requesting another check-in.'));
                            }

                            // Do the actual checkin
                            $ret = pull_v2_license_info();
                            if ($ret) {
                                set_option('last_api_v2_license_checkin', time());
                                return array('success' => _('Successfully ran v2 license check-in.'));
                            } else {
                                return array('error' => _('Could not get updated v2 license information from v2 API.'));
                            }

                            break;

                    }
                    break;
            }
        }
        return array('error' => _('Unknown API endpoint.'));
    }

    protected function system($verb, $args)
    {
        if (!is_admin()) { return array('error' => _('You are not authorized to use this portion of the API. Please contant your Nagios administrator for more info.')); }

        switch ($this->method)
        {
            case 'GET':
                switch ($verb)
                {
                    case 'users':
                        return $this->get_users(@$args[0]);
                        break;

                    case 'user':
                        return $this->get_user(@$args[0]);
                        break;

                    case 'status':
                        return $this->get_system_status(@$args[0]);
                        break;

                    case 'servers':
                        return $this->get_fused_servers(@$args[0]);
                        break;

                    case 'server':
                        return $this->get_fused_server_info(@$args[0]);
                        break;

                    case 'authserver':
                        return $this->get_auth_servers();
                        break;

                    default:
                        return array('error' => _('Unknown API endpoint.'));
                        break;
                }
                break;

            case 'POST':
                switch ($verb)
                {
                    case 'user':
                        return $this->add_user();
                        break;

                    case 'server':
                        return $this->fuse_server();
                        break;

                    case 'authserver':
                        return $this->add_auth_server();
                        break;

                    default:
                        return array('error' => _('Unknown API endpoint.'));
                        break;
                }
                break;

            case 'PUT':
                return array('info' => _('This section has not yet been implemented.'));
                break;

            case 'DELETE':
                switch ($verb)
                {
                    case 'user':
                        return $this->delete_user(@$args[0]);
                        break;

                    case 'server':
                        return $this->delete_server(@$args[0]);
                        break;

                    case 'authserver':
                        return $this->remove_auth_server($args[0]);
                        break;

                    default:
                        return array('error' => _('Unknown API endpoint.'));
                        break;
                }
                break;
        }
    }

    /**
     * Individual user info/account settings, status, etc
     * TODO: Implement actions for users
     */
    protected function user($verb, $args)
    {
        switch ($this->method)
        {
            case 'GET':
                return array('info' => _('This section has not yet been implemented.'));
                break;

            case 'POST':
                return array('info' => _('This section has not yet been implemented.'));
                break;

            case 'PUT':
                return array('info' => _('This section has not yet been implemented.'));
                break;

            case 'DELETE':
                return array('info' => _('This section has not yet been implemented.'));
                break;
        }
    }

    // Get user ID and username for all users
    protected function get_users()
    {
        $data = array();
        $opts = array();

        // Get users
        $users = get_users($opts);

        // Add in the data
        $data['records'] = count($users);
        foreach ($users as $u) {
            $data['users'][] = array('user_id' => $u['user_id'],
                                     'username' => $u['username']);
        }

        return $data;
    }

    // Get detailed information for specified user
    protected function get_user() {

        global $db;

        $args = $this->request;

        $user_id = intval(grab_array_var($args, 'user_id', ''));

        if (empty($user_id)) {
            return array('error' => 'No user ID was provided. Please provide a user ID and resubmit your request.');
        }

        if (!is_valid_user_id($user_id)) {
            return array('error' => 'No user was found with the supplied ID. Please ensure you have the correct ID and resubmit your request.');
        }
        
        $db->query('SELECT * FROM users WHERE user_id = :user_id');
        $db->bind(':user_id', $user_id);
        $db->exec();

        $user_info = $db->fetch_rows();
        $user_info = $user_info[0];

        $data['user_info'][] = array('user_id' => $user_info['user_id'],
                                     'username' => $user_info['username'],
                                     'name' => $user_info['name'],
                                     'email' => $user_info['email'],
                                     'level' => $user_info['level'],
                                     'enabled' => $user_info['enabled'],
                                     'times_logged_in' => $user_info['times_logged_in'],
                                     'api_enabled' => $user_info['api_enabled'],
                                     'recent_login_failures' => $user_info['recent_login_failures'],
                                     'account_locked' => $user_info['account_locked'],
                                     'last_login_time' => $user_info['last_login_time'],
                                );

        // Convert access level to something more meaningful for the user
        if ($data['user_info'][0]['level'] == 1) {
            $data['user_info'][0]['level'] = 'admin';
        } else {
            $data['user_info'][0]['level'] = 'user';
        }

        return $data;

    }

    /**
     * Authenticate the user and return an auth token that can be
     * used to log into the web interface.
     */
    protected function authenticate_user()
    {
        $args = $this->request;

        $username = strtolower(grab_array_var($args, "username", ""));
        $password = grab_array_var($args, "password", "");
        $valid_min = floatval(grab_array_var($args, "valid_min", 5));

        // Verify that everything we need is here
        if (empty($username) || empty($password)) {
            return array("error" => _("Must be valid username and password."));
        }

        // Verify user credentials (local, AD, LDAP, etc)
        $msg = array();
        $debug = array();
        if (check_login_credentials($username, $password, $msg, $debug)) {
            $user_id = get_user_id($username);
            $valid_until = time() + ($valid_min * 60);
            $auth_token = user_generate_auth_token($user_id, $valid_until);
        } else {
            $message = "";
            if (!empty($msg)) {
                $message = $msg[0]; // Only display first message
            }
            $data = array('username' => $username, 'message' => $message, 'error' => 1);
            return $data;
        }

        // Generate auth token and return it
        $data = array('username' => $username,
                      'user_id' => $user_id,
                      'auth_token' => $auth_token,
                      'valid_min' => $valid_min,
                      'valid_until' => date('r', $valid_until));

        return $data;
    }

    protected function add_user()
    {

        $missing = array();
        $args = $this->request;

        if (in_demo_mode()) {
            return array("error" => _("Can not use this action in demo mode."));
        }

        // Get values
        $username = grab_array_var($args, "username", "");
        $password = grab_array_var($args, "password", "");
        $email = grab_array_var($args, "email", "");
        $name = grab_array_var($args, "name", "");
        $level = grab_array_var($args, "level", "user");
        $enabled = grab_array_var($args, "enabled", 1);
        $forcechangepass = grab_array_var($args, "force_password_change", 1);
        $api_enabled = grab_array_var($args, 'api_access', 0);
        $email_info = grab_array_var($args, "email_info", 1);
        $language = grab_array_var($args, "language", "en_US");
        $date_format = grab_array_var($args, "date_format", 1);
        $number_format = grab_array_var($args, "number_format", 2);
        $readonly_user = grab_array_var($args, "account_enabled", 1);

        // Auth type variables
        ///// Keeping this here for now, since we are planning ad/ldap integration /////

        $auth_type = grab_array_var($args, "auth_type", "local");
        $allow_local = grab_array_var($args, "allow_local", 0);
        $auth_server_id = grab_array_var($args, "auth_server_id", "");
        $ldap_ad_username = grab_array_var($args, "ad_username", "");
        $ldap_ad_dn = grab_array_var($args, "ldap_dn", "");

        // Verify that everything we need is here
        $required = array('username', 'password', 'email', 'name');
        foreach ($required as $r) {
            if (!array_key_exists($r, $args)) {
                $missing[] = $r;
            }
        }

        // Verify auth level
        $auth = '';
        if ($level == "user") {
            $level = USER_LEVEL_NONPRIV;
        } else if ($level == "admin") {
            $level = USER_LEVEL_ADMIN;
        } else {
            $auth = array('auth_level' => _('Must be either user or admin.'));
        }

        //Verify auth types
        if ($auth_type != 'local') {
            if (empty($auth_server_id)) {
                $missing[] = "auth_server_id";
            }
        }

        // We are missing required fields
        if (count($missing) > 0 || !empty($auth)) {
            $errormsg = array('error' => _('Could not create user. Missing required fields.'));
            if (!empty($auth)) { $errormsg['messages'] = $auth; }
            if (count($missing) > 0) { $errormsg['missing'] = $missing; }
            return $errormsg;
        }

        // If everything looks okay then proceed to create the user
        // $user_id = update_user($username, $password, $name, $email, $level, $forcechangepass, $add_contact, $api_enabled, $errmsg);

        // Make sure there isn't already a user with the provided username
        if (get_user_id($username)) {
            return array('error' => 'A user with that username already exists. Please choose a unique username and try again.');
        }

        $user_id = add_user($username, $password, $name, $email, $enabled, $level);

        set_user_meta($user_id, 'name', $name);
        set_user_meta($user_id, 'language', $language);
        set_user_meta($user_id, "date_format", $date_format);
        set_user_meta($user_id, "number_format", $number_format);

        // Insert key into database
        if ($api_enabled) {
            // Generate the API key
            $api_key = random_string(64);

            set_user_attr('api_enabled', $api_access, $user_id);
            set_user_attr('api_key', $api_key, $user_id);
        }

        // Settings from the AD/LDAP component
        set_user_meta("auth_type", $auth_type, true, $user_id);
        set_user_meta("allow_local", $allow_local, true, $user_id);
        set_user_meta("auth_server_id", $auth_server_id, true, $user_id);

        // Set authentication settings
        set_user_meta("auth_type", $auth_type, false, $user_id);
        set_user_meta("allow_local", $allow_local, false, $user_id);
        if ($auth_type == 'ad') {
            set_user_meta("ldap_ad_username", $ldap_ad_username, false, $user_id);
        } else if ($auth_type == 'ldap') {
            set_user_meta("ldap_ad_dn", $ldap_ad_dn, false, $user_id);
        }

        if ($email_info) {
            $url = get_option("internal_url");

            // Use this for debug output in PHPmailer log
            $debugmsg = "";

            // Set where email is coming from for PHPmailer log
            $send_mail_referer = "api/includes/utils-api.inc.php > API - Add User";

            $msg = _("An account has been created for you to access Nagios Fusion.  You can login using the following information:\n\nUsername: %s\nPassword: %s\nURL: %s\n\n");
            $message = sprintf($msg, $username, $password, $url);
            $opts = array(
                "to" => $email,
                "from" => "Nagios Fusion",
                "subject" => _("Nagios Fusion Account Created"),
                "message" => $message,
            );
            send_email($opts, $debugmsg, $send_mail_referer);
        }

        // Log it
        // if ($level == L_GLOBALADMIN) {
        //     send_to_audit_log("User account '" . $username . "' was created with GLOBAL ADMIN privileges", AUDITLOGTYPE_SECURITY);
        // }

        $ret = array('success' => _('User account') . ' ' . $username . ' ' . _('was added successfully!'));
        $ret['user_id'] = $user_id;
        return $ret;
    }

    protected function delete_user($user_id)
    {
        $args = $this->request;

        // The user may try to pass the user_id as a request parameter
        if (empty($user_id)) {
            $user_id = grab_array_var($args, 'user_id', '');
        }
        $user_id = intval($user_id);

        if (in_demo_mode()) {
            return array("error" => _("Can not use this action in demo mode."));
        }

        if (!is_valid_user_id($user_id)) {
            return array("error" => _('User with') . ' ID ' . $user_id . ' ' . _('does not exist.'));
        }

        if (isset($_SESSION['user_id'])) {
            if ($user_id == $_SESSION["user_id"]) {
                return array("error" => _('Cannot delete the user you are currently using.'));
            }      
        }

        delete_user($user_id);

        return array('success' => _('User successfully deleted'));
    }

    protected function get_system_status()
    {
        global $db;

        $query = 'select value from sysstat where metric in (:load, :mem, :swap, :cpu)';

        $bind_array = array(
            ':load' => 'sysstat_load',
            ':mem' => 'sysstat_memory',
            ':swap' => 'sysstat_swap',
            ':cpu' => 'sysstat_cpustat'
        );

        $data = $db->exec_query($query, $bind_array);

        if ($data) {

            $status = array();

            $status['load'] = unserialize(base64_decode($data[0]['value']));
            $status['memory'] = unserialize(base64_decode($data[1]['value']));
            $status['swap'] = unserialize(base64_decode($data[2]['value']));
            $status['cpu'] = unserialize(base64_decode($data[3]['value']));

            $status = json_encode($status);
            
            return $status;
        }
    }

    protected function get_fused_servers()
    {
        global $db;

        $data = array();

        $query = 'select * from servers';

        $servers = $db->exec_query($query);
        
        if (empty($servers)) {
            return array("error" => "No fused servers were found.");
        }

        $data['records'] = count($servers);

        if ($servers) {
            foreach ($servers as $server => $info) {
                $data['servers'][] = array('server_id' => $info['server_id'],
                                           'name' => $info['name'],
                                           'address' => $info['url']
                                     );
        }
        
            $data = json_encode($data);
            return $data;            
        }
    }

    protected function get_fused_server_info() {

        global $db;

        $args = $this->request;

        $server_id = intval(grab_array_var($args, 'server_id', null));

        if (!server_id_exists($server_id)) {
            return array('error' => "No server with an ID of $server_id was found. Please ensure you have the correct ID and resubmit your request.");
        }

        $db->query('SELECT * FROM servers WHERE server_id = :server_id');
        $db->bind(':server_id', $server_id);
        $db->exec();

        $server_info = $db->fetch_rows();
        $server_info = $server_info[0];

        $authentication_type_dict = array(
            0 => 'Fusekey',
            1 => 'Session',
            2 => 'Basic',
            3 => 'API'
        );

        $data['server_info'] = array(
            'server_id' => $server_info['server_id'],
            'name' => $server_info['name'],
            'server_type' => server_type_to_readable($server_info['server_type']),
            'authentication_type' => $authentication_type_dict[$server_info['authentication_type']],
            'url' => $server_info['url'],
            'cgi_bin' => $server_info['cgi_bin'],
            'location' => $server_info['location'],
            'notes' => $server_info['notes'],
            'username' => $server_info['username'],
            'last_auth_time' => $server_info['last_auth_time'],
            'polling_interval' => $server_info['polling_interval'],
            'timezone' => $server_info['timezone']
        );

        // Convert null values to empty strings for consistency
        foreach($data['server_info'] as $key => $value) {
            if ($value == null) {
                $data['server_info'][$key] = "";
            }
        }

        return $data;
    }

    protected function fuse_server() {

        global $db;

        $args = $this->request;

        // Grab global intervals
        $global_auth_interval = get_option('auth_interval', DEFAULT_AUTH_INTERVAL);
        $global_polling_interval = get_option('polling_interval', DEFAULT_POLLING_INTERVAL);

        $enabled = grab_array_var($args, "enabled", 1);
        $server_name = grab_array_var($args, "server_name", "");
        $server_type = grab_array_var($args, "server_type", "");
        $url = grab_array_var($args, "url", "");
        $authentication_type = grab_array_var($args, "auth_type", 0);
        $username = grab_array_var($args, "username", ""); 
        $password = grab_array_var($args, "password", "");
        $fusekey = grab_array_var($args, "fusekey", "");
        $cgi_bin = grab_array_var($args, "cgi_bin", "");
        $ssl_hostname_verify = grab_array_var($args, "ssl_hostname_verify", 0);
        
        // Intervals in seconds
        $auth_interval = intval(grab_array_var($args, "auth_interval", $global_auth_interval));
        $polling_interval = intval(grab_array_var($args, "poll_interval", $global_polling_interval));
        
        $location = grab_array_var($args, "location", "");
        $timezone = grab_array_var($args, "timezone", "America/Chicago");
        $notes = grab_array_var($args, "notes", "");

        // Verify that everything we need is here
        $missing = array();
        $required = array('server_name', 'server_type', 'url', 'username', 'password');
        foreach ($required as $r) {
            if (!array_key_exists($r, $args)) {
                $missing[] = $r;
            }
        }

        // We are missing required fields
        if (count($missing) > 0 || !empty($auth)) {
            $errormsg = array('error' => _('Could not fuse server. Missing required fields.'));
            if (count($missing) > 0) { $errormsg['missing'] = $missing; }
            return $errormsg;
        }

        // Make sure the supplied auth type is valid
        $valid_server_types = array('xi', 'core', 'nls');
        if (!in_array(strtolower($server_type), $valid_server_types)) {
            return array('error' => 'The supplied server type was invalid. Please consult the documentation and resubmit your request.');
        }

        // Dictionary for converting user data to our backend server type definitions
        $server_type_dict = array(
            'xi' => SERVER_TYPE_XI,
            'core' => SERVER_TYPE_CORE,
            'nls' => SERVER_TYPE_NLS,
            'nna' => SERVER_TYPE_NNA
        );

        // Convert the server type string to constant using above dict
        $server_type = $server_type_dict[$server_type];

        // Set authentication type based on server type
        $authentication_type = -1; 
        if ($server_type == SERVER_TYPE_CORE) {
            $authentication_type = AUTH_TYPE_BASIC;
        } else if ($server_type == SERVER_TYPE_XI) {
            if (!empty($fusekey)) {
                $authentication_type = AUTH_TYPE_FUSEKEY;
            } else {
                $authentication_type = AUTH_TYPE_SESSION;
            }
        } else if ($server_type == SERVER_TYPE_NLS) {
            $authentication_type = AUTH_TYPE_API;
        } else if ($server_type == SERVER_TYPE_NNA) {
            $authentication_type = AUTH_TYPE_API;
        }

        // Make sure the timezone supplied is valid
        if (!is_valid_timezone($timezone)) {
            return array('error' => 'The supplied timezone was invalid. Please consult the documentation and resubmit your request.');
        }

        // Make sure a server with the provided name doesn't already exist
        if (get_server($server_name) == true) {
            return array ('error' => "The specified server name {$server_name} already exists. Please choose a unique server name and resubmit your request.");
        }

        // Everything checks out, add the server
        $server_id = add_server($enabled, $server_name, $server_type, $authentication_type, $url, $cgi_bin, $ssl_hostname_verify, $location, $timezone, $notes);
    
        set_server_attr('password', _encrypt($password), $server_id);
        set_server_attr('username', $username, $server_id);

        // If auth interval is different than global and isn't empty, set it
        if ($auth_interval != $global_auth_interval && !empty($auth_interval))
            set_server_attr('auth_interval', $auth_interval, $server_id);

        // If polling interval is different than global and isn't empty, set it
        if ($polling_interval != $global_polling_interval && !empty($polling_interval))
            set_server_attr('polling_interval', $polling_interval, $server_id);

        // Set a fusekey if we have one
        if (isset($fusekey)) {
            set_server_attr('fusekey', $fusekey, $server_id);
        }

        return array('success' => 'Server successfully added with an ID of ' . $server_id);

    }

    protected function delete_server($server_id) {

        global $db;

        if (!isset($server_id)) {
            return array('error' => 'No valid server ID provided. Please specify a valid server ID and resubmit your request.');
        }

        $was_deleted = delete_server($server_id);

        if ($was_deleted) {
            return array('success' => "Server with ID of {$server_id} successfully removed.");
        } else {
            return array('error' => 'Server was not removed. Please ensure a valid server ID was specified and resubmit your request.');
        }
    }

    protected function get_system_info()
    {
        $ret = array(
            'product' => get_product_name(true),
            'version' => get_product_version(),
            'version_major' => get_product_version('major'),
            'version_minor' => get_product_version('minor'),
            'build_id' => get_build_id(),
            'release' => get_product_release()
        );
        return $ret;
    }

    protected function add_fuse_host()
    {
        global $ccmDB;
        $args = $this->request;
        $host = $ccmDB->escape_string($args['host']);

        if (empty($host)) {
            return array('error' => _('No host given.'));
        }

        // Add the fused host to the fused host list
        $fuse_hosts = explode(',', get_option('frame_options_allowed_fusion_hosts', ''));
        $add = true;
        foreach ($fuse_hosts as $k => $h) {
            if ($h == $host) {
                $add = false;
            }
        }
        if ($add) {
            $fuse_hosts[] = $host;
        }
        set_option('frame_options_allowed_fusion_hosts', implode(',', $fuse_hosts));

        return array('success' => _("Fusion host added successfully."));
    }

    protected function remove_fuse_host($host='')
    {
        if (empty($host)) {
            return array('error' => _('No host given.'));
        }

        // Remove fuse host from the list
        $fuse_hosts = explode(',', get_option('frame_options_allowed_fusion_hosts', ''));
        if ($fuse_hosts > 0) {
            foreach ($fuse_hosts as $k => $h) {
                if ($h == $host) {
                    unset($fuse_hosts[$k]);
                }
            }
            set_option('frame_options_allowed_fusion_hosts', implode(',', $fuse_hosts));
        } else {
            return array('error' => _("No fuse hosts exist."));
        }

        return array('success' => _("Fusion host removed successfully."));
    }

    // =========================================
    // Auth Servers
    // =========================================

    protected function get_auth_servers()
    {
        // Check if we are getting a single auth server by ID
        $server_id = grab_request_var('server_id', '');
        $data = array('records' => 0, 'authservers' => array());

        // Remove the auth server from the DB
        if (empty($server_id)) {
            $servers = auth_server_list();
            $data = array('records' => count($servers), 'authservers' => $servers);
        } else {
            $server = auth_server_get($server_id);
            if ($server) {
                $data = array('records' => 1, 'authservers' => array($server));
            }
        }

        return $data;
    }

    protected function add_auth_server()
    {
        $missing = array();
        $args = $this->request;
        $valid_methods = array('ldap', 'ad');

        $enabled = grab_request_var($args, 'enabled', 1);
        $conn_method = grab_array_var($args, 'conn_method', '');
        $base_dn = grab_array_var($args, 'base_dn', '');
        $domain_controllers = grab_array_var($args, 'domain_controllers', '');
        $security_level = grab_array_var($args, 'security_level', 'none');
        $ldap_host = grab_array_var($args, 'ldap_host', '');
        $ldap_port = grab_array_var($args, 'ldap_port', '');
        $account_suffix = grab_array_var($args, 'account_suffix', '');

        // Add default LDAP port if we need to
        if ($conn_method == 'ldap') {
            if (empty($ldap_port)) {
                $ldap_port = '389';
            }
        }

        // Verification checks
        if (!in_array($conn_method, $valid_methods)) {
            return array('error' => _('The conn_method you specified is not allowed. Choose ad or ldap only.'));
        }

        // Create the auth server
        $server = array(
                    "id" => uniqid(),
                    "enabled" => $enabled,
                    "conn_method" => $conn_method,
                    "ad_account_suffix" => $account_suffix,
                    "ad_domain_controllers" => $domain_controllers,
                    "base_dn" => $base_dn,
                    "security_level" => $security_level,
                    "ldap_port" => $ldap_port,
                    "ldap_host" => $ldap_host
                );
        $server_id = auth_server_add($server);

        return array('success' => 'Auth server created successfully.',
                     'server_id' => $server_id);
    }

    protected function remove_auth_server($id='')
    {
        if (empty($id)) {
            return array('error' => _('No auth server ID given.'));
        }

        // Remove the auth server
        $removed = auth_server_remove($id);

        // If the server wasn't removed, we should not save it and error out
        if (!$removed) {
            return array('error' => _("Could not remove auth server with ID given. Please ensure the correct ID is being specified and resubmit your request."));
        }

        return array('success' => _("Removed auth server successfully."));
    }

}
