#!/usr/bin/php
<?php
/*
  vim: set expandtab tabstop=4 softtabstop=4 shiftwidth=4:
  Codificación: UTF-8
  +----------------------------------------------------------------------+
  | Issabel version 4.0.0                                                |
  | http://www.issabel.org                                               |
  +----------------------------------------------------------------------+
  | Copyright (c) 2017 Issabel Foundation                                |
  | Copyright (c) 2006 Palosanto Solutions S. A.                         |
  +----------------------------------------------------------------------+
  | The contents of this file are subject to the General Public License  |
  | (GPL) Version 2 (the "License"); you may not use this file except in |
  | compliance with the License. You may obtain a copy of the License at |
  | http://www.opensource.org/licenses/gpl-license.php                   |
  |                                                                      |
  | Software distributed under the License is distributed on an "AS IS"  |
  | basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See  |
  | the License for the specific language governing rights and           |
  | limitations under the License.                                       |
  +----------------------------------------------------------------------+
  | The Initial Developer of the Original Code is PaloSanto Solutions    |
  +----------------------------------------------------------------------+
*/
require_once 'Console/Getopt.php';

$g_mysql_running = FALSE;

define('BACKTITLE', 'Issabel password configuration');
$PASSWD_PATH = array('/etc/issabel.conf');
//$PASSWD_PATH = array('/etc/elastix.conf','/etc/issabel.conf');
define('REGEXP_VALID_PASSWORD', '/^([a-zA-Z0-9 .@=_!-]+)$/');

// Parse command-line options
$opt = Console_Getopt::getopt($argv, '', array(
    'init',                 // prepare passwords for first-time use
    'change',               // change existing set of passwords
    'cli',                  // prepare passwords without graphical interface
    'ensure-jwt-secret',    // create the PBX API JWT secret if it is missing
));
if (PEAR::isError($opt)) error_exit($opt->getMessage()."\n");
//validateOptions($opt);
foreach ($opt[0] as $option) switch ($option[0]) {
case '--init':
    if (!ensure_pbxapi_jwt_secret()) exit(1);
    $ret = action_initPasswords($opt);
    if($ret) {
        action_changeLanguage();
        action_changeSip();
        system("/usr/bin/issabel-patreon > /dev/null 2>&1");
        exit(0);
    }
    exit(1);
case '--change':
    if (!ensure_pbxapi_jwt_secret()) exit(1);
    exit(action_changePasswords($opt) ? 0 : 1);
case '--cli':
    exit(action_cliPasswords($opt) ? 0 : 1);
case '--ensure-jwt-secret':
    exit(ensure_pbxapi_jwt_secret() ? 0 : 1);
}
error_exit("No action specified (--init, --change, --cli or --ensure-jwt-secret)\n");

function error_exit($sMsg, $errorcode = 1)
{
    fwrite(STDERR, $sMsg);
    exit($errorcode);
}

function action_cliPasswords($opt)
{
    if (empty($opt[1]))
    {
         exit("Parameter for --cli must be nonempty. 'init' and 'change' are valid parameters".PHP_EOL);
    }

    switch($opt[1][0]){
        case "init":
            if (!ensure_pbxapi_jwt_secret()) return FALSE;
            validate_passwd_cli($opt);

            $bFirstBoot = FALSE;
            $passwords = load_keys();
            if (!isset($passwords['mysqlrootpwd'])) {
                $bFirstBoot = TRUE;

                check_mysql_running();

                // Prompt for the MySQL password for this system
                if (!issabel_cli_mysql_passwd($opt[1][1])) return FALSE;
            } else {
                // dialog_infobox('ERROR', 'Password configuration already present.', 7, 70);
                print "Password configuration already present.\n";
                die();
            }

            apply_updatesql();

            // Init web passwords if first boot
            if ($bFirstBoot) {
                check_mysql_running();
        
                if (!issabel_cli_web_passwd(TRUE,$opt[1][2])) return FALSE;
            }
            return TRUE;
        break;

        case "change":
                if (!ensure_pbxapi_jwt_secret()) return FALSE;
                validate_passwd_cli($opt);

                file_conf_present();
                check_mysql_running();
        
                // Prompt for the MySQL password for this system
                if (!issabel_cli_mysql_passwd($opt[1][1])) return FALSE;
        
                // Prompt for web password
                if (!issabel_cli_web_passwd(TRUE,$opt[1][2])) return FALSE;

                return TRUE;
        break;

        default:
            exit("Not a valid option for cli: init or change".PHP_EOL);
        break;
    }    
}

function generate_self_signed_with_extended_key_usage() {
    // OSX Catalina requires self sigend certificates to have extendedkeyusage section, something that is not present in stock Centos 7
    system('/usr/local/sbin/generate_self_signed.sh > /dev/null 2>&1');
    writeAsteriskPEMfile();
}

function apply_updatesql(){
    // Read the MySQL root password for this system
    $passwords = load_keys();
        
    // The scripts placed in /var/spool/issabel-mysqldbscripts should be executed now.
    foreach (glob('/var/spool/issabel-mysqldbscripts/*.sql') as $dbscript) {
        if (file_exists($dbscript)) {
            check_mysql_running();            

            dialog_infobox('Status', "Applying MariaDB script $dbscript ...", 7, 70);
            //print "Applying MariaDB script $dbscript ...\n";
            $output = $retval = NULL;
            exec('mysql -u root '.escapeshellarg('-p'.$passwords['mysqlrootpwd']).' < '.escapeshellarg($dbscript), $output, $retval);
            if ($retval != 0) return FALSE;
                unlink($dbscript);
        }
    }
}

function file_conf_present(){
    global $PASSWD_PATH;
    foreach($PASSWD_PATH as $conffile) {
        if (!file_exists($conffile)) {
            fwrite(STDERR, "Password configuration $conffile not present.");
            return FALSE;
        }
    }
    if (!file_exists('/etc/amportal.conf')) {
        fwrite(STDERR, 'Configuration file /etc/amportal.conf not present');
        return FALSE;
    }
}

function validate_passwd_cli($opt)
{
    if (!isset($opt[1][1])){
        exit("MariaDB root password must be nonempty.".PHP_EOL);
    }
    if (!isset($opt[1][2])){
        exit("Admin root password must be nonempty.".PHP_EOL);
    }
    char_validate_passwd($opt[1][1], 'MariaDB');
    char_validate_passwd($opt[1][2], "Admin");
}


function char_validate_passwd($passwd, $type_passwd)
{
    if (!preg_match(REGEXP_VALID_PASSWORD, $passwd)) {
        $passwd = '';              
           exit("$type_passwd password may only contain alphanumeric characters, spaces, or the following: .@=_!-.".PHP_EOL);
    }
}

function issabel_cli_mysql_passwd($sMySQL_passwd)
{
    if (!set_mysql_root_password($sMySQL_passwd)) return FALSE;
    if (!set_cyrus_password($sMySQL_passwd)) return FALSE;
    
    dialog_infobox('Status', 'The password for MariaDB and Cyrus admin were successfully changed!', 7, 70);
    //print "The password for MariaDB and Cyrus admin were successfully changed!\n";
    sleep(3);

    return TRUE;
}

function issabel_cli_web_passwd($bRestart,$sIssabelPBX_passwd)
{
    $res_connection = test_MySQLcon($sIssabelPBX_passwd);
    
    $updateList = phase1_mkarray_updates_passwd($res_connection['qPwd'],$sIssabelPBX_passwd);

    phase2_cond_updates($updateList,$res_connection['db'],$sIssabelPBX_passwd);

    // Save newly-updated password
    $res_connection['psswd']['amiadminpwd'] = $sIssabelPBX_passwd;
    save_keys($res_connection['psswd']);

    if ($bRestart) {    
        restart_amportal();
    }

    return TRUE;
}

function restart_amportal() {
    dialog_infobox('Status', 'Restarting amportal...', 7, 70);
    system('/var/lib/asterisk/bin/retrieve_conf > /dev/null 2>&1');
    system('/usr/sbin/asterisk -rx "manager reload" > /dev/null 2>&1');
    system('/usr/sbin/amportal a r > /dev/null 2>&1');
    system('/usr/sbin/amportal chown > /dev/null 2>&1');
    system('/usr/sbin/amportal restart > /dev/null 2>&1');
    dialog_infobox('Status', 'Restarted', 7, 70);
    dialog_infobox('Status', ' O O O\n O O O\n O O O\n   O\nIssabel',7,11);
}

function action_initPasswords($opt)
{
    global $langs;

    $bFirstBoot = FALSE;
    $passwords = load_keys();
    if (!isset($passwords['mysqlrootpwd'])) {
        $bFirstBoot = TRUE;

        check_mysql_running();

        // Prompt for the MySQL password for this system
        if (!issabel_prompt_mysql_passwd()) return FALSE;
    } else {
        //dialog_infobox('Status', 'Password configuration already present.', 7, 70);
        print "Password configuration already present.\n";
        die();
    }

    apply_updatesql();

    generate_self_signed_with_extended_key_usage();

    // Init web passwords if first boot
    if ($bFirstBoot) {
        check_mysql_running();
        check_etc_issabel();
    
        if (!issabel_prompt_web_passwd(FALSE)) return FALSE;
    }

    return TRUE;
}

function action_changePasswords($opt)
{
    file_conf_present();
    
    check_mysql_running();
    
    // Prompt for the MySQL password for this system
    if (!issabel_prompt_mysql_passwd()) return FALSE;
    
    // Prompt for web password
    if (!issabel_prompt_web_passwd(TRUE)) return FALSE;

    return TRUE;
}

function action_changeLanguage() {
    system("/usr/bin/issabel-change-language > /dev/null 2>&1");
}

function action_changeSip() {
    system("/usr/bin/issabel-change-sip > /dev/null 2>&1");
    restart_amportal();
}

function check_mysql_running()
{
    global $g_mysql_running;

    if ($g_mysql_running) return TRUE;
    
    $output = $retval = NULL;
    exec('/sbin/service mariadb status', $output, $retval);
    if ($retval == 0) {
        exec('/sbin/service mariadb start', $output, $retval);
        if ($retval) die("FATAL: unable to start MariaDB database server!\n");
    }
    $g_mysql_running = TRUE;
}

function issabel_prompt_mysql_passwd()
{
    $sDialogPurpose =
        "The Issabel system uses the open-source database engine MariaDB for " .
        "storage of important telephony information. In order to protect your " .
        "data, a master password must be set up for the database.\n\n" .
        "This screen will now ask for a password for the 'root' account of ".
        "MariaDB.\n\n";

    // Read and set new MySQL root password
    $sMySQL_passwd = array('', '');
    while ($sMySQL_passwd[0] == '') {
        while ($sMySQL_passwd[0] == '') {
            $retstatus = dialog_passwordbox(
                BACKTITLE." (Screen 1 of 4)",
                "$sDialogPurpose Please enter your new MariaDB root password:",
                16, 70);
            if ($retstatus['retval'] != 0) return FALSE; 
            $sMySQL_passwd[0] = $retstatus['password'];
            if ($sMySQL_passwd[0] == '') {
                dialog_msgbox(BACKTITLE,
                    'MariaDB root password must be nonempty.',
                    7, 40);
            } elseif (!preg_match(REGEXP_VALID_PASSWORD, $sMySQL_passwd[0])) {
                $sMySQL_passwd[0] = '';              
                dialog_msgbox(BACKTITLE,
                    'Admin password may only contain alphanumeric characters, spaces, or the following: .@=_!-.',
                    7, 40);
            }
        }
        while ($sMySQL_passwd[1] == '') {
            $retstatus = dialog_passwordbox(
                BACKTITLE." (Screen 2 of 4)",
                "Please (re)confirm your new MariaDB root password:",
                10, 70);
            if ($retstatus['retval'] != 0) return FALSE;
            $sMySQL_passwd[1] = $retstatus['password'];
        }
        
        if ($sMySQL_passwd[0] != $sMySQL_passwd[1]) {
            dialog_msgbox(BACKTITLE,
                'Password and confirmation do not match!',
                7, 40);
            $sMySQL_passwd[0] = $sMySQL_passwd[1] = '';
        }
    }

    if (!issabel_cli_mysql_passwd($sMySQL_passwd[0])) return FALSE;

    return TRUE;
}

function set_mysql_root_password($sNewPassword)
{
    // Load old mysql password from file, if it exists
    $sMySQL_oldpasswd = NULL;
    $passwords = load_keys();
    if (isset($passwords['mysqlrootpwd']))
        $sMySQL_oldpasswd = $passwords['mysqlrootpwd'];
    
    // Set new MySQL root password, immediately save on success
    try {
        $db = new PDO('mysql:host=localhost', 'root', $sMySQL_oldpasswd);
        $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

        // MySQL does not support preparing a GRANT statement
        $quotedPwd = $db->quote($sNewPassword);
        if ($quotedPwd === FALSE) {
            fwrite(STDERR, 'FATAL: failed to quote new MySQL password');
            return FALSE;
        }
        $db->exec("GRANT USAGE ON *.* TO root@localhost IDENTIFIED BY $quotedPwd");
        $db->exec("GRANT USAGE ON *.* TO root IDENTIFIED BY $quotedPwd");
        $db = NULL;

        $passwords['mysqlrootpwd'] = $sNewPassword;
        save_keys($passwords);
    } catch (PDOException $e) {
        fwrite(STDERR, 'FATAL: unable to change mysql root password: '.$e->getMessage()."\n");
        return FALSE;
    }
    
    return TRUE;
}

function set_cyrus_password($sNewPassword)
{
    // Run saslpasswd2 to set the new password
    $r = popen('/usr/sbin/saslpasswd2 -c cyrus -u example.com', 'w');
    if (!is_resource($r)) {
        fwrite(STDERR, "FATAL: failed to open pipe to saslpasswd2\n");
        return FALSE;
    }
    fwrite($r, $sNewPassword);
    $ret = pclose($r);
    if ($ret != 0) {
        fwrite(STDERR, "ERR: unable to set new cyrus password via saslpasswd2\n");
        return FALSE;
    }
    
    // Store just-changed password
    $passwords = load_keys();
    $passwords['cyrususerpwd'] = $sNewPassword;
    save_keys($passwords);
    
    chmod('/etc/sasldb2', 0644);
    
    return TRUE;
}

function issabel_prompt_web_passwd($bRestart)
{
    $sDialogPurpose =
        "Several Issabel components have administrative interfaces that can " .
        "be used through the Web. A web login password must be set for these " .
        "components in order to prevent unauthorized access to these " .
        "administration interfaces.\n\n" .
        "This screen will now ask for a password for user 'admin' that will " .
        "be used for: Issabel Web Login and IssabelPBX.\n\n";

    // Read and set new IssabelPBX admin password. This procedure works with IssabelPBX 2.7.0
    $sIssabelPBX_passwd = array('', '');
    while ($sIssabelPBX_passwd[0] == '') {
        while ($sIssabelPBX_passwd[0] == '') {
            $retstatus = dialog_passwordbox(
                BACKTITLE." (Screen 3 of 4)",
                "$sDialogPurpose Please enter your new password for IssabelPBX 'admin':",
                16, 70);
            if ($retstatus['retval'] != 0) return FALSE;
            $sIssabelPBX_passwd[0] = $retstatus['password'];
            if ($sIssabelPBX_passwd[0] == '') {
                dialog_msgbox(BACKTITLE,
                    'Admin password must be nonempty.',
                    7, 40);
            } elseif (!preg_match(REGEXP_VALID_PASSWORD, $sIssabelPBX_passwd[0])) {
                $sIssabelPBX_passwd[0] = '';
                dialog_msgbox(BACKTITLE,
                    'Admin password may only contain alphanumeric characters, spaces, or the following: .@=_!<>-.',
                    7, 40);
            } else{
                $passwordCheck = "echo $sIssabelPBX_passwd[0] | cracklib-check";
                $resultCheck = explode(':',exec($passwordCheck));
                $resultCheck[1] = trim($resultCheck[1] ,$character_mask = " \t\n\r\0\x0B" );
                if ($resultCheck[1] != 'OK') {
                    $MessageCheck = "Password Check: " . $resultCheck[1] . ". Continue anyway?";
                    $continue = dialog_yesno('Warning',
                    $MessageCheck,
                    7, 40);
                    if ($continue == 1) {
                        $sIssabelPBX_passwd[0] = '';
                    }
                }
            }
        }
        while ($sIssabelPBX_passwd[1] == '') {
            $retstatus = dialog_passwordbox(
                BACKTITLE." (Screen 4 of 4)",
                "Please (re)confirm your new password for IssabelPBX 'admin':",
                10, 70);
            if ($retstatus['retval'] != 0) return FALSE;
            $sIssabelPBX_passwd[1] = $retstatus['password'];
        }
        if ($sIssabelPBX_passwd[0] != $sIssabelPBX_passwd[1]) {
            dialog_msgbox(BACKTITLE,
                'Password and confirmation do not match!',
                7, 40);
            $sIssabelPBX_passwd[0] = $sIssabelPBX_passwd[1] = '';
        }
    }
    $res_connection = test_MySQLcon($sIssabelPBX_passwd[0]);
    
    $updateList = phase1_mkarray_updates_passwd($res_connection['qPwd'],$sIssabelPBX_passwd[0]);

    phase2_cond_updates($updateList,$res_connection['db'],$sIssabelPBX_passwd[0]);

    // Save newly-updated password
    $res_connection['psswd']['amiadminpwd'] = $sIssabelPBX_passwd[0];
    save_keys($res_connection['psswd']);

    if ($bRestart) {    
        restart_amportal();
    }

    return TRUE;
}

function test_MySQLcon($passwd)
{
    // Open database connection used in several updates
    $passwords = load_keys();
    if (!isset($passwords['mysqlrootpwd'])) {
        fwrite(STDERR, "FATAL: unable to extract MariaDB root password\n");
        return FALSE;
    }
    try {
        $db = new PDO('mysql:host=localhost', 'root', $passwords['mysqlrootpwd']);
        $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    } catch (PDOException $e) {
        fwrite(STDERR, 'FATAL: unable to open database connection: '.$e->getMessage()."\n");
        return FALSE;
    }

    // MySQL does not support preparing a GRANT statement
    $quotedPwd = $db->quote($passwd);
    if ($quotedPwd === FALSE) {
        fwrite(STDERR, "FATAL: failed to quote new password\n");
        return FALSE;
    }
    $res_MySQLcon = array(
                    'qPwd'     =>    $quotedPwd,
                    'psswd'    =>   $passwords,
                    'db'       =>   $db,
                    );
    return $res_MySQLcon;
}

function phase1_mkarray_updates_passwd($qPwd,$passwd)
{
    /* The following list defines one element for each known password that needs
     * to be changed to match the password entered above. Each element defines
     * targets on sqlite, mysql, or files. For sqlite, a list of database files
     * is listed, along with the update query and the query parameters. For
     * mysql, the same is done, but the update must indicate the schema name in
     * all of the tables. For files, each file has a PCRE regexp that locates
     * the target line, optionally with a target old password, and the contents
     * of the line that includes the new password. Some cases need additional
     * evaluation and are added dynamically after this declaration. 
     * 
     * For mysql case, a third optional item contains a list of tables to check
     * for. If supplied, all tables listed must be present for the query to 
     * apply.
     */
    $updateList = array(
        'IssabelPBX database password' => array(
            'sqlite'    =>  NULL,
            'mysql'     =>  array(
                array(
                    "GRANT USAGE ON *.* TO asteriskuser@localhost IDENTIFIED BY $qPwd",
                    array()
                ),
                array(
                    "GRANT ALL ON asterisk.* TO asteriskuser@localhost",
                    array()
                ),   
                array(
                    "GRANT ALL ON asteriskcdrdb.* TO asteriskuser@localhost",
                    array()
                ),   
            ),
            'file'      =>  array(
                array(
                    '/etc/amportal.conf',
                    '^AMPDBPASS=',
                    'AMPDBPASS='.$passwd,
                ),
                array(
                    '/etc/asterisk/res_mysql.conf',
                    '^dbpass\s*=\s*',
                    'dbpass = '.$passwd,
                ),
                array(
                    '/etc/asterisk/res_config_mysql.conf',
                    '^dbpass\s*=\s*',
                    'dbpass = '.$passwd,
                ),
                array(
                    '/etc/asterisk/cbmysql.conf',
                    '^password=',
                    'password='.$passwd,
                ),
                array(
                    '/etc/asterisk/cdr_mysql.conf',
                    '^password\s*=\s*',
                    'password = '.$passwd,
                ),
                array(
                    '/etc/asterisk/extensions_additional.conf',
                    '^AMPDBPASS =',
                    'AMPDBPASS ='.$passwd,
                ),
            ),
        ),
        'IssabelPBX admin password' => array(
            'sqlite'    =>  NULL,
            'mysql'     =>  array(
                array(
                    'UPDATE asterisk.ampusers SET password_sha1 = SHA1(?) WHERE username = ?',
                    array($passwd, 'admin')
                ),
            ),
            'file'      =>  array(
                array(
                    '/etc/issabelpbx.conf',
                    '^\$amp_conf\[\'AMPDBPASS\'\]\s*=\s*',
                    '$amp_conf[\'AMPDBPASS\']  = \''.$passwd.'\';',
                ),
            ),
        ),
        'Asterisk Manager Interface password' => array(
            'sqlite'    =>  NULL,
            'mysql'     =>   array(
                array(
                    'UPDATE asterisk.issabelpbx_settings SET value = ? WHERE keyword = ?',
                    array($passwd,'AMPMGRPASS'),
                    array(array('asterisk', 'issabelpbx_settings'))
                ),
            ),
            'file'      =>  array(
                array(
                    '/etc/asterisk/manager.conf',
                    array(
                        'custom', 'change_ami_password'
                    ),
                    'secret = '.$passwd,
                ),
                array(
                    '/etc/amportal.conf',
                    '^AMPMGRPASS=',
                    'AMPMGRPASS='.$passwd,
                ),
                array(
                    '/etc/asterisk/extensions_additional.conf',
                    '^AMPMGRPASS =',
                    'AMPMGRPASS ='.$passwd,
                ),
            ),
        ),
        'Issabel admin password' => array(
            'sqlite'    =>  array(
                array(
                    '/var/www/db/acl.db',
                    'UPDATE acl_user SET md5_password = ? WHERE name = ?',
                    array(md5($passwd), 'admin'),
                ),
            ),
            'mysql'     =>  NULL,
            'file'      =>  NULL,
        ),
    );
    return $updateList;
}

function phase2_cond_updates($uplist,$db,$passwd)
{
    // List all databases (cannot list specific databases with LIKE)
    $databases = NULL;
    try {
        $sth = $db->prepare('SHOW DATABASES');
        $sth->execute();
        $databases = $sth->fetchAll(PDO::FETCH_COLUMN, 0);
    } catch (PDOException $e) {
        fwrite(STDERR, "FATAL: unable to list databases: ".$e->getMessage()."\n");
        return FALSE;
    }


    // Conditionally add CallCenter update for AMI password
    try {
        if (!in_array('call_center', $databases)) {
            //print "No Issabel CallCenter database found.\n";
            //dialog_infobox('Status',"No Issabel CallCenter database found.\n",7,70);
        } else {
            dialog_infobox('Status',"Found Issabel CallCenter database.\n",7,70);
//            print "Found Issabel CallCenter database.\n";
            $sth = $db->prepare('SELECT config_key, config_value FROM call_center.valor_config WHERE config_key LIKE ?');
            $sth->execute(array('asterisk.%'));
            $values = $sth->fetchAll(PDO::FETCH_COLUMN|PDO::FETCH_UNIQUE);
            if (isset($values['asterisk.asthost']) && 
                in_array($values['asterisk.asthost'], array('127.0.0.1', 'localhost')) &&
                isset($values['asterisk.astuser']) &&
                $values['asterisk.astuser'] == 'admin') {
                    if (!is_array($uplist['Asterisk Manager Interface password']['mysql']))
                        $uplist['Asterisk Manager Interface password']['mysql'] = array();
                    $uplist['Asterisk Manager Interface password']['mysql'][] = array(
                        'UPDATE valor_config SET config_value = ? WHERE config_key = ?',
                        array($passwd, 'asterisk.astpass'),
                    );
            }
        }
    } catch (PDOException $e) {
        fwrite(STDERR, "FATAL: unable to check whether CallCenter references AMI: ".$e->getMessage()."\n");
        return FALSE;
    }    

    // Conditionally add updates for A2Billing
    try {
        if (!in_array('mya2billing', $databases)) {
            //print "No A2Billing database found.\n";
            //dialog_infobox('Status',"No A2Billing database found.\n",7,70);
        } else {
            //print "Found A2Billing database.\n";
            dialog_infobox('Status',"Found A2Billing database.\n",7,70);
            $uplist['A2Billing password'] = array(
                'sqlite'    =>  NULL,
                'mysql'     =>  array(
                    array(
                        'UPDATE mya2billing.cc_ui_authen SET pwd_encoded = ? WHERE login = ? OR login = ?',
                        array(hash('whirlpool', $passwd), 'admin', 'root'),
                    ),
                    array(
                        'UPDATE mya2billing.cc_config SET config_value = ? WHERE config_group_title = ? AND config_key = ?',
                        array('admin', 'global', 'manager_username')
                    ),
                    array(
                        'UPDATE mya2billing.cc_config SET config_value = ? WHERE config_group_title = ? AND config_key = ?',
                        array($passwd, 'global', 'manager_secret'),
                    ),
                    array(
                        'UPDATE mya2billing.cc_server_manager SET manager_username = ?, manager_secret = ? WHERE id = ? AND id_group = ?',
                        array('admin', $passwd, 1, 1),
                    ),
                ),
                'file'      =>  NULL,
            );
            
            // Conditionally update or remove redundant 'root' user as required
            $sth = $db->prepare('SELECT count(*) FROM mya2billing.cc_ui_authen WHERE login = ?');
            $sth->execute(array('admin'));
            $iNumTotal = $sth->fetch(PDO::FETCH_COLUMN, 0);
            $sth->closeCursor();
            if (!is_null($iNumTotal) && $iNumTotal > 0) {
                // Remove redundant user root
                $uplist['A2Billing password']['mysql'][] = array(
                    'DELETE FROM mya2billing.cc_ui_authen WHERE login = ?',
                    array('root'),
                );
            } else {
                // Shift root user to admin
                $uplist['A2Billing password']['mysql'][] = array(
                    'UPDATE mya2billing.cc_ui_authen SET login = ? WHERE login = ?',
                    array('admin', 'root'),
                );
            }
        }
    } catch (PDOException $e) {
        fwrite(STDERR, "FATAL: unable to check whether A2Billing database has 'root': ".$e->getMessage()."\n");
        return FALSE;
    }

    // Conditionally add updates for VTigerCRM password
    $sVTigerDB = NULL;
    if (in_array('vtigercrm510', $databases))
        $sVTigerDB = 'vtigercrm510';
    if (in_array('vtigercrm521', $databases))
        $sVTigerDB = 'vtigercrm521';
    if (is_null($sVTigerDB)) {
        //print "No VTigerCRM database found.\n";
        //dialog_infobox('Status',"No VTigerCRM database found.\n",7,70);
    } else {
        //print "Found VTigerCRM database $sVTigerDB\n";
        dialog_infobox('Status',"Found VTigerCRM database $sVTigerDB\n",7,70);
        $uplist['VTigerCRM password'] = array(
            'sqlite'    =>  NULL,
            'mysql'     =>  array(
                array(
                    "UPDATE $sVTigerDB.vtiger_users SET user_password = ENCRYPT(?, CONCAT(?, SUBSTRING(? FROM 1 FOR 2), ?)), user_hash = md5(?) WHERE user_name = ?",
                    array($passwd, '$1$', 'admin', '$', $passwd, 'admin'),
                ),
            ),
            'file'      =>  NULL,
        );
    }

    // Prepare query to check if MySQL table exists
    try {
        $sth_tableExists = $db->prepare(
            'SELECT COUNT(*) AS N FROM information_schema.TABLES '.
            'WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?');
    } catch (PDOException $e) {
        fwrite(STDERR, "FATAL: unable to prepare table check query: ".$e->getMessage()."\n");
        return FALSE;
    }

    foreach ($uplist as $k => $updateItem) {
        dialog_infobox('Status', "Updating $k ",7,70);

        // Update all instances of the password in sqlite databases
        if (!is_null($updateItem['sqlite'])) {
            //print "sqlite... ";
            foreach ($updateItem['sqlite'] as $updateSqliteItem) {
                try {
                    $dbsqlite = new PDO('sqlite:'.$updateSqliteItem[0]);
                    $dbsqlite->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
                    $sth = $dbsqlite->prepare($updateSqliteItem[1]);
                    $sth->execute($updateSqliteItem[2]);
                    $sth = NULL;
                    $dbsqlite = NULL;
                } catch (PDOException $e) {
                    fwrite(STDERR, "FATAL: unable to update $k: ".$e->getMessage()."\n");
                    return FALSE;
                }
            }
        }
        
        // Update all instances of the password in MySQL
        if (!is_null($updateItem['mysql'])) {
            //print "mariadb... ";
            foreach ($updateItem['mysql'] as $updateMysqlItem) {
                try {
                    // Check whether this is an optional update
                    $bAllTablesExist = TRUE;
                    if (count($updateMysqlItem) > 2) {
                        foreach ($updateMysqlItem[2] as $t) {
                            $sth_tableExists->execute($t);
                            $tuple = $sth_tableExists->fetch(PDO::FETCH_ASSOC);
                            $sth_tableExists->closeCursor();
                            if ($tuple['N'] <= 0) {
                                $bAllTablesExist = FALSE;
                                break;
                            }
                        }
                    }
                    if (!$bAllTablesExist) continue;
                    
                    if (strpos($updateMysqlItem[0], 'GRANT') === 0) {
                        // MySQL does not support preparing a GRANT statement
                        $db->exec($updateMysqlItem[0]);
                    } else {
                        $sth = $db->prepare($updateMysqlItem[0]);
                        $sth->execute($updateMysqlItem[1]);
                        $sth = NULL;
                    }
                } catch (PDOException $e) {
                    fwrite(STDERR, "FATAL: unable to update $k: ".$e->getMessage()."\n");
                    return FALSE;
                }
            }
        }
        
        // Update all instances of the password in system files
        if (!is_null($updateItem['file'])) {
            //print "files... ";
            foreach ($updateItem['file'] as $fileinfo) {
                if (file_exists($fileinfo[0])) {
                    $content = file($fileinfo[0]);
                    if (is_array($fileinfo[1])) {
                        switch ($fileinfo[1][0]) {
                        case 'custom':
                            if (function_exists($fileinfo[1][1]))
                                $fileinfo[1][1]($content, $passwd);
                            break;
                        }
                    } else {
                        for ($i = 0; $i < count($content); $i++) {
                            if (preg_match("/".$fileinfo[1]."/", rtrim($content[$i], "\r\n"))) {
                                $content[$i] = $fileinfo[2]."\n";
                                break;
                            }
                        }
                    }
                    file_put_contents($fileinfo[0], $content);
                }
            }
        }
        
        //dialog_infobox('Status', "Done",7,70);
        //print " updated\n";
    }
dialog_infobox('Status', ' O O O\n O O O\n O O O\n   O\nIssabel',7,11);
}

function change_ami_password(&$content, $sNewPassword)
{
    $bAdmin = FALSE;
    for ($i = 0; $i < count($content); $i++) {
        $regs = NULL;
        if (preg_match('/^\[(\w+)\]/', $content[$i], $regs)) {
            $bAdmin = ($regs[1] == 'admin');
        } elseif ($bAdmin && preg_match('/^secret\s*=\s*/', $content[$i])) {
            $content[$i] = "secret = $sNewPassword\n";
        }
    }
    exec('/var/lib/asterisk/bin/module_admin reload');
}

function dialog_infobox($backtitle, $msgbox, $height, $width)
{
    $height = (int)$height;
    $width = (int)$width;
    system('/usr/bin/dialog'.
        ' --stdout --sleep 1 --backtitle '.escapeshellarg($backtitle).
        ' --infobox '.escapeshellarg($msgbox).
        " $height $width");
}

function dialog_msgbox($backtitle, $msgbox, $height, $width)
{
    $height = (int)$height;
    $width = (int)$width;
    passthru('/usr/bin/dialog'.
        ' --backtitle '.escapeshellarg($backtitle).
        ' --msgbox '.escapeshellarg($msgbox).
        " $height $width");
}

function dialog_yesno($backtitle, $msgbox, $height, $width)
{
    $height = (int)$height;
    $width = (int)$width;
    passthru('/usr/bin/dialog'.
        ' --backtitle '.escapeshellarg($backtitle).
        ' --yesno '.escapeshellarg($msgbox).
        " $height $width", $return_val);
    return($return_val);
}

function dialog_passwordbox($backtitle, $msgbox, $height, $width)
{
    global $option;
    $height = (int)$height;
    $width = (int)$width;

    $pipes = NULL;
    $pipespec = array(
        0 => STDIN,
        1 => STDOUT,
        2 => STDERR,
        3 => array('pipe', 'w'));
        
    if ($option[0] == "--init"){
       $cncl=' --no-cancel';
    }
     
    $r = @proc_open('/usr/bin/dialog'.
        $cncl.
        ' --output-fd 3'.
        ' --backtitle '.escapeshellarg($backtitle).
        ' --insecure --passwordbox '.escapeshellarg($msgbox).
        " $height $width",
        $pipespec,
        $pipes);
    if (is_resource($r)) {
        $password = stream_get_contents($pipes[3]);
        fclose($pipes[3]);
        return array('retval' => proc_close($r), 'password' => $password);
    } else {
        return NULL;
    }
}

// Need custom function to load conf file, odd characters choke parse_ini_file()
function load_keys()
{
    global $PASSWD_PATH;
    $keys = array();
    foreach($PASSWD_PATH as $conffile) {
        if (file_exists($conffile)) foreach (file($conffile) as $s) {
            $s = rtrim($s, "\r\n");
            $regs = NULL;
            if (preg_match('/^(\w+)=(.*)$/', $s, $regs))
                $keys[$regs[1]] = $regs[2];
        }
    }
    return $keys;
}

function ensure_pbxapi_jwt_secret()
{
    $passwords = load_keys();

    if (isset($passwords['pbxapijwtsecret'])) {
        $jwtKey = base64_decode($passwords['pbxapijwtsecret'], TRUE);
        if ($jwtKey === FALSE || strlen($jwtKey) < 32) {
            fwrite(STDERR, "FATAL: pbxapijwtsecret in /etc/issabel.conf must contain at least 32 random bytes encoded as Base64\n");
            return FALSE;
        }
        return TRUE;
    }

    try {
        $jwtSecret = base64_encode(secure_random_bytes(32));
    } catch (Exception $e) {
        fwrite(STDERR, "FATAL: unable to generate pbxapijwtsecret: ".$e->getMessage()."\n");
        return FALSE;
    }

    $passwords['pbxapijwtsecret'] = $jwtSecret;
    save_keys($passwords);

    $savedPasswords = load_keys();
    if (!isset($savedPasswords['pbxapijwtsecret']) ||
        !secure_hash_equals($jwtSecret, $savedPasswords['pbxapijwtsecret'])) {
        fwrite(STDERR, "FATAL: unable to save pbxapijwtsecret in /etc/issabel.conf\n");
        return FALSE;
    }

    return TRUE;
}

function secure_random_bytes($length)
{
    if (function_exists('random_bytes')) {
        return random_bytes($length);
    }

    if (function_exists('openssl_random_pseudo_bytes')) {
        $strong = FALSE;
        $bytes = openssl_random_pseudo_bytes($length, $strong);
        if ($bytes !== FALSE && strlen($bytes) === $length && $strong) {
            return $bytes;
        }
    }

    $handle = @fopen('/dev/urandom', 'rb');
    if ($handle !== FALSE) {
        $bytes = '';
        while (strlen($bytes) < $length && !feof($handle)) {
            $chunk = fread($handle, $length - strlen($bytes));
            if ($chunk === FALSE) {
                break;
            }
            $bytes .= $chunk;
        }
        fclose($handle);

        if (strlen($bytes) === $length) {
            return $bytes;
        }
    }

    throw new RuntimeException('No cryptographically secure random source is available');
}

function secure_hash_equals($known, $provided)
{
    if (function_exists('hash_equals')) {
        return hash_equals($known, $provided);
    }

    if (!is_string($known) || !is_string($provided) ||
        strlen($known) !== strlen($provided)) {
        return FALSE;
    }

    $result = 0;
    $length = strlen($known);
    for ($i = 0; $i < $length; $i++) {
        $result |= ord($known[$i]) ^ ord($provided[$i]);
    }

    return $result === 0;
}

function save_keys($keys)
{
    global $PASSWD_PATH;
    $s = '';
    foreach ($keys as $k => $v) $s.= "$k=$v\n";
    foreach($PASSWD_PATH as $conffile) {
        file_put_contents($conffile, $s);
        chmod($conffile, 0600);
        chown($conffile, 'asterisk');
        chgrp($conffile, 'asterisk');
    }
}

function check_etc_issabel() {
    $passwords = load_keys();
    $skip=0;
    $handle = fopen("/etc/asterisk/manager.conf", "r");
    if ($handle) {
        // Decide if we have to copy files over
        while (($line = fgets($handle)) !== false) {
            if(substr($line,0,12)=='writetimeout') {
                $skip=1;
            }
        }
        fclose($handle);

        if($skip==0) {

           // we must copy files issabelPBX files
           echo "Copying /etc/asterisk/issabel configuration files ...\n";
           $dir = @opendir('/etc/asterisk.issabel');
           while (false !== ($file = readdir($dir))) {
               if (( $file != '.' ) && ( $file != '..' )) {
                   copy('/etc/asterisk.issabel/'.$file,'/etc/asterisk/'.$file);
               }
           }

           // admin manager password
           $issabel_ami_password = $passwords['amiadminpwd'];
           exec("sed -i \"s/AMPMGRPASS/{$issabel_ami_password}/\" /etc/asterisk/manager.conf", $output, $status);

           // base module directory
           $base=php_uname('m');
           if($base=='x86_64' || $base=='aarch64') { $LIBDIR='/usr/lib64/asterisk/modules';  } else { $LIBDIR='/usr/lib/asterisk/modules';  };

           $db  = new PDO('mysql:host=localhost;dbname=asterisk', 'root', $passwords['mysqlrootpwd']);
           $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
           $query="UPDATE issabelpbx_settings SET value=? WHERE keyword='ASTMODDIR'";
           $sth = $db->prepare($query);
           $sth->execute(array($LIBDIR));
           $sth = NULL;

           exec("sed --in-place \"s|.*astmoddir.*|astmoddir => $LIBDIR|g\" /etc/asterisk.issabel/asterisk.conf", $output, $status);
           exec("sed --in-place \"s|.*astmoddir.*|astmoddir => $LIBDIR|g\" /etc/asterisk/asterisk.conf", $output, $status);

        }
    }
}

function writeAsteriskPEMfile() {
    if (!file_exists('/etc/asterisk/keys')) {
        mkdir('/etc/asterisk/keys', 0751, true);
    }
    exec(">/etc/asterisk/keys/asterisk.pem", $output, $ret);
    exec("for A in `grep '^SSLCert' /etc/httpd/conf.d/ssl.conf | awk '{print $2}'`; do cat \$A >>/etc/asterisk/keys/asterisk.pem; done", $output, $ret);
}


?>
