<?php

class license_application extends Padl
{
    /**
     * The number of allowed differences between the $_SERVER vars and the vars
     * stored in the key
     *
     * @var number
     */
    public $_ALLOWED_SERVER_DIFS = 0;
    /**
     * The number of allowed differences between the $ip vars in the key and the ip
     * vars collected from the server
     *
     * @var number
     */
    public $_ALLOWED_IP_DIFS = 0;
    /**
     * the path of the license key file, remember this would be relative to the
     * include path of the class file.
     */
    public $_LICENSE_PATH;

    /**
     * Constructor
     *
     * @access public
     * @param $use_mcrypt boolean Determines if mcrypt encryption is used or not (defaults to true,
     *                     however if mcrypt is not available, it is set to false)
     * @param $use_time boolean Sets if time binding should be used in the key (defaults to true)
     * @param $use_server boolean Sets if server binding should be used in the key (defaults to true)
     * @param $allow_local boolean Sets if server binding is in use then localhost servers are valid (defaults to false)
     *
     * @see PmLicenseManager::__construct()
     * @see PmLicenseManager::installLicense()
     * @see PmLicenseManager::validateLicense()
     * @link https://wiki.processmaker.com/3.2/Enterprise_Manager_Tool#Importing_a_License
     * @link https://wiki.processmaker.com/3.2/Upgrading_ProcessMaker#Activating_the_License
     */
    public function __construct($license_path = 'license.dat', $use_mcrypt = true, $use_time = true, $use_server = true, $allow_local = false, $challenge = false)
    {
        //Check to see if the class has been secured
        if (isset($_SESSION)) {
            unset($_SESSION["__sw__"]);

            if ($challenge) {
                $_SESSION["__sw__"] = true;
            }
        }

        $this->_check_secure();
        $this->_LICENSE_PATH = $license_path;
        $this->init($use_mcrypt, $use_time, $use_server, $allow_local);
        if ($this->USE_SERVER) {
            $this->_MAC = $this->_get_mac_address();
        }
    }

    /**
     * set_server_vars
     *
     * to protect against spoofing you should copy the $_SERVER vars into a
     * seperate array right at the first line of your script so parameters can't
     * be changed in unencoded php files. This doesn't have to be set. If it is
     * not set then the $_SERVER is copied when _get_server_info (private) function
     * is called.
     *
     * @access public
     * @param $array array The copied $_SERVER array
     * */
    public function set_server_vars($array)
    {
        # check to see if the class has been secured
        $this->_check_secure();
        $this->_SERVER_VARS = $array;
        # some of the ip data is dependant on the $_SERVER vars, so update them
        # after the vars have been set
        $this->_IPS = $this->_get_ip_address();
        # update the server info
        $this->_SERVER_INFO = $this->_get_server_info();
    }

    /**
     * _get_os_var
     *
     * gets various vars depending on the os type
     *
     * @access private
     * @return string various values
     * */
    public function _get_os_var($var_name, $os)
    {
        $var_name = strtolower($var_name);
        # switch between the os's
        switch ($os) {
            # not sure if the string is correct for FreeBSD
            # not tested
            case 'freebsd':
                # not sure if the string is correct for NetBSD
                # not tested
            case 'netbsd':
                # not sure if the string is correct for Solaris
                # not tested
            case 'solaris':
                # not sure if the string is correct for SunOS
                # not tested
            case 'sunos':
                # darwin is mac os x
                # tested only on the client os
            case 'darwin':
                # switch the var name
                switch ($var_name) {
                    case 'conf':
                        $var = '/sbin/ifconfig';
                        break;
                    case 'mac':
                        $var = 'ether';
                        break;
                    case 'ip':
                        $var = 'inet ';
                        break;
                }
                break;
                # linux variation
                # tested on server
            case 'linux':
                # switch the var name
                switch ($var_name) {
                    case 'conf':
                        $var = '/sbin/ifconfig';
                        break;
                    case 'mac':
                        $var = 'HWaddr';
                        break;
                    case 'ip':
                        $var = 'inet addr:';
                        break;
                }
                break;
        }
        return $var;
    }

    /**
     * _get_config
     *
     * gets the server config file and returns it. tested on Linux,
     * Darwin (Mac OS X), and Win XP. It may work with others as some other
     * os's have similar ifconfigs to Darwin but they haven't been tested
     *
     * @access private
     * @return string config file data
     * @see _get_ip_address()
     * @see _get_mac_address()
     */
    public function _get_config()
    {
        # check to see if the class has been secured
        $this->_check_secure();
        if (!$this->USE_SERVER) {
            return 'NOT_USE_SERVER_CONFIG';
        }
        # if anyone has any clues for windows environments
        # or other server types let me know
        $os = strtolower(PHP_OS);
        if (substr($os, 0, 3) == 'win') {
            # this windows version works on xp running apache
            # based server. it has not been tested with anything
            # else, however it should work with NT, and 2000 also
            # execute the ipconfig
            @exec('ipconfig/all', $lines);
            # count number of lines, if none returned return MAC_404
            # thanks go to Gert-Rainer Bitterlich <bitterlich -at- ima-dresden -dot- de>
            if (count($lines) == 0) {
                return 'ERROR_OPEN';
            }
            # $path the lines together
            $conf = implode($this->_LINEBREAK, $lines);
        } else {
            # get the conf file name
            $os_file = $this->_get_os_var('conf', $os);
            # open the ipconfig
            $fp = @popen($os_file, "rb");
            # returns invalid, cannot open ifconfig
            if (!$fp) {
                return 'ERROR_OPEN';
            }
            # read the config
            $conf = @fread($fp, 4096);
            @pclose($fp);
        }
        return $conf;
    }

    /**
     * _get_ip_address
     *
     * Used to get the MAC address of the host server. It works with Linux,
     * Darwin (Mac OS X), and Win XP. It may work with others as some other
     * os's have similar ifconfigs to Darwin but they haven't been tested
     *
     * @access private
     * @return array IP Address(s) if found (Note one machine may have more than one ip)
     * @return string ERROR_OPEN means config can't be found and thus not opened
     * @return string IP_404 means ip adress doesn't exist in the config file and can't be found in the $_SERVER
     * @return string NOT_USE_SERVER_CONFIG the server configuration is not used in license validation.
     *
     * @see set_server_vars()
     */
    public function _get_ip_address()
    {
        $ips = array();
        # get the cofig file
        $conf = $this->_get_config();
        # if the conf has returned and error return it
        if ($conf != 'NOT_USE_SERVER_CONFIG' && $conf != 'ERROR_OPEN') {
            # if anyone has any clues for windows environments
            # or other server types let me know
            $os = strtolower(PHP_OS);
            if (substr($os, 0, 3) == 'win') {
                # anyone any clues on win ip's
            } else {
                # explode the conf into seperate lines for searching
                $lines = explode($this->_LINEBREAK, $conf);
                # get the ip delim
                $ip_delim = $this->_get_os_var('ip', $os);

                # ip pregmatch
                $num = "(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])";
                # seperate the lines
                foreach ($lines as $key => $line) {
                    # check for the ip signature in the line
                    if (!preg_match("/^$num\\.$num\\.$num\\.$num$/", $line) && strpos($line, $ip_delim)) {
                        # seperate out the ip
                        $ip = substr($line, strpos($line, $ip_delim) + strlen($ip_delim));
                        $ip = trim(substr($ip, 0, strpos($ip, " ")));
                        # add the ip to the collection
                        if (!isset($ips[$ip])) {
                            $ips[$ip] = $ip;
                        }
                    }
                }
            }
        }

        # if the conf has returned nothing
        # attempt to use the $_SERVER data
        if (isset($this->_SERVER_VARS['SERVER_NAME'])) {
            $ip = gethostbyname($this->_SERVER_VARS['SERVER_NAME']);
            if (!isset($ips[$ip])) {
                $ips[$ip] = $ip;
            }
        }
        # count return ips and return if found
        if (count($ips) > 0) {
            return $ips;
        }
        if (isset($this->_SERVER_VARS['SERVER_ADDR'])) {
            $name = gethostbyaddr($this->_SERVER_VARS['SERVER_ADDR']);
            $ip = gethostbyname($name);
            if (!isset($ips[$ip])) {
                $ips[$ip] = $ip;
            }
            # if the $_SERVER addr is not the same as the returned ip include it aswell
            if ($ip != $this->_SERVER_VARS['SERVER_ADDR']) {
                if (!isset($ips[$this->_SERVER_VARS['SERVER_ADDR']])) {
                    $ips[$this->_SERVER_VARS['SERVER_ADDR']] = $this->_SERVER_VARS['SERVER_ADDR'];
                }
            }
        }
        # count return ips and return if found
        if (count($ips) > 0) {
            return $ips;
        }
        # failed to find an ip check for conf error or return 404
        if ($conf == 'NOT_USE_SERVER_CONFIG' || $conf == 'ERROR_OPEN') {
            return $conf;
        }
        return 'IP_404';
    }

    /**
     * _get_mac_address
     *
     * Used to get the MAC address of the host server. It works with Linux,
     * Darwin (Mac OS X), and Win XP. It may work with others as some other
     * os's have similar ifconfigs to Darwin but they haven't been tested
     *
     * @access private
     * @return string Mac address if found
     * @return string ERROR_OPEN means config can't be found and thus not opened
     * @return string MAC_404 means mac adress doesn't exist in the config file
     * @return string NOT_USE_SERVER_CONFIG the server configuration is not used in license validation.
     *
     * @see __construct()
     */
    public function _get_mac_address()
    {
        # open the config file
        $conf = $this->_get_config();

        # if anyone has any clues for windows environments
        # or other server types let me know
        $os = strtolower(PHP_OS);
        if (substr($os, 0, 3) == 'win') {
            # explode the conf into lines to search for the mac
            $lines = explode($this->_LINEBREAK, $conf);
            # seperate the lines for analysis
            foreach ($lines as $key => $line) {
                # check for the mac signature in the line
                # originally the check was checking for the existence of string 'physical address'
                # however Gert-Rainer Bitterlich pointed out this was for english language
                # based servers only. preg_match updated by Gert-Rainer Bitterlich. Thanks
                if (preg_match("/([0-9a-f][0-9a-f][-:]){5}([0-9a-f][0-9a-f])/i", $line)) {
                    $trimmed_line = trim($line);
                    # take of the mac addres and return
                    return trim(substr($trimmed_line, strrpos($trimmed_line, " ")));
                }
            }
        } else {
            # get the mac delim
            $mac_delim = $this->_get_os_var('mac', $os);

            # get the pos of the os_var to look for
            $pos = strpos($conf, $mac_delim);
            if ($pos) {
                # seperate out the mac address
                $str1 = trim(substr($conf, ($pos + strlen($mac_delim))));
                return trim(substr($str1, 0, strpos($str1, "\n")));
            }
        }
        # failed to find the mac address
        return 'MAC_404';
    }

    /**
     * _get_server_info
     *
     * used to generate the server binds when server binding is needed.
     *
     * @access private
     * @return array server bindings
     * @return boolean false means that the number of bindings failed to
     * 		  meet the required number
     * */
    public function _get_server_info()
    {
        if (empty($this->_SERVER_VARS)) {
            $this->set_server_vars($_SERVER);
        }
        # get the server specific uris
        $a = array();
        if (isset($this->_SERVER_VARS['SERVER_ADDR']) && (!strrpos($this->_SERVER_VARS['SERVER_ADDR'], '127.0.0.1') || $this->ALLOW_LOCAL)) {
            $a['SERVER_ADDR'] = $this->_SERVER_VARS['SERVER_ADDR'];
        }
        # corrected by Gert-Rainer Bitterlich <bitterlich -at- ima-dresden -dot- de>, Thanks
        if (isset($this->_SERVER_VARS['HTTP_HOST']) && (!strrpos($this->_SERVER_VARS['HTTP_HOST'], '127.0.0.1') || $this->ALLOW_LOCAL)) {
            $a['HTTP_HOST'] = $this->_SERVER_VARS['HTTP_HOST'];
        }
        if (isset($this->_SERVER_VARS['SERVER_NAME'])) {
            $a['SERVER_NAME'] = $this->_SERVER_VARS['SERVER_NAME'];
        }
        if (isset($this->_SERVER_VARS['PATH_TRANSLATED'])) {
            $a['PATH_TRANSLATED'] = substr($this->_SERVER_VARS['PATH_TRANSLATED'], 0, strrpos($this->_SERVER_VARS['PATH_TRANSLATED'], '/'));
        } elseif (isset($this->_SERVER_VARS['SCRIPT_FILENAME'])) {
            $a['SCRIPT_FILENAME'] = substr($this->_SERVER_VARS['SCRIPT_FILENAME'], 0, strrpos($this->_SERVER_VARS['SCRIPT_FILENAME'], '/'));
        }
        if (isset($_SERVER['SCRIPT_URI'])) {
            $a['SCRIPT_URI'] = substr($this->_SERVER_VARS['SCRIPT_URI'], 0, strrpos($this->_SERVER_VARS['SCRIPT_URI'], '/'));
        }

        # if the number of different uris is less than the required amount,
        # fail the request
        if (count($a) < $this->REQUIRED_URIS) {
            return 'SERVER_FAILED';
        }

        return $a;
    }

    /**
     * validate
     *
     * validates the server key and returns a data array.
     *
     * @access public
     * @return array Main object in array is 'RESULT', it contains the result
     * 		 of the validation.
     * 		 OK 		- key is valid
     * 		 CORRUPT 	- key has been tampered with
     * 		 TMINUS 	- the key is being used before the valid start date
     * 		 EXPIRED 	- the key has expired
     * 		 ILLEGAL 	- the key is not on the same server the license was registered to
     * 		 ILLEGAL_LOCAL 	- the key is not allowed to be installed on a local machine
     * 		 INVALID 	- the the encryption key used to encrypt the key differs or the key is not complete
     * 		 EMPTY	 	- the the key is empty
     * 		 404	 	- the the key is missing
     * */
    public function validate($str = false, $dialhome = false, $dialhost = "", $dialpath = "", $dialport = "80")
    {
        # check to see if the class has been secured
        $this->_check_secure();
        # get the dat string
        $dat_str = (!$str && !is_null($this->_LICENSE_PATH)) ? @file_get_contents($this->_LICENSE_PATH) : $str;
        if (strlen($dat_str) > 0) {
            # decrypt the data
            $DATA = $this->_unwrap_license($dat_str);
            if (is_array($DATA)) {
                # missing / incorrect id therefore it has been tampered with
                /*
                 *Disable to accept licenses from other workspaces
                 *if ($DATA['ID'] != G::encryptOld($this->ID1)) {
                    $DATA['RESULT'] = 'CORRUPT';
                }*/
                if ($this->USE_TIME) {
                    # the license is being used before it's official start
                    if ($DATA['DATE']['START'] > time() + $this->START_DIF) {
                        $DATA['RESULT'] = 'TMINUS';
                    }
                    # the license has expired
                    if (intval($DATA['DATE']['END']) - time() < 0 && $DATA['DATE']['SPAN'] !== 'NEVER' && $DATA['DATE']['SPAN'] !== '~') {
                        $DATA['RESULT'] = 'EXPIRED';
                    }
                    $DATA['DATE']['HUMAN']['START'] = date($this->DATE_STRING, $DATA['DATE']['START']);
                    if (($DATA['DATE']['END'] == "") || ($DATA['DATE']['END'] == "NEVER")) {
                        $DATA['DATE']['HUMAN']['END'] = "PERPETUAL";
                    } else {
                        $DATA['DATE']['HUMAN']['END'] = date($this->DATE_STRING, $DATA['DATE']['END']);
                    }
                }
                if ($this->USE_SERVER) {
                    $mac = $DATA['SERVER']['MAC'] == $this->_MAC;
                    $path = count(array_diff($this->_SERVER_INFO, $DATA['SERVER']['PATH'])) <= $this->_ALLOWED_SERVER_DIFS;
                    $domain = $this->_compare_domain_ip($DATA['SERVER']['DOMAIN'], $this->_IPS);
                    $ip = count(array_diff($this->_IPS, $DATA['SERVER']['IP'])) <= $this->_ALLOWED_IP_DIFS;

                    # the server details
                    if (!$mac || !$path || !$domain || !$ip) {
                        $DATA['RESULT'] = 'ILLEGAL';
                    }

                    # check if local
                    $local = $this->ALLOW_LOCAL && (in_array('127.0.0.1', $DATA['SERVER']['IP']) || $DATA['PATH']['SERVER_ADDR'] == '127.0.0.1' || $DATA['PATH']['HTTP_HOST'] == '127.0.0.1');
                    if (!$local) {
                        $DATA['RESULT'] = 'ILLEGAL_LOCAL';
                    }
                }
                # passed all current test so license is ok
                if (!isset($DATA['RESULT'])) {
                    # dial to home server if required
                    if ($dialhome) {
                        # create the details to send to the home server
                        $stuff_to_send = array();
                        $stuff_to_send['LICENSE_DATA'] = $DATA;
                        $stuff_to_send['LICENSE_DATA']['KEY'] = G::encryptOld($dat_str);
                        # dial home
                        $DATA['RESULT'] = $this->_call_home($stuff_to_send, $dialhost, $dialpath, $dialport);
                    } else {
                        # result is ok all test passed, license is legal
                        $DATA['RESULT'] = 'OK';
                    }
                }
                /*
                 */
                # data is returned for use
                return $DATA;
            } else {
                # the are two reason that mean a invalid return
                # 1 - the other hash key is different
                # 2 - the key has been tampered with
                return array('RESULT' => 'INVALID');
            }
        }
        # returns empty because there is nothing in the dat_string
        return array('RESULT' => 'EMPTY');
    }

    /**
     * _call_home
     *
     * calls the dial home server (your server) andvalidates the clients license
     * with the info in the mysql db
     *
     * @access private
     * @param $data array Array that contains the info to be validated
     * @param $dialhost string Host name of the server to be contacted
     * @param $dialpath string Path of the script for the data to be sent to
     * @param $dialport number Port Number to send the data through
     * @return string Returns: the encrypted server validation result from the dial home call
     * 						: SOCKET_FAILED		=> socket failed to connect to the server
     * */
    public function _call_home($data, $dialhost, $dialpath, $dialport)
    {
        print "_call_home($data, $dialhost, $dialpath, $dialport)";
        # post the data home
        $data = $this->_post_data($dialhost, $dialpath, $data, $dialport);
        return (empty($data['RESULT'])) ? 'SOCKET_FAILED' : $data['RESULT'];
    }
}
