Cacti / cacti

Cacti ™
http://www.cacti.net
GNU General Public License v2.0
1.62k stars 403 forks source link

Improve support of ping on Windows #5649

Closed MSS970 closed 6 months ago

MSS970 commented 7 months ago

Hi There, If fping is installed in Windows, fping would rung, but the results are check/validated against the logic of Windows ping.

https://github.com/Cacti/cacti/blob/4563aaa1db7cc90f44edb25b23698a1d0208139e/lib/ping.php#L243

I suggest to change the code in ping.php starting from line #243 to the following:

        } else {
            // -- START - Modified by MSS on 2024-01-15
            // ------------------------------------
            if ($fping != '' && file_exists($fping) && is_executable($fping)) {
                $position = strpos($result, 'min/avg/max');

                if ($position > 0) {
                    $output  = trim(str_replace(' ms', '', substr($result, $position)));
                    $pieces  = explode('=', $output);
                    $results = explode('/', $pieces[1]);

                    $this->ping_status   = $results[1];
                    $this->ping_response = __('ICMP Ping Success (%s ms)', $results[1]);

                    return true;
                } else {
                    $this->status        = 'down';
                    $this->ping_response = __('ICMP ping Timed out (' . $this->host['hostname'] . '), Result [' . $result . ']');

                    return false;
                }
            } else {
                $position = strpos($result, 'Minimum');

                if ($position > 0) {
                    $output  = trim(substr($result, $position));
                    $pieces  = explode(',', $output);
                    $results = explode('=', $pieces[2]);

                    $this->ping_status   = trim(str_replace('ms', '', $results[1]));
                    $this->ping_response = __('ICMP Ping Success (%s ms)', $this->ping_status);

                    return true;
                } else {
                    $this->status        = 'down';
                    $this->ping_response = __('ICMP ping Timed out (' . $this->host['hostname'] . '), Result [' . $result . ']');

                    return false;
                }
            }
            // -- END - Modified by MSS on 2024-01-15
            // ------------------------------------
        }
TheWitness commented 7 months ago

That's a good catch. Found some inconsistent use elsewhere too. Not material like lib/ping.php though.

MSS970 commented 7 months ago

Hi TheWitness, I use CACTI dev 1.3.

Furthermore, I don't see any ' ms' in the output of fping as stated in the ping.php, hence my previous suggestion requires fine tuning be removing the part related to ' ms'.

Regards,

TheWitness commented 7 months ago

So, it needs some more tuning. Just for Windows right?

MSS970 commented 7 months ago

Yes, that's right

TheWitness commented 7 months ago

Can you drop the output from doing here?

MSS970 commented 7 months ago

Hi TheWitness,

It seems you have omitted the complete part related to Windows native "ping" if it is in use.

For Windows OS, the native "ping" command is available and could be used if "fping" is NOT installed, however, if the "fping" is installed on Windows then I will recommend to leave the native "ping" works.

The below can be considered as the solution:


function ping_icmp() {
    global $config;

    /* ping me */
    if ($this->host['hostname']) {
        /* initialize variables */
        $this->ping_status   = 'down';
        $this->ping_response = __('ICMP Ping timed out');

        /* establish timeout variables */
        $to_sec  = floor($this->timeout / 1000);
        $to_usec = ($this->timeout % 1000) * 1000;

        /* clean up hostname if specifying snmp_transport */
        $this->host['hostname'] = $this->strip_ip_address($this->host['hostname']);

        /* determine the host's ip address
         * this prevents from command injection as well*/
        if ($this->is_ipaddress($this->host['hostname'])) {
            $host_ip = $this->host['hostname'];
        } else {
            /* again, as a side effect, prevention from command injection */
            $host_ip = cacti_gethostbyname($this->host['hostname']);

            if (!$this->is_ipaddress($host_ip)) {
                cacti_log('WARNING: ICMP Ping Error: cacti_gethostbyname failed for ' . $this->host['hostname']);
                $this->response = 'ICMP Ping Error: cacti_gethostbyname failed for ' . $this->host['hostname'];

                return false;
            }
        }

        /* we have to use the real ping, in cases where windows failed or while using UNIX/Linux */
        $pattern  = bin2hex('cacti-monitoring-system'); // the actual test data

        $fping = read_config_option('path_fping');

        if ($fping != '' && file_exists($fping) && is_executable($fping)) {
            if (strpos($this->host['hostname'], ':') !== false) {
                $result = shell_exec('/usr/sbin/fping6 -q -t ' . $this->timeout . ' -c 1 -r ' . $this->retries . ' ' . $this->host['hostname'] . ' 2>&1');
            } else {
                $result = shell_exec($fping . ' -q -t ' . $this->timeout . ' -c 1 -r ' . $this->retries . ' ' . $this->host['hostname'] . ' 2>&1');
            }
        } else {
            /* host timeout given in ms, recalculate to sec, but make it an integer
             * we might consider to use escapeshellarg on hostname,
             * but this field has already been verified.
             * The other fields are numerical fields only and thus
             * not vulnerable for command injection */
            if (substr_count(strtolower(PHP_OS), 'sun')) {
                $result = shell_exec('ping ' . $this->host['hostname']);
            } elseif (substr_count(strtolower(PHP_OS), 'hpux')) {
                $result = shell_exec('ping -m ' . ceil($this->timeout / 1000) . ' -n ' . $this->retries . ' ' . $this->host['hostname']);
            } elseif (substr_count(strtolower(PHP_OS), 'mac')) {
                $result = shell_exec('ping -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . $this->host['hostname']);
            } elseif (substr_count(strtolower(PHP_OS), 'freebsd')) {
                if (strpos($this->host['hostname'], ':') !== false) {
                    $result = shell_exec('ping6 -X ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . $this->host['hostname']);
                } else {
                    $result = shell_exec('ping -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . $this->host['hostname']);
                }
            } elseif (substr_count(strtolower(PHP_OS), 'darwin')) {
                $result = shell_exec('ping -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . $this->host['hostname']);
            } elseif (substr_count(strtolower(PHP_OS), 'bsd')) {
                $result = shell_exec('ping -w ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . $this->host['hostname']);
            } elseif (substr_count(strtolower(PHP_OS), 'aix')) {
                $result = shell_exec('ping -i ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . $this->host['hostname']);
            } elseif (substr_count(strtolower(PHP_OS), 'winnt')) {
                $result = shell_exec('chcp 437 && ping -w ' . $this->timeout . ' -n ' . $this->retries . ' ' . $this->host['hostname']);
            } else {
                /* please know, that when running SELinux, httpd will throw
                 * ping: cap_set_proc: Permission denied
                 * as it now tries to open an ICMP socket and fails
                 * $result will be empty, then. */
                if (strpos($host_ip, ':') !== false) {
                    $result = shell_exec('ping6 -W ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' -p ' . $pattern . ' ' . $this->host['hostname']);
                } else {
                    $result = shell_exec('ping -W ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' -p ' . $pattern . ' ' . $this->host['hostname'] . ' 2>&1');

                    if ((strpos($result, 'unknown host') !== false || strpos($result, 'Address family') !== false) && file_exists('/bin/ping6')) {
                        $result = shell_exec('ping6 -W ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' -p ' . $pattern . ' ' . $this->host['hostname']);
                    }
                }
            }
        }

        if (strtolower(PHP_OS) != 'winnt') {
            $position = strpos($result, 'min/avg/max');

            if ($position > 0) {
                $output  = trim(str_replace(' ms', '', substr($result, $position)));
                $pieces  = explode('=', $output);
                $results = explode('/', $pieces[1]);

                $this->ping_status   = $results[1];
                $this->ping_response = __('ICMP Ping Success (%s ms)', $results[1]);

                return true;
            } else {
                $this->status        = 'down';
                $this->ping_response = __('ICMP ping Timed out');

                return false;
            }
        } else {
            // -- START - Modified by MSS on 2024-01-15
            // ----------------------------------------
            // in case of strtolower(PHP_OS) = 'winnt'

            if ($fping != '' && file_exists($fping) && is_executable($fping)) {
                // in case fping is used in Windows
                // $results would look like:
                // 172.31.112.2 : xmt/rcv/%loss = 1/1/0%, min/avg/max = 7.01/7.01/7.01

                $position = strpos($result, 'min/avg/max');

                if ($position > 0) {
                    $output  = substr($result, $position);
                    $pieces  = explode('=', $output);
                    $results = explode('/', $pieces[1]);

                    $this->ping_status   = $results[1];
                    $this->ping_response = __('ICMP Ping Success (%s ms)', $results[1]);

                    return true;
                } else {
                    $this->status        = 'down';
                    $this->ping_response = __('ICMP ping Timed out (' . $this->host['hostname'] . '), Result [' . $result . ']');

                    return false;
                }
            } else {
                // in case native ping is used in Windows
                // the ping result would look like:
                /*
                ping -w 500 -n 3 www.google.com

                Pinging forcesafesearch.google.com [216.239.38.120] with 32 bytes of data:
                Reply from 216.239.38.120: bytes=32 time=30ms TTL=53
                Reply from 216.239.38.120: bytes=32 time=22ms TTL=53
                Reply from 216.239.38.120: bytes=32 time=25ms TTL=53

                Ping statistics for 216.239.38.120:
                    Packets: Sent = 3, Received = 3, Lost = 0 (0% loss),
                Approximate round trip times in milli-seconds:
                    Minimum = 22ms, Maximum = 30ms, Average = 25ms
                */
                $position = strpos($result, 'Minimum');

                if ($position > 0) {
                    $output  = trim(substr($result, $position));

                    // $output will be:
                    // Minimum = 22ms, Maximum = 30ms, Average = 25ms

                    $pieces  = explode(',', $output);
                    $results = explode('=', $pieces[2]);

                    $this->ping_status   = trim(str_replace('ms', '', $results[1]));
                    $this->ping_response = __('ICMP Ping Success (%s ms)', $this->ping_status);

                    return true;
                } else {
                    $this->status        = 'down';
                    $this->ping_response = __('ICMP ping Timed out (' . $this->host['hostname'] . '), Result [' . $result . ']');

                    return false;
                }
            }
            // -- END - Modified by MSS on 2024-01-15
            // ------------------------------------
        }
    } else {
        $this->ping_status   = 'down';
        $this->ping_response = __('Destination address not specified');

        return false;
    }
}

Let me know if this is ok.

TheWitness commented 7 months ago

I'll take a look this week. Post here again once I verify.

TheWitness commented 7 months ago

A pull request would be better...