redis) { $settings = $this->getSetSettings(); $this->setupPubServer($settings); $this->redis = $this->createRedisConnection($settings); } } /** * Read the pid file, if it exists, check if the process is actually running * if either the pid file doesn't exists or the process is not running return false * otherwise return the pid. * * @param string|null $pidFilePath * @return bool|int False when process is not running, PID otherwise. * @throws Exception */ public function checkIfRunning($pidFilePath = null) { $pidFile = $pidFilePath ?: self::SCRIPTS_TMP . 'mispzmq.pid'; clearstatcache(false, $pidFile); if (!file_exists($pidFile)) { return false; } $pid = file_get_contents($pidFile); if ($pid === false || $pid === '') { return false; } if (!is_numeric($pid)) { throw new Exception('Internal error (invalid PID file for the MISP zmq script)'); } clearstatcache(false, "/proc/$pid"); $result = file_exists("/proc/$pid"); if ($result === false) { return false; } return $pid; } public function statusCheck() { $settings = $this->getSetSettings(); $redis = $this->createRedisConnection($settings); $redis->rPush( 'command', 'status'); $response = $redis->blPop('status', 5); if ($response === null) { throw new Exception("No response from status command returned after 5 seconds."); } return JsonTool::decode(trim($response[1])); } /** * @return bool * @throws ProcessException */ public function checkIfPythonLibInstalled() { $script = APP . 'files' . DS . 'scripts' . DS . 'mispzmq' . DS . 'mispzmqtest.py'; $result = ProcessTool::execute([ProcessTool::pythonBin(), $script]); if (trim($result) === "OK") { return true; } return false; } public function publishEvent($event) { App::uses('JSONConverterTool', 'Tools'); $json = JSONConverterTool::convert($event, false, true); return $this->pushToRedis('data:misp_json', $json); } public function event_save(array $event, $action) { if (!empty($action)) { $event['action'] = $action; } return $this->pushToRedis('data:misp_json_event', $event); } public function object_save(array $object, $action) { if (!empty($action)) { $object['action'] = $action; } return $this->pushToRedis('data:misp_json_object', $object); } public function object_reference_save(array $object_reference, $action) { if (!empty($action)) { $object_reference['action'] = $action; } return $this->pushToRedis('data:misp_json_object_reference', $object_reference); } public function publishConversation(array $message) { return $this->pushToRedis('data:misp_json_conversation', $message); } public function attribute_save(array $attribute, $action = false) { if (!empty($action)) { $attribute['action'] = $action; } return $this->pushToRedis('data:misp_json_attribute', $attribute); } public function tag_save(array $tag, $action = false) { if (!empty($action)) { $tag['action'] = $action; } return $this->pushToRedis('data:misp_json_tag', $tag); } public function sighting_save(array $sighting, $action = false) { if (!empty($action)) { $sighting['action'] = $action; } return $this->pushToRedis('data:misp_json_sighting', $sighting); } public function warninglist_save(array $warninglist, $action = false) { if (!empty($action)) { $warninglist['action'] = $action; } return $this->pushToRedis('data:misp_json_warninglist', $warninglist); } public function workflow_push(array $data) { $topic = 'data:misp_json_workflow'; return $this->pushToRedis($topic, $data); } /** * @param array $data * @param string $type * @param string|false $action * @return bool * @throws JsonException */ public function modified($data, $type, $action = false) { if (!empty($action)) { $data['action'] = $action; } return $this->pushToRedis('data:misp_json_' . $type, $data); } public function publish($data, $type, $action = false) { if (!empty($action)) { $data['action'] = $action; } return $this->pushToRedis('data:misp_json_' . $type, $data); } public function killService() { $settings = $this->getSetSettings(); if ($settings['supervisor_managed']) { throw new RuntimeException('ZeroMQ server is managed by supervisor, it is not possible to restart it.'); } if ($this->checkIfRunning()) { $redis = $this->createRedisConnection($settings); $redis->rPush('command', 'kill'); sleep(1); if ($this->checkIfRunning()) { // Still running. return false; } } return true; } /** * Reload the server if it is running, if not, start it. * * @return bool|string * @throws Exception */ public function reloadServer() { $settings = $this->getSetSettings(); $this->saveSettingToFile($settings); if ($this->checkIfRunning()) { $redis = $this->createRedisConnection($settings); $redis->rPush( 'command', 'reload'); } else { return 'Setting saved, but something is wrong with the ZeroMQ server. Please check the diagnostics page for more information.'; } return true; } public function restartServer() { $settings = $this->getSetSettings(); if ($settings['supervisor_managed']) { throw new RuntimeException('ZeroMQ server is managed by supervisor, it is not possible to restart it.'); } if (!$this->checkIfRunning()) { if (!$this->killService()) { return 'Could not kill the previous instance of the ZeroMQ script.'; } } $this->setupPubServer($settings); if ($this->checkIfRunning() === false) { return 'Failed starting the ZeroMQ script.'; } return true; } public function createConfigFile() { $settings = $this->getSetSettings(); $this->saveSettingToFile($settings); } /** * @param array $settings * @throws Exception */ private function setupPubServer(array $settings) { if ($settings['supervisor_managed']) { return; // server is managed by supervisor, we don't need to check if is running or start it when not } if ($this->checkIfRunning() === false) { if ($this->checkIfRunning(self::OLD_PID_LOCATION)) { // Old version is running, kill it and start again new one. $redis = $this->createRedisConnection($settings); $redis->rPush('command', 'kill'); sleep(1); } $this->saveSettingToFile($settings); shell_exec(ProcessTool::pythonBin() . ' ' . APP . 'files' . DS . 'scripts' . DS . 'mispzmq' . DS . 'mispzmq.py >> ' . APP . 'tmp' . DS . 'logs' . DS . 'mispzmq.log 2>> ' . APP . 'tmp' . DS . 'logs' . DS . 'mispzmq.error.log &'); } } /** * @param string $ns * @param string|array $data * @return bool * @throws JsonException * @throws RedisException */ private function pushToRedis($ns, $data) { $data = JsonTool::encode($data); $this->redis->rPush($ns, $data); return true; } /** * @param array $settings * @return Redis * @throws Exception */ private function createRedisConnection(array $settings) { if (!class_exists('Redis')) { throw new Exception("Class Redis doesn't exists. Please install redis extension for PHP."); } $redis = new Redis(); $redis->connect($settings['redis_host'], $settings['redis_port']); $redisPassword = $settings['redis_password']; if (!empty($redisPassword)) { $redis->auth($redisPassword); } $redis->select($settings['redis_database']); $redis->setOption(Redis::OPT_PREFIX, $settings['redis_namespace'] . ':'); return $redis; } /** * @param array $settings * @throws Exception */ private function saveSettingToFile(array $settings) { $settingFilePath = self::SCRIPTS_TMP . 'mispzmq_settings.json'; // Because setting file contains secrets, it should be readable just by owner. But because in Travis test, // config file is created under one user and then changed under other user, file must be readable and writable // also by group. FileAccessTool::createFile($settingFilePath, 0660); FileAccessTool::writeToFile($settingFilePath, JsonTool::encode($settings)); } /** * @return array */ private function getSetSettings() { $settings = [ 'redis_host' => 'localhost', 'redis_port' => 6379, 'redis_password' => '', 'redis_database' => 1, 'redis_namespace' => 'mispq', 'host' => '127.0.0.1', 'port' => '50000', 'username' => null, 'password' => null, 'supervisor_managed' => false, ]; $pluginConfig = Configure::read('Plugin'); foreach ($settings as $key => $setting) { $temp = $pluginConfig['ZeroMQ_' . $key] ?? null; if ($temp) { $settings[$key] = $temp; } } return $settings; } }