diff --git a/js/settings.js b/js/settings.js
index 4c27b01d..de76fd9f 100644
--- a/js/settings.js
+++ b/js/settings.js
@@ -135,22 +135,27 @@ var antivirusSettings = antivirusSettings || {
function av_mode_show_options(str, mode = 'slow') {
- if ( str === 'daemon' || str === 'kaspersky' || str === 'icap'){
+ if ( str === 'daemon' || str === 'kaspersky' || str === 'icap' || str === 'symantec'){
$('tr.av_socket, tr.av_path').hide(mode);
$('tr.av_host, tr.av_port').show(mode);
} else if ( str === 'socket' ) {
$('tr.av_socket').show(mode);
$('tr.av_path, tr.av_host, tr.av_port').hide(mode);
} else if (str === 'executable'){
- $('tr.av_socket, tr.av_host, tr.av_port').hide(mode);
+ $('tr.av_socket, tr.av_host, tr.av_port, tr.av_password_action').hide(mode);
$('tr.av_path').show(mode);
}
- if (str === 'icap'){
+ if (str === 'icap' || str === 'symantec'){
$('tr.av_icap_service, tr.av_icap_header, tr.av_icap_preset, tr.av_icap_mode, tr.av_icap_tls').show(mode);
} else {
$('tr.av_icap_service, tr.av_icap_header, tr.av_icap_preset, tr.av_icap_mode, tr.av_icap_tls').hide(mode);
}
- if (str === 'kaspersky' || str === 'icap') {
+ if (str === 'symantec'){
+ $('tr.av_password_action').show(mode);
+ } else {
+ $('tr.av_password_action').hide(mode);
+ }
+ if (str === 'kaspersky' || str === 'icap' || str === 'symantec') {
$('#antivirus-advanced-wrapper').hide(mode);
} else {
$('#antivirus-advanced-wrapper').show(mode);
diff --git a/lib/AppConfig.php b/lib/AppConfig.php
index c99e5327..2704d81d 100644
--- a/lib/AppConfig.php
+++ b/lib/AppConfig.php
@@ -18,6 +18,7 @@
* @method ?string getAvCmdOptions()
* @method ?string getAvPath()
* @method ?string getAvInfectedAction()
+ * @method ?string getAvPasswordAction()
* @method ?string getAvStreamMaxLength()
* @method string getAvIcapMode()
* @method ?string getAvIcapRequestService()
@@ -34,6 +35,7 @@
* @method null setAvChunkSize(int $chunkSize)
* @method null setAvPath(string $avPath)
* @method null setAvInfectedAction(string $avInfectedAction)
+ * @method null setAvPasswordAction(string $avPasswordAction)
* @method null setAvIcapScanBackground(string $scanBackground)
* @method null setAvIcapMode(string $mode)
* @method null setAvIcapRequestService($reqService)
@@ -65,6 +67,7 @@ class AppConfig {
'av_icap_chunk_size' => '1048576',
'av_icap_connect_timeout' => '5',
'av_scan_first_bytes' => -1,
+ 'av_password_action' => 'deny',
];
/**
diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php
index abc04a48..aeaf39e6 100644
--- a/lib/Controller/SettingsController.php
+++ b/lib/Controller/SettingsController.php
@@ -47,6 +47,7 @@ public function __construct($appName, IRequest $request, AppConfig $appconfig, I
* @param string $avCmdOptions - extra command line options
* @param string $avPath - path to antivirus executable (Executable mode)
* @param string $avInfectedAction - action performed on infected files
+ * @param string $avPasswordAction - action performed on password protected files
* @param $avStreamMaxLength - reopen socket after bytes
* @param int $avMaxFileSize - file size limit
* @param int $avScanFirstBytes - scan size limit
@@ -62,6 +63,7 @@ public function save(
$avCmdOptions,
$avPath,
$avInfectedAction,
+ $avPasswordAction,
$avStreamMaxLength,
$avMaxFileSize,
$avScanFirstBytes,
@@ -77,6 +79,7 @@ public function save(
$this->settings->setAvCmdOptions($avCmdOptions);
$this->settings->setAvPath($avPath);
$this->settings->setAvInfectedAction($avInfectedAction);
+ $this->settings->setAvPasswordAction($avPasswordAction);
$this->settings->setAvStreamMaxLength($avStreamMaxLength);
$this->settings->setAvMaxFileSize($avMaxFileSize);
$this->settings->setAvScanFirstBytes($avScanFirstBytes);
diff --git a/lib/ICAP/SymantecICAP.php b/lib/ICAP/SymantecICAP.php
new file mode 100644
index 00000000..7e94fbc9
--- /dev/null
+++ b/lib/ICAP/SymantecICAP.php
@@ -0,0 +1,164 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\Files_Antivirus\Scanner;
+
+use OCA\Files_Antivirus\AppConfig;
+use OCA\Files_Antivirus\ICAP\ICAPClient;
+use OCA\Files_Antivirus\ICAP\ICAPRequest;
+use OCA\Files_Antivirus\ICAP\ICAPTlsClient;
+use OCA\Files_Antivirus\Status;
+use OCA\Files_Antivirus\StatusFactory;
+use OCP\ICertificateManager;
+use Psr\Log\LoggerInterface;
+
+class SymantecICAP extends ScannerBase {
+ /** @var ICAPClient::MODE_REQ_MOD|ICAPClient::MODE_RESP_MOD */
+ private string $mode;
+ private ICAPClient $icapClient;
+ private ?ICAPRequest $icapRequest;
+ private string $service;
+ private string $virusHeader;
+ private int $chunkSize;
+ private bool $tls;
+ private string $passwordProtected;
+
+ public function __construct(
+ AppConfig $config,
+ LoggerInterface $logger,
+ StatusFactory $statusFactory,
+ ICertificateManager $certificateManager
+ ) {
+ parent::__construct($config, $logger, $statusFactory);
+
+ $avHost = $this->appConfig->getAvHost();
+ $avPort = $this->appConfig->getAvPort();
+ $this->service = $config->getAvIcapRequestService();
+ $this->virusHeader = $config->getAvIcapResponseHeader();
+ $this->chunkSize = (int)$config->getAvChunkSize();
+ $this->mode = $config->getAvIcapMode();
+ $this->tls = $config->getAvIcapTls();
+ $this->passwordProtected = $config->getAvPasswordAction();
+
+ if (!($avHost && $avPort)) {
+ throw new \RuntimeException('The ICAP port and host are not set up.');
+ }
+ if ($this->tls) {
+ $this->icapClient = new ICAPTlsClient($avHost, (int)$avPort, (int)$config->getAvIcapConnectTimeout(), $certificateManager);
+ } else {
+ $this->icapClient = new ICAPClient($avHost, (int)$avPort, (int)$config->getAvIcapConnectTimeout());
+ }
+ }
+
+ public function initScanner() {
+ parent::initScanner();
+ $this->writeHandle = fopen("php://temp", 'w+');
+ $path = '/' . trim($this->path, '/');
+ if (str_contains($path, '.ocTransferId') && str_ends_with($path, '.part')) {
+ [$path] = explode('.ocTransferId', $path, 2);
+ }
+ $remote = $this->request?->getRemoteAddress();
+ $encodedPath = implode("/", array_map("rawurlencode", explode("/", $path)));
+ if ($this->mode === ICAPClient::MODE_REQ_MOD) {
+ $this->icapRequest = $this->icapClient->reqmod($this->service, [
+ 'Allow' => 204,
+ "X-Client-IP" => $remote,
+ ], [
+ "PUT $encodedPath HTTP/1.0",
+ "Host: nextcloud"
+ ]);
+ } else {
+ $this->icapRequest = $this->icapClient->respmod($this->service, [
+ 'Allow' => 204,
+ "X-Client-IP" => $remote,
+ ], [
+ "GET $encodedPath HTTP/1.0",
+ "Host: nextcloud",
+ ], [
+ "HTTP/1.0 200 OK",
+ "Content-Length: 1", // a dummy, non-zero, content length seems to be enough
+ ]);
+ }
+ }
+
+ protected function writeChunk($chunk) {
+ if (ftell($this->writeHandle) > $this->chunkSize) {
+ $this->flushBuffer();
+ }
+ parent::writeChunk($chunk);
+ }
+
+ private function flushBuffer() {
+ rewind($this->writeHandle);
+ $data = stream_get_contents($this->writeHandle);
+ $this->icapRequest->write($data);
+ $this->writeHandle = fopen("php://temp", 'w+');
+ }
+
+ protected function scanBuffer() {
+ $this->flushBuffer();
+ $response = $this->icapRequest->finish();
+ $code = $response->getStatus()->getCode();
+ $unchecked_list = array("decode_error","max_archive_layers_exceeded");
+ $blocked_list = array("file_type_blocked", "file_extension_blocked");
+
+ $this->status->setNumericStatus(Status::SCANRESULT_CLEAN);
+ if ($code === 200 || $code === 204) {
+ // c-icap/clamav reports this header
+ $virus = $response->getIcapHeaders()[$this->virusHeader] ?? false;
+ if ($virus) {
+ $this->status->setNumericStatus(Status::SCANRESULT_INFECTED);
+ $this->status->setDetails($virus);
+ }
+
+ // kaspersky(pre 2020 product editions) and McAfee handling
+ $respHeader = $response->getResponseHeaders()['HTTP_STATUS'] ?? '';
+ if (\strpos($respHeader, '403 Forbidden') || \strpos($respHeader, '403 VirusFound')) {
+ $this->status->setNumericStatus(Status::SCANRESULT_INFECTED);
+ }
+ } elseif ($code === 202) {
+ $this->status->setNumericStatus(Status::SCANRESULT_UNCHECKED);
+ } elseif ($code === 500 && $response->getIcapHeaders()['X-Error-Code'] === 'password_protected') {
+ if ($this->passwordProtected === "accept") {
+ $this->status->setNumericStatus(Status::SCANRESULT_CLEAN);
+ } else {
+ $this->status->setNumericStatus(Status::SCANRESULT_INFECTED);
+ }
+ } elseif ($code === 500 && in_array($response->getIcapHeaders()['X-Error-Code'], $unchecked_list)) {
+ $this->status->setNumericStatus(Status::SCANRESULT_UNCHECKED);
+ } elseif ($code === 500 && in_array($response->getIcapHeaders()['X-Error-Code'], $blocked_list)) {
+ $this->status->setNumericStatus(Status::SCANRESULT_INFECTED);
+ } else {
+ throw new \RuntimeException('Invalid response from ICAP server');
+ }
+ }
+
+ protected function shutdownScanner() {
+ $this->scanBuffer();
+ }
+
+ public function setDebugCallback(callable $callback): void {
+ $this->icapClient->setDebugCallback($callback);
+ }
+}
\ No newline at end of file
diff --git a/lib/Scanner/ScannerFactory.php b/lib/Scanner/ScannerFactory.php
index b6ab653d..8b31cd88 100644
--- a/lib/Scanner/ScannerFactory.php
+++ b/lib/Scanner/ScannerFactory.php
@@ -47,6 +47,9 @@ public function getScanner(string $path) {
case 'icap':
$scannerClass = ICAP::class;
break;
+ case 'symantec':
+ $scannerClass = SymantecICAP::class;
+ break;
default:
throw new \InvalidArgumentException('Application is misconfigured. Please check the settings at the admin page. Invalid mode: ' . $avMode);
}
diff --git a/templates/settings.php b/templates/settings.php
index 7424d5e3..71ef7ac2 100644
--- a/templates/settings.php
+++ b/templates/settings.php
@@ -23,6 +23,7 @@
'socket' => $l->t('ClamAV Daemon (Socket)'),
'kaspersky' => $l->t('Kaspersky Daemon'),
'icap' => $l->t('ICAP server'),
+ 'symantec' => $l->t('Symantec Icap'),
], $_['avMode'])) ?>
|
@@ -121,6 +122,11 @@
|
|
+
+ |
+ |
+ |
+
diff --git a/test.patch b/test.patch
new file mode 100644
index 00000000..7db6ec2c
--- /dev/null
+++ b/test.patch
@@ -0,0 +1,130 @@
+diff --git a/js/settings.js b/js/settings.js
+index cdfe82a..e503e9a 100644
+--- a/js/settings.js
++++ b/js/settings.js
+@@ -130,22 +130,27 @@ var antivirusSettings = antivirusSettings || {
+
+
+ function av_mode_show_options(str, mode = 'slow') {
+- if ( str === 'daemon' || str === 'kaspersky' || str === 'icap'){
++ if ( str === 'daemon' || str === 'kaspersky' || str === 'icap' || str === 'symantec'){
+ $('tr.av_socket, tr.av_path').hide(mode);
+ $('tr.av_host, tr.av_port').show(mode);
+ } else if ( str === 'socket' ) {
+ $('tr.av_socket').show(mode);
+ $('tr.av_path, tr.av_host, tr.av_port').hide(mode);
+ } else if (str === 'executable'){
+- $('tr.av_socket, tr.av_host, tr.av_port').hide(mode);
++ $('tr.av_socket, tr.av_host, tr.av_port, tr.av_password_action').hide(mode);
+ $('tr.av_path').show(mode);
+ }
+- if (str === 'icap'){
++ if (str === 'icap' || str === 'symantec'){
+ $('tr.av_icap_service, tr.av_icap_header, tr.av_icap_preset, tr.av_icap_mode, tr.av_icap_tls').show(mode);
+ } else {
+ $('tr.av_icap_service, tr.av_icap_header, tr.av_icap_preset, tr.av_icap_mode, tr.av_icap_tls').hide(mode);
+ }
+- if (str === 'kaspersky' || str === 'icap') {
++ if (str === 'symantec'){
++ $('tr.av_password_action').show(mode);
++ } else {
++ $('tr.av_password_action').hide(mode);
++ }
++ if (str === 'kaspersky' || str === 'icap' || str === 'symantec') {
+ $('#antivirus-advanced-wrapper').hide(mode);
+ } else {
+ $('#antivirus-advanced-wrapper').show(mode);
+diff --git a/lib/AppConfig.php b/lib/AppConfig.php
+index 8c127c5..6bc12a0 100644
+--- a/lib/AppConfig.php
++++ b/lib/AppConfig.php
+@@ -19,6 +19,7 @@ use OCP\IConfig;
+ * @method ?string getAvCmdOptions()
+ * @method ?string getAvPath()
+ * @method ?string getAvInfectedAction()
++ * @method ?string getAvPasswordAction()
+ * @method ?string getAvStreamMaxLength()
+ * @method string getAvIcapMode()
+ * @method ?string getAvIcapRequestService()
+@@ -35,6 +36,7 @@ use OCP\IConfig;
+ * @method null setAvChunkSize(int $chunkSize)
+ * @method null setAvPath(string $avPath)
+ * @method null setAvInfectedAction(string $avInfectedAction)
++ * @method null setAvPasswordAction(string $avPasswordAction)
+ * @method null setAvIcapScanBackground(string $scanBackground)
+ * @method null setAvIcapMode(string $mode)
+ * @method null setAvIcapRequestService($reqService)
+@@ -66,6 +68,7 @@ class AppConfig {
+ 'av_icap_chunk_size' => '1048576',
+ 'av_icap_connect_timeout' => '5',
+ 'av_scan_first_bytes' => -1,
++ 'av_password_action' => 'deny',
+ ];
+
+ /**
+diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php
+index c44c1e9..1c08e49 100644
+--- a/lib/Controller/SettingsController.php
++++ b/lib/Controller/SettingsController.php
+@@ -48,6 +48,7 @@ class SettingsController extends Controller {
+ * @param string $avCmdOptions - extra command line options
+ * @param string $avPath - path to antivirus executable (Executable mode)
+ * @param string $avInfectedAction - action performed on infected files
++ * @param string $avPasswordAction - action performed on password protected files
+ * @param $avStreamMaxLength - reopen socket after bytes
+ * @param int $avMaxFileSize - file size limit
+ * @param int $avScanFirstBytes - scan size limit
+@@ -63,6 +64,7 @@ class SettingsController extends Controller {
+ $avCmdOptions,
+ $avPath,
+ $avInfectedAction,
++ $avPasswordAction,
+ $avStreamMaxLength,
+ $avMaxFileSize,
+ $avScanFirstBytes,
+@@ -78,6 +80,7 @@ class SettingsController extends Controller {
+ $this->settings->setAvCmdOptions($avCmdOptions);
+ $this->settings->setAvPath($avPath);
+ $this->settings->setAvInfectedAction($avInfectedAction);
++ $this->settings->setAvPasswordAction($avPasswordAction);
+ $this->settings->setAvStreamMaxLength($avStreamMaxLength);
+ $this->settings->setAvMaxFileSize($avMaxFileSize);
+ $this->settings->setAvScanFirstBytes($avScanFirstBytes);
+diff --git a/lib/Scanner/ScannerFactory.php b/lib/Scanner/ScannerFactory.php
+index f682765..145c1af 100644
+--- a/lib/Scanner/ScannerFactory.php
++++ b/lib/Scanner/ScannerFactory.php
+@@ -41,6 +41,9 @@ class ScannerFactory {
+ case 'icap':
+ $scannerClass = ICAP::class;
+ break;
++ case 'symantec':
++ $scannerClass = SymantecICAP::class;
++ break;
+ default:
+ throw new \InvalidArgumentException('Application is misconfigured. Please check the settings at the admin page. Invalid mode: ' . $avMode);
+ }
+diff --git a/templates/settings.php b/templates/settings.php
+index b55e0bd..9802f41 100644
+--- a/templates/settings.php
++++ b/templates/settings.php
+@@ -16,6 +16,7 @@ script('files_antivirus', 'settings');
+ 'socket' => $l->t('ClamAV Daemon (Socket)'),
+ 'kaspersky' => $l->t('Kaspersky Daemon'),
+ 'icap' => $l->t('ICAP server'),
++ 'symantec' => $l->t('Symantec Icap'),
+ ], $_['avMode'])) ?>
+
+ |
+@@ -114,6 +115,11 @@ script('files_antivirus', 'settings');
+ |
+ |
+
++
++ |
++ |
++ |
++
+
+
+
\ No newline at end of file