From 18601f1df0c8331a097a43162a51f3d7fd40b22b Mon Sep 17 00:00:00 2001 From: UBiqube-ydu Date: Thu, 22 Jan 2026 15:38:27 +0100 Subject: [PATCH 1/3] OPSLAB-245: Enhance error management in stormshield --- adapters/stormshield/connect.php | 39 ++++++++++------- adapters/stormshield/nsrpc.php | 74 ++++++++++++++++++++++++++------ 2 files changed, 83 insertions(+), 30 deletions(-) diff --git a/adapters/stormshield/connect.php b/adapters/stormshield/connect.php index 20a887f3..b5b3533b 100644 --- a/adapters/stormshield/connect.php +++ b/adapters/stormshield/connect.php @@ -5,6 +5,9 @@ require_once 'smsd/sms_common.php'; require_once 'smsd/expect.php'; require_once 'smsd/generic_connection.php'; + +require_once load_once('stormshield', 'nsrpc.php'); + require_once "$db_objects"; class connect extends GenericConnection { @@ -21,6 +24,7 @@ class connect extends GenericConnection { private $response; private $cookie; private $session_id; + private $curl_cmd; public function __construct($ip = null, $login = null, $passwd = null, $admin_password = null, $port = null) { @@ -117,15 +121,6 @@ public function send($origin, $rest_cmd) { $url = "https://{$this->sd_ip_config}/{$rest_path}"; - $headers = ''; - foreach ($this->http_header_list as $header) { - $H = trim($header); - $headers .= " -H '{$H}'"; - } - - // for debug - $curl_cmd = "curl -X {$http_op} {$headers} --connect-timeout {$this->conn_timeout} --max-time {$this->conn_timeout} -k '{$url}'"; - if (count($cmd_list) > 2) { if (isset($this->session_id)) { $payload = $cmd_list[2]; @@ -157,14 +152,25 @@ public function send($origin, $rest_cmd) { $rest_payload = ''; } - $curl_cmd .= " --data-raw '{$rest_payload}'"; + $this->execute_curl_command($origin, $http_op, $url, $rest_payload); - debug_dump($curl_cmd, "HTTP REQUEST:\n"); - $this->execute_curl_command($origin, $http_op, $url, $rest_payload, $curl_cmd); - debug_dump($this->response, "HTTP RESPONSE:\n"); + $ret = is_error_xml($this->response); + if ($ret !== false) + { + throw new SmsException("Response to API {$this->curl_cmd} Failed: \n$ret", ERR_SD_CMDFAILED, $origin); + } } - private function execute_curl_command($origin, $http_op, $url, $rest_payload, $curl_cmd) { + private function execute_curl_command($origin, $http_op, $url, $rest_payload) { + + // for debug + $headers = ''; + foreach ($this->http_header_list as $h) { + $H = trim($h); + $headers .= " -H '{$H}'"; + } + $this->curl_cmd = "curl -X {$http_op} {$headers} --connect-timeout {$this->conn_timeout} --max-time {$this->conn_timeout} -k '{$url}' --data-raw '{$rest_payload}'"; + debug_dump($this->curl_cmd, "HTTP REQUEST:\n"); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); @@ -206,7 +212,7 @@ private function execute_curl_command($origin, $http_op, $url, $rest_payload, $c if ($http_code < 200 || $http_code > 209) { $cmd_quote = str_replace("\"", "'", $body); $cmd_return = str_replace("\n", "", $cmd_quote); - throw new SmsException("Call to API {$curl_cmd} Failed, header = $header, $cmd_return error", ERR_SD_CMDFAILED, $origin); + throw new SmsException("Call to API {$this->curl_cmd} Failed, header = $header, $cmd_return error", ERR_SD_CMDFAILED, $origin); } if (!isset($this->cookie)) { @@ -221,9 +227,10 @@ private function execute_curl_command($origin, $http_op, $url, $rest_payload, $c else { if ($http_code != 204) { - throw new SmsException ("$origin: Response to API {$curl_cmd} Failed, expected json received empty response, header $header", ERR_SD_CMDFAILED); + throw new SmsException ("$origin: Response to API {$this->curl_cmd} Failed, expected json received empty response, header $header", ERR_SD_CMDFAILED); } } + debug_dump($this->response, "HTTP RESPONSE:\n"); } } diff --git a/adapters/stormshield/nsrpc.php b/adapters/stormshield/nsrpc.php index f2e805ee..e75f6348 100644 --- a/adapters/stormshield/nsrpc.php +++ b/adapters/stormshield/nsrpc.php @@ -27,6 +27,22 @@ 206 licence restriction */ +// return code considered as ok +$ok_return_code = array ( + "100" => true, + "103" => true, + "104" => true, + "110" => true, + "111" => true, +); + +// intermediate return code, it is normaly followed by another return code +$not_a_return_code = array ( + "101" => true, + "102" => true, +); + + function get_return_codes(&$nsrpc_output) { define("RC_LEN", 3); @@ -73,20 +89,8 @@ function get_return_codes(&$nsrpc_output) */ function is_error(&$nsrpc_output) { - // return code considered as ok - $ok_return_code = array ( - "100" => true, - "103" => true, - "104" => true, - "110" => true, - "111" => true, - ); - - // intermediate return code, it is normaly followed by another return code - $not_a_return_code = array ( - "101" => true, - "102" => true, - ); + global $ok_return_code; + global $not_a_return_code; $rc_list = get_return_codes($nsrpc_output); @@ -105,6 +109,48 @@ function is_error(&$nsrpc_output) return false; } +/* + * Get return codes of SimpleXMLElement response from the ME + * and check if there is an error, stops at the first error + * return an error message if any, false otherwise + */ +function is_error_xml($xml) +{ + global $ok_return_code; + global $not_a_return_code; + + $return_list = array(); + + if (isset($xml->command)) + { + foreach ($xml->command as $command) + { + if (isset($command->serverd)) + { + foreach ($command->serverd as $serverd) + { + if (isset($serverd['ret'])) + { + $return_list[] = $serverd->attributes(); + $rc = (int)$serverd['ret']; + if (empty($ok_return_code[$rc]) && empty($not_a_return_code[$rc])) + { + $err = ''; + foreach ($return_list as $ret) + { + $err .= "ret={$ret['ret']}, code={$ret['code']}, msg={$ret['msg']} \n"; + } + return $err; + } + } + } + } + } + } + + return false; +} + /* * Get the return code from $nsrpc_output for the command $cmd to see if a reboot is needed * Assume no error, this means is_error() is called before From 39a73574394dcf265b249e755dd1dcc7d22a4047 Mon Sep 17 00:00:00 2001 From: UBiqube-ydu Date: Thu, 22 Jan 2026 16:44:42 +0100 Subject: [PATCH 2/3] OPSLAB-245: Fix copilot comment in stormshield --- adapters/stormshield/nsrpc.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/adapters/stormshield/nsrpc.php b/adapters/stormshield/nsrpc.php index e75f6348..78fcfd08 100644 --- a/adapters/stormshield/nsrpc.php +++ b/adapters/stormshield/nsrpc.php @@ -36,7 +36,7 @@ "111" => true, ); -// intermediate return code, it is normaly followed by another return code +// intermediate return code, it is normally followed by another return code $not_a_return_code = array ( "101" => true, "102" => true, @@ -110,7 +110,7 @@ function is_error(&$nsrpc_output) } /* - * Get return codes of SimpleXMLElement response from the ME + * Get return codes of a SimpleXMLElement response from the managed entity * and check if there is an error, stops at the first error * return an error message if any, false otherwise */ @@ -119,6 +119,11 @@ function is_error_xml($xml) global $ok_return_code; global $not_a_return_code; + if (empty($xml)) + { + return false; + } + $return_list = array(); if (isset($xml->command)) @@ -132,7 +137,7 @@ function is_error_xml($xml) if (isset($serverd['ret'])) { $return_list[] = $serverd->attributes(); - $rc = (int)$serverd['ret']; + $rc = (string)$serverd['ret']; if (empty($ok_return_code[$rc]) && empty($not_a_return_code[$rc])) { $err = ''; From c35078c236f75ce7622e6268ac7dd7d38bed32c3 Mon Sep 17 00:00:00 2001 From: UBiqube-ydu Date: Mon, 26 Jan 2026 14:24:16 +0100 Subject: [PATCH 3/3] OPSLAB-245: Fix restore for Stormshield --- adapters/stormshield/conf/device.properties | 2 +- adapters/stormshield/connect_cli.php | 4 +- adapters/stormshield/do_get_archive_conf.php | 2 +- adapters/stormshield/do_restore_conf.php | 173 ++++++++++++++++--- 4 files changed, 157 insertions(+), 24 deletions(-) diff --git a/adapters/stormshield/conf/device.properties b/adapters/stormshield/conf/device.properties index f1c81550..5b99fe68 100644 --- a/adapters/stormshield/conf/device.properties +++ b/adapters/stormshield/conf/device.properties @@ -3,5 +3,5 @@ model.name = Generic manufacturer.id = 16010401 manufacturer.name = Stormshield manufacturer.category = Security -feature.restore = false +feature.restore = true obsolete = false diff --git a/adapters/stormshield/connect_cli.php b/adapters/stormshield/connect_cli.php index d1f8fcfd..0b93a5ae 100644 --- a/adapters/stormshield/connect_cli.php +++ b/adapters/stormshield/connect_cli.php @@ -42,8 +42,8 @@ public function do_connect() { } public function do_disconnect() { - $this->sendexpectone(__FILE__.':'.__LINE__, "quit", '>'); - $this->sendCmd(__FILE__.':'.__LINE__, 'exit'); + //$this->sendexpectone(__FILE__.':'.__LINE__, "quit", '>'); + //$this->sendCmd(__FILE__.':'.__LINE__, 'exit'); parent::disconnect(); } diff --git a/adapters/stormshield/do_get_archive_conf.php b/adapters/stormshield/do_get_archive_conf.php index 4af490e7..bf55f911 100644 --- a/adapters/stormshield/do_get_archive_conf.php +++ b/adapters/stormshield/do_get_archive_conf.php @@ -55,7 +55,7 @@ echo "/opt/sms/bin/sms_scp_transfer -r -s $local_backup -d $target_file -a $sd->SD_IP_CONFIG -l $sd->SD_LOGIN_ENTRY -p '$sd->SD_PASSWD_ENTRY' -P $sd->SD_MANAGEMENT_PORT\n"; -$ret_scp = exec_local(__FILE__.':'.__LINE__, "/opt/sms/bin/sms_scp_transfer -r -s $local_backup -d $target_file -a $sd->SD_IP_CONFIG -l $sd->SD_LOGIN_ENTRY -p '$sd->SD_PASSWD_ENTRY' -P $sd->SD_MANAGEMENT_PORT", $output); +exec_local(__FILE__.':'.__LINE__, "/opt/sms/bin/sms_scp_transfer -r -s $local_backup -d $target_file -a $sd->SD_IP_CONFIG -l $sd->SD_LOGIN_ENTRY -p '$sd->SD_PASSWD_ENTRY' -P $sd->SD_MANAGEMENT_PORT", $output); // remove the backup on the ME connect(); diff --git a/adapters/stormshield/do_restore_conf.php b/adapters/stormshield/do_restore_conf.php index d5448ddd..037f374c 100644 --- a/adapters/stormshield/do_restore_conf.php +++ b/adapters/stormshield/do_restore_conf.php @@ -16,11 +16,136 @@ */ // Restore configuration from archive file -// TODO require_once 'smsd/sms_common.php'; -require_once load_once('stormshield', 'netasq_configuration.php'); +require_once load_once('stormshield', 'connect_cli.php'); +require_once load_once('stormshield', 'nsrpc.php'); + +function get_old_revision($restore_conf_file, $revision_id) +{ + global $sdid; + + echo("restore_from_old_revision revision_id: $revision_id\n"); + + $get_saved_conf_cmd = "/opt/sms/script/get_saved_conf --getfile {$sdid} file {$restore_conf_file} r{$revision_id}"; + + $ret = exec_local(__FILE__ . ':' . __LINE__, $get_saved_conf_cmd, $output); + if ($ret !== SMS_OK) + { + echo("no running conf found\n"); + unlink($restore_conf_file); + return $ret; + } + + if (!file_exists($restore_conf_file)) + { + echo("no running conf found\n"); + return ERR_CONFIG_EMPTY; + } + + return SMS_OK; +} + +function copy_restore_file_to_me($sd, $src, $dst) +{ + global $sms_sd_ctx; + + $ret = SMS_OK; + + echo "/opt/sms/bin/sms_scp_transfer -s $src -d $dst -a $sd->SD_IP_CONFIG -l $sd->SD_LOGIN_ENTRY -p '$sd->SD_PASSWD_ENTRY' -P $sd->SD_MANAGEMENT_PORT\n"; + + exec_local(__FILE__.':'.__LINE__, "/opt/sms/bin/sms_scp_transfer -s $src -d $dst -a $sd->SD_IP_CONFIG -l $sd->SD_LOGIN_ENTRY -p '$sd->SD_PASSWD_ENTRY' -P $sd->SD_MANAGEMENT_PORT", $output); + // no check ot the outcome, will check the presence of the file on the ME later on + + // remove the backup on the MSA + unlink($src); + + // check file on ME + connect(); + + sendexpectone(__FILE__.':'.__LINE__, $sms_sd_ctx, "quit", '>'); + $result = sendexpectone(__FILE__.':'.__LINE__, $sms_sd_ctx, "test -f $dst; echo $?", '>'); + if (strpos($result, "\n0") === false) + { + $err = __FILE__ . ':' . __LINE__ . ": Impossible to copy restore file from $src to $dst\n"; + sms_log_error("$err\n"); + $ret = ERR_SD_CMDFAILED; + } + + disconnect(); + + return $ret; +} + +function restore_conf($sd, $restore_conf_file) +{ + global $sms_sd_ctx; + + connect(); + + sendexpectone(__FILE__.':'.__LINE__, $sms_sd_ctx, "modify on force"); + + $ret = sendexpectone(__FILE__.':'.__LINE__, $sms_sd_ctx, "config restore list=\"all\" refresh=1 < $restore_conf_file"); + + sendexpectone(__FILE__.':'.__LINE__, $sms_sd_ctx, "quit", '>'); + sendexpectone(__FILE__.':'.__LINE__, $sms_sd_ctx, "rm $restore_conf_file", '>'); + + if (is_error($ret)) + { + $err = __FILE__ . ':' . __LINE__ . ": Error config restore list=\"all\" refresh=1 < $restore_conf_file returns\n$ret"; + sms_log_error("$err\n"); + disconnect(); + return ERR_SD_CMDFAILED; + } + + // reboot + sendexpectone(__FILE__.':'.__LINE__, $sms_sd_ctx, 'cli', 'assword'); + sendexpectone(__FILE__.':'.__LINE__, $sms_sd_ctx, $sd->SD_PASSWD_ENTRY); + sendexpectone(__FILE__.':'.__LINE__, $sms_sd_ctx, "system reboot", '>'); + + disconnect(); + + return SMS_OK; +} + +function wait_until_device_is_up($sd, $nb_loop = 60, $initial_sec_to_wait = 30) +{ + // wait the device become up after reboot + $ret = wait_for_device_up($sd->SD_IP_CONFIG, $nb_loop, $initial_sec_to_wait); + if ($ret != SMS_OK) + { + return $ret; + } + + sms_sleep($initial_sec_to_wait); // Wait for the ssh service + $done = $nb_loop; + do + { + echo "waiting for the device (SSH), $done\n"; + sleep(5); + try + { + connect(); + break; + } + catch (Exception | Error $e) + { + $done--; + } + } while ($done > 0); + + if ($done === 0) + { + sms_log_error(__FILE__ . ':' . __LINE__ . ": The device stay DOWN\n"); + return ERR_SD_CMDTMOUT; + } + + disconnect(); + + return SMS_OK; +} + $ret = sms_sd_lock($sms_csp, $sms_sd_info); if ($ret !== 0) @@ -37,39 +162,47 @@ // Asynchronous mode, the user socket is now closed, the results are written in database +$network = get_network_profile(); +$sd = &$network->SD; + try { - netasq_connect(); - - $conf = new netasq_configuration($sdid); + $restore_conf_file = "{$sdid}_r{$revision_id}.conf"; + $msa_restore_conf_file = "/opt/sms/spool/tmp/{$restore_conf_file}"; - // Should be connected before calling restore_from_old_revision - // After restore_from_old_revision is called, we are disconnected - $ret = $conf->restore_from_old_revision($revision_id); + $ret = get_old_revision($msa_restore_conf_file, $revision_id); if ($ret !== SMS_OK) { - sms_set_update_status($sms_csp, $sdid, $ret, 'RESTORE', 'FAILED', "Restoring revision $revision_id failed"); + sms_set_update_status($sms_csp, $sdid, $ret, 'RESTORE', 'FAILED', "Getting revision $revision_id failed"); sms_sd_unlock($sms_csp, $sms_sd_info); return SMS_OK; } - sms_set_update_status($sms_csp, $sdid, SMS_OK, 'RESTORE', 'WORKING', "Waiting the device (restore revision: $revision_id)"); - $ret = $conf->wait_until_device_is_up(60, 0); - if ($ret !== SMS_OK) + $me_restore_conf_file = "/tmp/{$restore_conf_file}"; + + $ret = copy_restore_file_to_me($sd, $msa_restore_conf_file, $me_restore_conf_file); + if ($ret != SMS_OK) { - sms_set_update_status($sms_csp, $sdid, $ret, 'RESTORE', 'FAILED', "The device is unreachable after restoring the configuration (restore revision: $revision_id)"); + sms_set_update_status($sms_csp, $sdid, $ret, 'RESTORE', 'FAILED', "Copying revision $revision_id failed"); sms_sd_unlock($sms_csp, $sms_sd_info); return SMS_OK; } - if ($conf->sd->SD_HSRP_TYPE !== 0) + $ret = restore_conf($sd, $me_restore_conf_file); + if ($ret != SMS_OK) { - netasq_connect(); - - sms_set_update_status($sms_csp, $sdid, SMS_OK, 'RESTORE', 'WORKING', "Synchronize the configuration on the passive node (restore revision: $revision_id)"); - $ret = $conf->ha_sync(); + sms_set_update_status($sms_csp, $sdid, $ret, 'RESTORE', 'FAILED', "Restoring revision $revision_id failed"); + sms_sd_unlock($sms_csp, $sms_sd_info); + return SMS_OK; + } - netasq_disconnect(); + sms_set_update_status($sms_csp, $sdid, SMS_OK, 'RESTORE', 'WORKING', "Waiting the device (restore revision: $revision_id)"); + $ret = wait_until_device_is_up($sd); + if ($ret !== SMS_OK) + { + sms_set_update_status($sms_csp, $sdid, $ret, 'RESTORE', 'FAILED', "The device is unreachable after restoring the configuration (restore revision: $revision_id)"); + sms_sd_unlock($sms_csp, $sms_sd_info); + return SMS_OK; } sms_set_update_status($sms_csp, $sdid, SMS_OK, 'RESTORE', 'WORKING', "Backup of the restored configuration (restore revision: $revision_id)"); @@ -80,7 +213,7 @@ } catch (Exception | Error $e) { - netasq_disconnect(); + disconnect(); sms_set_update_status($sms_csp, $sdid, $e->getCode(), 'RESTORE', 'FAILED', "Restore failure (restore revision: $revision_id)"); sms_sd_unlock($sms_csp, $sms_sd_info); return SMS_OK;