<?php

    // Generatore chiave x licenze BitOnLive c/o Riva Stefano


    function generateBitonliveKey($product_id = 0, $product_version = 0, $plain = false){

    $version = "2.0";
    $container_id = "";
    $hostname = "";
    $motherboard_model = "";
    $motherboard_serial = "";
    $hardware_uuid = "";
    $mac_addresses_array = array();
    $disks_serial_array = array();

    if( strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ){

        // Windows

        // Scheda Madre
        $output_mobo = shell_exec("wmic baseboard GET SerialNumber 2>&1");
        $explode_mobo = explode(PHP_EOL, $output_mobo);
        for($i = 0; $i < count($explode_mobo); $i++){
            if(str_replace(" ", "" , $explode_mobo[$i]) == "SerialNumber"){
                $motherboard_serial = rtrim($explode_mobo[$i + 1]);
                break;
            }
        }

        // Hardware uuid
        $output_uuid = shell_exec("wmic path win32_computersystemproduct get uuid");
        $explode_uuid = explode(PHP_EOL, $output_uuid);
        for($i = 0; $i < count($explode_uuid); $i++){
            if(str_replace(" ", "" , $explode_uuid[$i]) == "UUID"){
                $hardware_uuid = rtrim(strtolower($explode_uuid[$i + 1]));
                break;
            }
        }

        // MAC Addresses Windows
        $mac_addresses_array = GetWindowsMAC_license();

        // Seriali dischi
        $disks_serial_array = getWindowsDisks();
        /*$output_disks = shell_exec("wmic DISKDRIVE GET SerialNumber , Name 2>&1");
        $explode_disks_rows = explode(PHP_EOL, $output_disks);
        for($i = 1; $i < count($explode_disks_rows); $i++){
            $explode_inline = explode(" " , $explode_disks_rows[$i]);
            if(count($explode_inline) == 5)
                array_push($disks_serial_array , array('name' => rtrim($explode_inline[0]) , 'serial' => rtrim($explode_inline[2]) ));
        }*/

        // hostname
        $output_hostname = shell_exec("hostname");
        $explode_hostname = explode(PHP_EOL, $output_hostname);
        $hostname = rtrim($explode_hostname[0]);


    } else {

        // Linux

        // Scheda madre
        $output_mobo = shell_exec("cat /sys/devices/virtual/dmi/id/board_serial");
        $explode_mobo = explode(PHP_EOL, $output_mobo);
        $motherboard_serial = $explode_mobo[0];

        // Hardware uuid
        $output_uuid = shell_exec("cat /sys/class/dmi/id/product_uuid");
        $explode_uuid = explode(PHP_EOL, $output_uuid);
        $hardware_uuid = strtolower($explode_uuid[0]);

        // Mac addresses
        $mac_addresses_array = getMacLinux_license();

        // seriali dischi
        $disks_serial_array = getLinuxDisks();

        // Seriali dischi
        /*$output_disks = shell_exec("lsblk --nodeps -o name,serial");
        $explode_disks = explode(PHP_EOL , $output_disks);
        for($i = 1; $i < count($explode_disks); $i++){

            $serial_line = explode(" " , $explode_disks[$i]);
            if( rtrim($serial_line[0]) !== '')
                array_push($disks_serial_array , array('name' => rtrim($serial_line[0]) , 'serial' => rtrim($serial_line[1]) ));

        }*/

        // hostname
        $output_hostname = shell_exec("hostname");
        $explode_hostname = explode(PHP_EOL, $output_hostname);
        $hostname = rtrim($explode_hostname[0]);

        // container ID
        $output_id = exec("cat /proc/self/cgroup | grep 'docker' | sed 's/^.*\\///' | tail -n1");
        $explode_id = explode(PHP_EOL , $output_id);
        $container_id = $explode_id[0];

    }
    $checksum = sha1(md5("licensehandlerchecksumsha1hashing" . sha1($container_id."_".$hostname."_".$hardware_uuid."_".$motherboard_serial)));

    /*"macs" => $mac_addresses_array,*/

    $license_json = array(

        "license" => [
            "hash" => $checksum,
            "os" => [
                "id" => $container_id,
                "hostname" => $hostname,
                "hardware" => $hardware_uuid,
            ],            
            "baseboard" => $motherboard_serial,
            "disks" => $disks_serial_array
        ]

    );

    //echo json_encode($license_json);

    if($plain) return $license_json;

    $cipher = "aes-256-cbc";
    //echo PHP_EOL . ">>>>>>>>>>> LICENSE MACHINE KEY >>>>>>>>>>>>>>" . PHP_EOL.PHP_EOL;
    //@openssl_encrypt ($licenseContent, 'aes256', $pass)
    if (in_array($cipher, openssl_get_cipher_methods())){

        $key = '8x!A%D*G-KaPdSgVkYp3s6v9y$B?E(H+';
        $plaintext = json_encode($license_json);
        
        $ivlen = openssl_cipher_iv_length($cipher="AES-256-CBC");

        $iv = substr("cb7d7c7fb26f3f356fb3d5a0f919dea6", 0 , 16);
        $ciphertext_raw = bin2hex(openssl_encrypt($checksum.$plaintext, $cipher, $key, $options=OPENSSL_RAW_DATA, $iv));
        $hmac = hash_hmac('sha512', $ciphertext_raw, $key, $as_binary=false);
        $ciphertext = base64_encode($ciphertext_raw.$hmac.$iv );
        $ciphertext = $ciphertext . "_V2" ;
        //echo $ciphertext;
        return $ciphertext;

    } else {

        //echo "Warning: you need at least PHP 7.0 to generate a valid machine license key.";
        return false;

    }

    //echo PHP_EOL.PHP_EOL;

}

/*
function verifyBitonliveLicense($license_path, $id_product){

    // Funzione di verifica della licenza dato un id prodotto, controlla che la licenza sia:
    // per questo software + questa macchina + sia valida + sia attiva + non sia bloccata.

    // Richiede come parametri il percorso per la licenza e l'id del prodotto da cui arriva questa richiesta.
    if(!file_exists($license_path)) return array("valid" => false , "error" => "Il percorso della licenza non esiste -> " . $license_path);

    $license_content = file_get_contents($license_path);

    // Decrypto la licenza
    $masterKey = "123456";

    $c = base64_decode($ciphertext);
    $ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
    $iv = substr($c, 0, $ivlen);
    $hmac = substr($c, $ivlen, $sha2len=32);
    $ciphertext_raw = substr($c, $ivlen+$sha2len);
    $original_plaintext = openssl_decrypt($ciphertext_raw, $cipher, $masterKey, $options=OPENSSL_RAW_DATA, $iv);
    $calcmac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary=true);
    if (!hash_equals($hmac, $calcmac)) return array("valid" => false , "error" => "L'hmac non corrisponde con quello calcolato."); // timing attack safe comparison

    $payload_license = json_decode($original_plaintext);

    echo $payload_license;


}*/

//print_r(checkBitonliveLicense([122,125], '../StreamerV2/api/nodejs/lic.bitonlive'));
//print_r(checkBitonliveLicense([122,125], '../Zelos/lic.bitonlive'));

function checkBitonliveLicense($product_id, $license_path, $get_payload = false){

    // Funzione che verifica che ci sia una licenza esistente e ne verifica la sua integrità, richiede l'id del prodotto a cui stiamo facendo riferimento per controllare in tutti e 3 i posti e il path della licenza
    // Ritorna:
    // missing_license -> non esiste la licenza\licenza non trovata
    // invalid_license -> licenza non valida, il controllo ha ritornato false
    // expired_license -> la licenza è scaduta
    // valid_license -> la licenza è valida per questo prodotto su questa macchina
    // blocked_license -> la licenza è stata bloccata

    if(!file_exists($license_path)) return array("status" => "missing_license" , "error" => "License file not found -> " . $license_path);
    

    $license_software_key = file_get_contents($license_path);
    $masterKey = '/B?E(G+KbPeShVmYq3t6w9z$C&F)J@Mc';

    if(empty($license_software_key)) return array("status" => "missing_license" , "error" => "License is empty");

    $keyVersion = substr($license_software_key , strlen($license_software_key) - 3 , 3) === "_V2" ? 2 : 1;

    $c = $keyVersion === 1 ? base64_decode($license_software_key) : base64_decode(substr($license_software_key , 0, strlen($license_software_key) - 3));

    $ivlen = openssl_cipher_iv_length($cipher="AES-256-CBC");    
    if($keyVersion === 2){

        // cambia ordine del calcolo iv per la v2

        $iv = substr($c, strlen($c) - $ivlen, strlen($c));
        $hmac = substr($c, (strlen($c) - $ivlen - 128), $sha2len=128);
        //echo $hmac . PHP_EOL;
        $ciphertext_raw = substr($c, 0, (strlen($c) - $ivlen - $sha2len));


    } else {

        $iv = substr($c, 0, $ivlen);
        $hmac = substr($c, $ivlen, $sha2len=128);
        $ciphertext_raw = substr($c, $ivlen+$sha2len);

    }

    $original_plaintext = openssl_decrypt( hex2bin($ciphertext_raw), $cipher, $masterKey, $options=OPENSSL_RAW_DATA, $iv);
    $calcmac = hash_hmac('sha512', $ciphertext_raw, $masterKey, $as_binary=false);

    //echo $calcmac . PHP_EOL . $hmac;

    if (!hash_equals($hmac, $calcmac)) return array("status" => "invalid_license" , "error" => "License hmac hash did not pass the test. License could be compromised or corrupted.");

    //if($keyVersion === 2){
        //echo $original_plaintext;
    $BODY_LICENSE = $keyVersion === 2 ? json_decode(substr($original_plaintext, 40, strlen($original_plaintext)), true) : json_decode($original_plaintext, true);

    //if($get_payload) return $BODY_LICENSE;

    $licSoftKey = $keyVersion === 1 ? $BODY_LICENSE['license_software_key'] : $BODY_LICENSE['keyid'];

    $body_software_key = verifyLicenseSoftwareIntegrity($licSoftKey);

    if(!$body_software_key) return array("status" => "invalid_license" , "error" => "License integrity check for hardware did not pass the test. License is not valid."); // integrity check fallito.

    //$product_check = ($BODY_LICENSE->product_id == $body_software_key->product_id);
    // Il software darà un eleneco di product ID compatibili con questa licenza, se questa licenza ha un productid nel range di quelli accettati, allora è good, altrimenti nah.
    $actualLicenseBody = array();

    if($keyVersion === 2){

        // metodo nuovo, controllo se abbiamo le licenze dell'array di input in ordine da + importante a meno importante.. già ordinate frfr
        $product_match = false;

       
        foreach ($product_id as $key=>$current_prod_code) {
                
            for($p = 0; $p < count($BODY_LICENSE['softwares']); $p++){

                if($BODY_LICENSE['softwares'][$p]['application_id'] === $current_prod_code) {
                    $product_match = true;
                    $current_attributes = array(
                        "maxAdmins" => $BODY_LICENSE['softwares'][$p]['maxAdmins'],
                        "maxUsers" => $BODY_LICENSE['softwares'][$p]['maxUsers'],
                        "maxChannels" => $BODY_LICENSE['softwares'][$p]['maxChannels'],
                        "CRM" => isset($BODY_LICENSE['softwares'][$p]['CRM']) && $BODY_LICENSE['softwares'][$p]['CRM'] == 1 ? 1 : 0
                    );
                    $actualLicenseBody = $BODY_LICENSE['softwares'][$p];
                    $actualLicenseBody['valid_from'] = strtotime($actualLicenseBody['valid_from']);
                    $actualLicenseBody['valid_to'] = strtotime($actualLicenseBody['valid_to']);
                    $actualLicenseBody['id'] = $BODY_LICENSE['id'];
                    //print_r($actualLicenseBody);
                    break;
                }
                
            }

            if($product_match) break;

        }

        $product_check = $product_match; 
        
    } else {

        // metodo vecchio
        $product_check=in_array($BODY_LICENSE['product_id'] , $product_id);
        $current_attributes = $BODY_LICENSE['attributes'];
        $actualLicenseBody = $BODY_LICENSE;

    }


    if($get_payload) return $actualLicenseBody;

    if(!$product_check) return array("status" => "invalid_license" , "error" => "License product_id is not the same for the product you are checking the license for. License is not valid."); // check prodotto fallito.
    $plain_hardware_key = generateBitonliveKey($product_id, 0, true);

    $MACHINE_VALIDITY = $plain_hardware_key['license']['hash'] == $body_software_key->hash;
    if(!$MACHINE_VALIDITY) return array("status" => "invalid_license" , "error" => "License hardware did not match the current hardware, when this happends, usually it means: different motherboard, different hardware id, different container id (for dockers) or different hostname. License is invalidated."); // Gli hash dell'hardware e seriali non corrispondono. qualcosa non va nella macchina, cambiato computer?

    // Verifico i seriali e macs.

    // IL CONTROLLO DEI MAC ADDRESS ERA INUTILE DALL'INIZIO NON SERVE A NIENTE BASTA COLLEGARE UNA VPN E SI GENERA UN NUOVO MAC ADDRESS E LA CHIAVE CAMBIA E FINE DEL CONTROLLO...
    /*$mac_addresses_array = $plain_hardware_key['license']['macs'];
    $ok_macs = 0;
    for($i = 0; $i < count($mac_addresses_array); $i++){
        $current_check = $mac_addresses_array[$i];
        if(in_array($current_check , $body_software_key->macs)) $ok_macs++;
    }
    if($ok_macs < (count($body_software_key->macs) / 2) ) return array("status" => "invalid_license" , "error" => "Current available mac addresses are different from the licensed ones. Therefore, license is invalidated."); // macs non corrispondono.
    */

    // Verifico i dischi
    $disks_array = $plain_hardware_key['license']['disks'];
    $ok_disks = 0;
    for($i = 0; $i < count($disks_array); $i++){
        $current_check = $disks_array[$i];
        for($o = 0; $o < count($body_software_key->disks); $o++)
            if($current_check['serial'] == $body_software_key->disks[$o]->serial) $ok_disks++;
    }
    if($ok_disks < (count($body_software_key->disks) / 2) ) return array("status" => "invalid_license" , "error" => "Current available disk serials are different from the licensed ones. Therefore, license is invalidated."); // i seriali dei dischi non corrispondono.

    // -------> Se siamo qui, la licenza è valida in termini di hardware.
    // Ora verifico il resto, validità, se è bloccata ecc..

    // Se la data attuale è minore della partenza o maggiore della scadenza, è scaduta punto
    if(strtotime(date('Y-m-d H:i:s')) < $actualLicenseBody['valid_from'] || strtotime(date('Y-m-d H:i:s')) > $actualLicenseBody['valid_to']) return array("status" => "expired_license" , "error" => "Current date returned by the server is out of the validity for this license."); // macs non corrispondono. // Licenza scaduta
    if(isset($BODY_LICENSE['locked']) && $BODY_LICENSE['locked']) return array("status" => "blocked_license" , "error" => "License is currently flagged as blocked, cannot continue."); // macs non corrispondono.

    // Controlli degli attributi?
    // Gli attributi andrebbero forse ritornati come array assieme allo stato della licenza in caso sia valida o sempre idk.
    return array("status" => "valid_license" , "attributes" => $current_attributes , "error" => false); // macs non corrispondono.

}

//print_r(verifyLicenseSoftwareIntegrity("YmEwYzg0MjQwODM5NzUyZmM5NDQ3MWFhYzk2N2NhYzYzMzNhZGM4NmI1YjkzNjY0ZDZlODAyZjlmNjRmYjYxZDdlZmFkMTUxNzk4ZGZmZWI0Mzc1MjgyNmYyYTlkNzU3NjFjNzQzM2RlNzZjOTIyODc4Y2I4MTRjYTI1N2M3YmYyZGVmYTI1NjI2YzNjYTJlZGFmZDBiOWI2YzYzZWQzNmQzYWQyNjAyNTQ3N2E1YTViNjgyNmRiOTYwZWVmYzJmNjA2NjVhMzUyOTczMDdkZjE5NmI5Y2Y0MjliMTc4MGRmM2ZiYTNhNDA5MzMzY2YzYzgzM2ZlZjcwMTdjZmVmMGUyZDBjZDZlMGQ0ZDk0MGMxZjBhYzczMDVkY2M4ZjQ3MDQyZGFjM2NlZDBmMjhhYTBkMjVlNmQ3NzBiZWEzNzUzNWFjOGViYzYwYWI1MzNmZDJkNmYyMTc3ZDk0MGQwMzEzYjMyNTEzMTFjYTE4NTY1OGQxYTM2ZTBlOTI3NmI3MjRmNjQyY2U2MDk0MjcyNWVmZWVjMDk3MDk5NzY3ZDM5MDRhMGJjMjhmOWNmMzk0OThlODg2NTVkOWRhMmNhNWM5YTU2N2ZhODJmZTY3MTIxMWQ1MThkY2RlZmU5ZjdmMDQxMjFkNDNmNDlmOWRlYTg3N2U2ODM5NzgzNDZmZmMwMDc4ZmFkNDg1MWU4ODMxMjM0M2Y4NGU1ZmYxZDIwYWJmOTVhMTlhOGMwMzU0ZWU5ZmQ5ZDdjYzU1NmU3ZDFhMzQ5MzFiNDA4N2EzZTM3YWIzYTE0YmRmN2U5ZmI0ZjI0MTVhZmM1ZTY4ZjdmOGE1NTBkMTEyYjZmZDY3NGE2MzIxMmY5YTQ5YzkwOTFiMDY4OTU0OTczNWMyZDhhZDAyNjIxOWUwYjEwMjZiNjM1Nzg5Y2FmODhkOThmNTYzN2RiZTI1ZmMwZGJhZjQzYjFhODY5YTQ1YjkyYWVkMzFiNjYyMjJmZjM4ODdlOWRmMjllMzc0ZDViMjUyNjdiM2I5NDU5MDA4YmY4OGQzNWFhODQyY2JhMTdkMWU5NmZmNjRiZjI3NmI5NWJiMGRmNTJkOWJiM2UzOTcwMjk2NGEyNmUzNDVhZGNhMWEwODMxYTI="));

function verifyLicenseSoftwareIntegrity($license_software_key){

    //echo $license_software_key;

    $key = '8x!A%D*G-KaPdSgVkYp3s6v9y$B?E(H+';

    $keyVersion = substr($license_software_key , strlen($license_software_key) - 3 , 3) === "_V2" ? 2 : 1;

    $c = $keyVersion === 1 ? base64_decode($license_software_key) : base64_decode(substr($license_software_key , 0, strlen($license_software_key) - 3));

    $ivlen = openssl_cipher_iv_length($cipher="AES-256-CBC");    
    if($keyVersion === 2){

        // cambia ordine del calcolo iv per la v2

        $iv = substr($c, strlen($c) - $ivlen, strlen($c));
        $hmac = substr($c, (strlen($c) - $ivlen - 128), $sha2len=128);
        $ciphertext_raw = substr($c, 0, (strlen($c) - $ivlen - $sha2len));


    } else {

        $iv = substr($c, 0, $ivlen);
        $hmac = substr($c, $ivlen, $sha2len=128);
        $ciphertext_raw = substr($c, $ivlen+$sha2len);

    }

    //$c = base64_decode($license_software_key);
    
    $ivlen = 16;

    /*$iv = substr($c, 0, $ivlen);
    $hmac = substr($c, $ivlen, $sha2len=128);
    $ciphertext_raw = substr($c, $ivlen+$sha2len);*/


    $original_plaintext = openssl_decrypt(hex2bin($ciphertext_raw), $cipher="AES-256-CBC", $key, $options=OPENSSL_RAW_DATA, $iv);
    $calcmac = hash_hmac('sha512', $ciphertext_raw, $key, $as_binary=false);

    //echo $iv.PHP_EOL.PHP_EOL;
    //echo $hmac.PHP_EOL.PHP_EOL;
    //echo $calcmac.PHP_EOL;
    //echo $ciphertext_raw;

    if (!hash_equals($hmac, $calcmac)) return false;

    //echo "hash OK!" . PHP_EOL;
    //echo $original_plaintext.PHP_EOL;


    $array_key = $keyVersion === 1 ? json_decode($original_plaintext) : json_decode(substr($original_plaintext, 40, strlen($original_plaintext)));
    $array_key = $array_key->license;

    // Ricalcolo l'hash checksum
    $checksum_fields = sha1(md5("licensehandlerchecksumsha1hashing".sha1($array_key->os->id."_".$array_key->os->hostname."_".$array_key->os->hardware."_".$array_key->baseboard)));

    if($checksum_fields !== $array_key->hash) return false; // Se gli hash NON corrispondono, male male.
    return $array_key;

}


function GetWindowsMAC_license(){
    ob_start();
    system('getmac /FO CSV');
    $Content = ob_get_contents();
    ob_clean();

        $parsed = str_getcsv4_license($Content);
        $array_macs = array();
        for($i = 1; $i < count($parsed);$i++){

            $test_mac = explode("-",$parsed[$i]);
            if(count($test_mac)==6)
                array_push($array_macs , str_replace('-' , ':' , strtolower($parsed[$i])) );
                //return $parsed[$i];
        }

        return $array_macs;

}

function str_getcsv4_license($input, $delimiter = ',', $enclosure = '"') {

    if( ! preg_match("/[$enclosure]/", $input) ) {
      return (array)preg_replace(array("/^\\s*/", "/\\s*$/"), '', explode($delimiter, $input));
    }

    $token = "##"; $token2 = "::";
    //alternate tokens "\034\034", "\035\035", "%%";
    $t1 = preg_replace(array("/\\\[$enclosure]/", "/$enclosure{2}/",
         "/[$enclosure]\\s*[$delimiter]\\s*[$enclosure]\\s*/", "/\\s*[$enclosure]\\s*/"),
         array($token2, $token2, $token, $token), trim(trim(trim($input), $enclosure)));

    $a = explode($token, $t1);
    foreach($a as $k=>$v) {
        if ( preg_match("/^{$delimiter}/", $v) || preg_match("/{$delimiter}$/", $v) ) {
            $a[$k] = trim($v, $delimiter); $a[$k] = preg_replace("/$delimiter/", "$token", $a[$k]); }
    }
    $a = explode($token, implode($token, $a));
    return (array)preg_replace(array("/^\\s/", "/\\s$/", "/$token2/"), array('', '', $enclosure), $a);

}

function getMacLinux_license() {

    $mac = shell_exec("ip -a link | grep ether");
    $explode = explode(" " , $mac);

    $arrayMACS = array();

    for($i=0;$i<count($explode);$i++){

        $test_mac = explode(":",$explode[$i]);
        if(count($test_mac)==6)
            array_push($arrayMACS , strtolower($explode[$i]) );

    }

    return $arrayMACS;

}

function getLinuxDisks(){

    $array_ = [];
    $output_disks = shell_exec("lsblk --nodeps -o name,serial");
    $explode_disks = explode(PHP_EOL , $output_disks);
    for($i = 1; $i < count($explode_disks); $i++){
        $serial_line = explode(" " , $explode_disks[$i]);
        if( rtrim($serial_line[0]) !== '')
            array_push($array_ , array('name' => rtrim($serial_line[0]) , 'serial' => rtrim($serial_line[1]) ));
    }
    return $array_;

}

function GetWindowsDisks(){

    $disks_ = [];
    $output_disks = shell_exec("wmic DISKDRIVE GET SerialNumber , PNPDeviceID 2>&1");
    $explode_disks_rows = explode(PHP_EOL, $output_disks);
    for($i = 1; $i < count($explode_disks_rows); $i++){
        $explode_inline = explode(" " , $explode_disks_rows[$i]);
        if(count($explode_inline) > 1){
            $serial_found = "";
            // ora cerco il seriale.
            for($f = 1; $f < count($explode_inline); $f++) if($explode_inline[$f] != ''){$serial_found = $explode_inline[$f]; break;}
            if($serial_found != '')
                array_push($disks_ , array('name' => rtrim($explode_inline[0]) , 'serial' => $serial_found ));
        }
    }
    return $disks_;
}


?>