Source of file NetworkHelper.php

Size: 20,768 Bytes - Last Modified: 2020-10-24T02:46:31+00:00

/home/travis/build/NextDom/nextdom-core/src/Helpers/NetworkHelper.php

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
<?php

/* This file is part of Jeedom.
 *
 * Jeedom is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Jeedom is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Jeedom. If not, see <http://www.gnu.org/licenses/>.
 */

/* This file is part of NextDom Software.
 *
 * NextDom is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * NextDom Software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with NextDom Software. If not, see <http://www.gnu.org/licenses/>.
 */

namespace NextDom\Helpers;

use NextDom\Enums\LogTarget;
use NextDom\Enums\UserLocation;
use NextDom\Exceptions\CoreException;
use NextDom\Managers\ConfigManager;
use NextDom\Managers\EqLogicManager;
use NextDom\Managers\PluginManager;
use NextDom\Managers\UpdateManager;
use NextDom\Model\Entity\Cmd;
use NextDom\Model\Entity\Update;

/**
 * Class NetworkHelper
 *
 * @TODO: Dépendance avec le plugin OpenVPN
 *
 * @package NextDom\Helpers
 */
class NetworkHelper
{
    /**
     * Get user source location
     * @return string
     * @throws \Exception
     */
    public static function getUserLocation(): string
    {
        $clientIp = self::getClientIp();
        $nextdomIp = self::getNetworkAccess(UserLocation::INTERNAL, 'ip', '', false);
        // Check validate IP
        if (!filter_var($nextdomIp, FILTER_VALIDATE_IP)) {
            return UserLocation::EXTERNAL;
        }
        // Check 4 parts of the IP
        // @TODO: Pourquoi ? Si l'ip est valide
        $nextdomIpParts = explode('.', $nextdomIp);
        if (count($nextdomIpParts) !== 4) {
            return UserLocation::EXTERNAL;
        }
        // Check all local IPs if defined
        if (ConfigManager::byKey('network::localip') != '') {
            $localIps = explode(';', ConfigManager::byKey('network::localip'));
            foreach ($localIps as $localIp) {
                if (self::netMatch($localIp, $clientIp)) {
                    return UserLocation::INTERNAL;
                }
            }
        }
        // Check CIDR /24
        $match = $nextdomIpParts[0] . '.' . $nextdomIpParts[1] . '.' . $nextdomIpParts[2] . '.*';
        return self::netMatch($match, $clientIp) ? UserLocation::INTERNAL : UserLocation::EXTERNAL;
    }

    /**
     * Get server IP from PHP server data
     *
     * @return string Server IP
     */
    public static function getClientIp(): string
    {
        if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            return $_SERVER['HTTP_X_FORWARDED_FOR'];
        } elseif (isset($_SERVER['HTTP_X_REAL_IP'])) {
            return $_SERVER['HTTP_X_REAL_IP'];
        } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
            return $_SERVER['HTTP_CLIENT_IP'];
        } elseif (isset($_SERVER['REMOTE_ADDR'])) {
            return $_SERVER['REMOTE_ADDR'];
        }
        return '';
    }

    /**
     * Get type of network access
     *
     * @param string $_mode
     * @param string $_protocol
     * @param string $_default
     * @param bool $_test
     * @return mixed|string
     * @throws \Exception
     */
    public static function getNetworkAccess($_mode = 'auto', $_protocol = '', $_default = '', $_test = false)
    {
        if ($_mode == 'auto') {
            $_mode = self::getUserLocation();
        }
        if ($_mode == UserLocation::INTERNAL && ConfigManager::byKey('internalAddr', 'core', '') == '') {
            self::checkConf($_mode);
        }
        if ($_mode == UserLocation::EXTERNAL && ConfigManager::byKey('market::allowDNS') != 1 && ConfigManager::byKey('externalAddr', 'core', '') == '') {
            self::checkConf($_mode);
        }
        if ($_test && !self::test($_mode)) {
            self::checkConf($_mode);
        }
        if ($_mode == UserLocation::INTERNAL) {
            if (strpos(ConfigManager::byKey('internalAddr', 'core', $_default), 'http://') !== false || strpos(ConfigManager::byKey('internalAddr', 'core', $_default), 'https://') !== false) {
                ConfigManager::save('internalAddr', str_replace(['http://', 'https://'], '', ConfigManager::byKey('internalAddr', 'core', $_default)));
            }
            if ($_protocol == 'ip' || $_protocol == 'dns') {
                return ConfigManager::byKey('internalAddr', 'core', $_default);
            }
            if ($_protocol == 'ip:port' || $_protocol == 'dns:port') {
                return ConfigManager::byKey('internalAddr') . ':' . ConfigManager::byKey('internalPort', 'core', 80);
            }
            if ($_protocol == 'proto:ip' || $_protocol == 'proto:dns') {
                return ConfigManager::byKey('internalProtocol') . ConfigManager::byKey('internalAddr');
            }
            if ($_protocol == 'proto:ip:port' || $_protocol == 'proto:dns:port') {
                return ConfigManager::byKey('internalProtocol') . ConfigManager::byKey('internalAddr') . ':' . ConfigManager::byKey('internalPort', 'core', 80);
            }
            if ($_protocol == 'proto:127.0.0.1:port:comp') {
                return trim(ConfigManager::byKey('internalProtocol') . '127.0.0.1:' . ConfigManager::byKey('internalPort', 'core', 80) . '/' . trim(ConfigManager::byKey('internalComplement'), '/'), '/');
            }
            if ($_protocol == 'http:127.0.0.1:port:comp') {
                return trim('http://127.0.0.1:' . ConfigManager::byKey('internalPort', 'core', 80) . '/' . trim(ConfigManager::byKey('internalComplement'), '/'), '/');
            }
            return trim(ConfigManager::byKey('internalProtocol') . ConfigManager::byKey('internalAddr') . ':' . ConfigManager::byKey('internalPort', 'core', 80) . '/' . trim(ConfigManager::byKey('internalComplement'), '/'), '/');

        }
        if ($_mode == 'dnsnextdom') {
            return ConfigManager::byKey('nextdom::url');
        }
        if ($_mode == UserLocation::EXTERNAL) {
            if ($_protocol == 'ip') {
                if (ConfigManager::byKey('market::allowDNS') == 1 && ConfigManager::byKey('nextdom::url') != '' && ConfigManager::byKey('network::disableMangement') == 0) {
                    return self::getIpFromString(ConfigManager::byKey('nextdom::url'));
                }
                return self::getIpFromString(ConfigManager::byKey('externalAddr'));
            }
            if ($_protocol == 'ip:port') {
                if (ConfigManager::byKey('market::allowDNS') == 1 && ConfigManager::byKey('nextdom::url') != '' && ConfigManager::byKey('network::disableMangement') == 0) {
                    $url = parse_url(ConfigManager::byKey('nextdom::url'));
                    if (isset($url['host'])) {
                        if (isset($url['port'])) {
                            return self::getIpFromString($url['host']) . ':' . $url['port'];
                        } else {
                            return self::getIpFromString($url['host']);
                        }
                    }
                }
                return ConfigManager::byKey('externalAddr') . ':' . ConfigManager::byKey('externalPort', 'core', 80);
            }
            if ($_protocol == 'proto:dns:port' || $_protocol == 'proto:ip:port') {
                if (ConfigManager::byKey('market::allowDNS') == 1 && ConfigManager::byKey('nextdom::url') != '' && ConfigManager::byKey('network::disableMangement') == 0) {
                    $url = parse_url(ConfigManager::byKey('nextdom::url'));
                    $return = '';
                    if (isset($url['scheme'])) {
                        $return = $url['scheme'] . '://';
                    }
                    if (isset($url['host'])) {
                        if (isset($url['port'])) {
                            return $return . $url['host'] . ':' . $url['port'];
                        } else {
                            return $return . $url['host'];
                        }
                    }
                }
                return ConfigManager::byKey('externalProtocol') . ConfigManager::byKey('externalAddr') . ':' . ConfigManager::byKey('externalPort', 'core', 80);
            }
            if ($_protocol == 'proto:dns' || $_protocol == 'proto:ip') {
                if (ConfigManager::byKey('market::allowDNS') == 1 && ConfigManager::byKey('nextdom::url') != '' && ConfigManager::byKey('network::disableMangement') == 0) {
                    $url = parse_url(ConfigManager::byKey('nextdom::url'));
                    $return = '';
                    if (isset($url['scheme'])) {
                        $return = $url['scheme'] . '://';
                    }
                    if (isset($url['host'])) {
                        if (isset($url['port'])) {
                            return $return . $url['host'] . ':' . $url['port'];
                        } else {
                            return $return . $url['host'];
                        }
                    }
                }
                return ConfigManager::byKey('externalProtocol') . ConfigManager::byKey('externalAddr');
            }
            if ($_protocol == 'dns:port') {
                if (ConfigManager::byKey('market::allowDNS') == 1 && ConfigManager::byKey('nextdom::url') != '' && ConfigManager::byKey('network::disableMangement') == 0) {
                    $url = parse_url(ConfigManager::byKey('nextdom::url'));
                    if (isset($url['host'])) {
                        if (isset($url['port'])) {
                            return $url['host'] . ':' . $url['port'];
                        } else {
                            return $url['host'];
                        }
                    }
                }
                return ConfigManager::byKey('externalAddr') . ':' . ConfigManager::byKey('externalPort', 'core', 80);
            }
            if ($_protocol == 'proto') {
                if (ConfigManager::byKey('market::allowDNS') == 1 && ConfigManager::byKey('nextdom::url') != '' && ConfigManager::byKey('network::disableMangement') == 0) {
                    $url = parse_url(ConfigManager::byKey('nextdom::url'));
                    if (isset($url['scheme'])) {
                        return $url['scheme'] . '://';
                    }
                }
                return ConfigManager::byKey('externalProtocol');
            }
            if (ConfigManager::byKey('dns::token') != '' && ConfigManager::byKey('market::allowDNS') == 1 && ConfigManager::byKey('nextdom::url') != '' && ConfigManager::byKey('network::disableMangement') == 0) {
                return trim(ConfigManager::byKey('nextdom::url') . '/' . trim(ConfigManager::byKey('externalComplement', 'core', ''), '/'), '/');
            }
            return trim(ConfigManager::byKey('externalProtocol') . ConfigManager::byKey('externalAddr') . ':' . ConfigManager::byKey('externalPort', 'core', 80) . '/' . trim(ConfigManager::byKey('externalComplement'), '/'), '/');
        }
        return '';
    }

    /**
     * @param string $_mode
     * @throws \Exception
     */
    public static function checkConf($_mode = UserLocation::EXTERNAL)
    {
        if (ConfigManager::byKey($_mode . 'Protocol') == '') {
            ConfigManager::save($_mode . 'Protocol', 'http://');
        }
        if (ConfigManager::byKey($_mode . 'Port') == '') {
            ConfigManager::save($_mode . 'Port', 80);
        }
        if (ConfigManager::byKey($_mode . 'Protocol') == 'https://' && ConfigManager::byKey($_mode . 'Port') == 80) {
            ConfigManager::save($_mode . 'Port', 443);
        }
        if (ConfigManager::byKey($_mode . 'Protocol') == 'http://' && ConfigManager::byKey($_mode . 'Port') == 443) {
            ConfigManager::save($_mode . 'Port', 80);
        }
        if (trim(ConfigManager::byKey($_mode . 'Complement')) == '/') {
            ConfigManager::save($_mode . 'Complement', '');
        }
        if ($_mode == UserLocation::INTERNAL) {
            foreach (self::getInterfacesList() as $interface) {
                if ($interface == 'lo') {
                    continue;
                }
                $ip = self::getInterfaceIp($interface);
                if (!self::netMatch('127.0.*.*', $ip) && $ip != '' && filter_var($ip, FILTER_VALIDATE_IP)) {
                    ConfigManager::save('internalAddr', $ip);
                    break;
                }
            }
        }
    }

    /**
     * @return array
     * @throws \Exception
     */
    public static function getInterfacesList()
    {
        $interfaces = explode("\n", shell_exec(SystemHelper::getCmdSudo() . "ip -o link show | awk -F': ' '{print $2}'"));
        $result = [];
        foreach ($interfaces as $interface) {
            if (trim($interface) == '') {
                continue;
            }
            $result[] = $interface;
        }
        return $result;
    }

    /*     * *********************DNS************************* */

    /**
     * @param $_interface
     * @return bool|string
     * @throws \Exception
     */
    public static function getInterfaceIp($_interface)
    {
        $ip = trim(shell_exec(SystemHelper::getCmdSudo() . "ip addr show " . $_interface . " 2> /dev/null | grep \"inet .*" . $_interface . "\" | awk '{print $2}' | cut -d '/' -f 1"));
        if (filter_var($ip, FILTER_VALIDATE_IP)) {
            return $ip;
        }
        return false;
    }

    /**
     * @param string $network
     * @param string $ip
     *
     * @return bool
     */
    public static function netMatch($network, $ip)
    {
        $ip = trim($ip);
        if ($ip === trim($network)) {
            return true;
        }
        $network = str_replace(' ', '', $network);
        if (strpos($network, '*') !== false) {
            if (strpos($network, '/') !== false) {
                $asParts = explode('/', $network);
                if ($asParts[0]) {
                    $network = $asParts[0];
                } else {
                    $network = null;
                }
            }
            $nCount = substr_count($network, '*');
            $network = str_replace('*', '0', $network);
            if ($nCount == 1) {
                $network .= '/24';
            } elseif ($nCount == 2) {
                $network .= '/16';
            } elseif ($nCount == 3) {
                $network .= '/8';
            } elseif ($nCount > 3) {
                return true; // if *.*.*.*, then all, so matched
            }
        }

        $d = strpos($network, '-');
        if ($d === false) {
            if (strpos($network, '/') === false) {
                if ($ip == $network) {
                    return true;
                }
                return false;
            }
            $ip_arr = explode('/', $network);
            if (!preg_match("@\d*\.\d*\.\d*\.\d*@", $ip_arr[0], $matches)) {
                $ip_arr[0] .= ".0"; // Alternate form 194.1.4/24
            }
            $network_long = ip2long($ip_arr[0]);
            $x = ip2long($ip_arr[1]);
            $mask = long2ip($x) == $ip_arr[1] ? $x : (0xffffffff << (32 - $ip_arr[1]));
            $ip_long = ip2long($ip);
            return ($ip_long & $mask) == ($network_long & $mask);
        } else {
            $from = trim(ip2long(substr($network, 0, $d)));
            $to = trim(ip2long(substr($network, $d + 1)));
            $ip = ip2long($ip);
            return ($ip >= $from && $ip <= $to);
        }
    }

    /**
     * @param string $_mode
     * @param int $_timeout
     * @return bool
     * @throws \Exception
     */
    public static function test($_mode = UserLocation::EXTERNAL, $_timeout = 2)
    {
        if (ConfigManager::byKey('network::disableMangement') == 1) {
            return true;
        }
        if ($_mode == UserLocation::INTERNAL && self::netMatch('127.0.*.*', self::getNetworkAccess($_mode, 'ip', '', false))) {
            return false;
        }
        $url = trim(self::getNetworkAccess($_mode, '', '', false), '/') . '/public/here.html';
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_TIMEOUT, $_timeout);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_HEADER, false);
        if ($_mode == UserLocation::EXTERNAL) {
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        }
        $data = curl_exec($ch);
        if (curl_errno($ch)) {
            LogHelper::addDebug(LogTarget::NETWORK, 'Erreur sur ' . $url . ' => ' . curl_errno($ch));
            curl_close($ch);
            return false;
        }
        curl_close($ch);
        if (trim($data) != 'ok') {
            LogHelper::addDebug(LogTarget::NETWORK, 'Retour NOK sur ' . $url . ' => ' . $data);
            return false;
        }
        return true;
    }

    /**
     * @param $_string
     * @return bool|mixed|string
     */
    public static function getIpFromString($_string)
    {
        $result = parse_url($_string);
        if (isset($result['host'])) {
            $_string = $result['host'];
        } else {
            $_string = str_replace(['https://', 'http://'], '', $_string);
            if (strpos($_string, '/') !== false) {
                $_string = substr($_string, 0, strpos($_string, '/'));
            }
            if (strpos($_string, ':') !== false) {
                $_string = substr($_string, 0, strpos($_string, ':'));
            }
        }
        if (!filter_var($_string, FILTER_VALIDATE_IP)) {
            $_string = gethostbyname($_string);
        }
        return $_string;
    }

    /**
     * @param $_interface
     * @return bool|string
     * @throws \Exception
     */
    public static function getInterfaceMac($_interface)
    {
        $valid_mac = "([0-9A-F]{2}[:-]){5}([0-9A-F]{2})";
        $mac = trim(shell_exec(SystemHelper::getCmdSudo() . "ip addr show " . $_interface . " 2>&1 | grep ether | awk '{print $2}'"));
        if (preg_match("/" . $valid_mac . "/i", $mac)) {
            return $mac;
        }
        return false;
    }

    public static function cron5()
    {
        if (ConfigManager::byKey('network::disableMangement') == 1) {
            return;
        }
        if (!self::test(UserLocation::INTERNAL)) {
            self::checkConf(UserLocation::INTERNAL);
        }
        if (!self::test(UserLocation::EXTERNAL)) {
            self::checkConf(UserLocation::EXTERNAL);
        }
        if (!NextDomHelper::isCapable('sudo') || NextDomHelper::getHardwareName() == 'docker') {
            return;
        }
        exec(SystemHelper::getCmdSudo() . 'ping -n -c 1 -t 255 8.8.8.8 2>&1 > /dev/null', $output, $return_val);
        if ($return_val == 0) {
            return;
        }
        $gw = shell_exec("ip route show default | awk '/default/ {print $3}'");
        if ($gw == '') {
            LogHelper::addError('network', __('Souci réseau détecté, redémarrage du réseau. Aucune gateway de trouvée'));
            exec(SystemHelper::getCmdSudo() . 'service networking restart');
            return;
        }
        exec(SystemHelper::getCmdSudo() . 'ping -n -c 1 -t 255 ' . $gw . ' 2>&1 > /dev/null', $output, $return_val);
        if ($return_val == 0) {
            return;
        }
        exec(SystemHelper::getCmdSudo() . 'ping -n -c 1 -t 255 ' . $gw . ' 2>&1 > /dev/null', $output, $return_val);
        if ($return_val == 0) {
            return;
        }
        LogHelper::addError('network', __('Souci réseau détecté, redémarrage du réseau. La gateway ne répond pas au ping : ') . $gw);
        exec(SystemHelper::getCmdSudo() . 'service networking restart');
    }

    public static function checkOpenPort($targetHost, $targetPort) {
        $fp = @fsockopen($targetHost, $targetPort, $errno, $errstr, 0.1);
        if (!is_resource($fp)) {
            return false;
        }
        fclose($fp);
        return true;
    }

}