Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"psalm/phar": "^4.3",
"icewind/streams": "v0.7.5",
"sabre/dav": "^4.2.1",
"nextcloud/coding-standard": "^v1.1.1",
"nextcloud/coding-standard": "=v1.1.1",
"symfony/event-dispatcher": "4.4.30",
"psr/clock": "^1.0"
},
Expand Down
452 changes: 247 additions & 205 deletions composer.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions lib/Activity/Filter.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
Expand Down
1 change: 1 addition & 0 deletions lib/Activity/Provider.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
Expand Down
1 change: 1 addition & 0 deletions lib/Activity/Setting.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
Expand Down
7 changes: 5 additions & 2 deletions lib/AppConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* @method ?string getAvCmdOptions()
* @method ?string getAvPath()
* @method ?string getAvInfectedAction()
* @method ?string getAvBlockUnreachable()
* @method ?string getAvStreamMaxLength()
* @method string getAvIcapMode()
* @method ?string getAvIcapRequestService()
Expand All @@ -34,6 +35,7 @@
* @method null setAvChunkSize(int $chunkSize)
* @method null setAvPath(string $avPath)
* @method null setAvInfectedAction(string $avInfectedAction)
* @method null setAvBlockUnreachable(string $avBlockUnreachable)
* @method null setAvIcapScanBackground(string $scanBackground)
* @method null setAvIcapMode(string $mode)
* @method null setAvIcapRequestService($reqService)
Expand All @@ -57,6 +59,7 @@ class AppConfig {
'av_max_file_size' => -1,
'av_stream_max_length' => '26214400',
'av_infected_action' => 'only_log',
'av_block_unreachable' => 'yes',
'av_background_scan' => 'on',
'av_icap_mode' => ICAPClient::MODE_REQ_MOD,
'av_icap_tls' => false,
Expand Down Expand Up @@ -247,8 +250,8 @@ public function __call(string $methodName, array $args): ?string {
} elseif (strpos($methodName, 'get') === 0) {
return $this->getter($key);
} else {
throw new \BadFunctionCallException($methodName .
' does not exist');
throw new \BadFunctionCallException($methodName
. ' does not exist');
}
}
}
1 change: 1 addition & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ function (string $mountPoint, IStorage $storage) {
'trashEnabled' => $appManager->isEnabledForUser('files_trashbin'),
'mount_point' => $mountPoint,
'block_unscannable' => $appConfig->getAvBlockUnscannable(),
'block_unreachable' => $appConfig->getAvBlockUnreachable(),
]);
},
1
Expand Down
42 changes: 38 additions & 4 deletions lib/AvirWrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,16 @@ class AvirWrapper extends Wrapper {
* Modes that are used for writing
*/
private array $writingModes = ['r+', 'w', 'w+', 'a', 'a+', 'x', 'x+', 'c', 'c+'];
protected ScannerFactory$scannerFactory;
protected ScannerFactory $scannerFactory;
protected IL10N $l10n;
protected LoggerInterface$logger;
protected LoggerInterface $logger;
protected ActivityManager $activityManager;
protected bool $isHomeStorage;
private bool $shouldScan = true;
private bool $trashEnabled;
private ?string $mountPoint;
private bool $blockUnscannable = false;
private string $blockUnReachable = 'yes';

/**
* @param array $parameters
Expand All @@ -47,6 +48,7 @@ public function __construct($parameters) {
$this->trashEnabled = $parameters['trashEnabled'];
$this->mountPoint = $parameters['mount_point'];
$this->blockUnscannable = $parameters['block_unscannable'];
$this->blockUnReachable = $parameters['block_unreachable'];

/** @var IEventDispatcher $eventDispatcher */
$eventDispatcher = $parameters['eventDispatcher'];
Expand Down Expand Up @@ -116,7 +118,11 @@ function () use ($scanner, $path) {
);
} catch (\Exception $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
if ($this->blockUnReachable == 'yes') {
$this->handleConnectionError($path);
}
}

return $stream;
}

Expand Down Expand Up @@ -192,8 +198,8 @@ private function handleInfected(string $path, Status $status): void {
->setType(Provider::TYPE_VIRUS_DETECTED);
$this->activityManager->publish($activity);

$this->logger->error('Infected file deleted. ' . $status->getDetails() .
' File: ' . $path . ' Account: ' . $owner, ['app' => 'files_antivirus']);
$this->logger->error('Infected file deleted. ' . $status->getDetails()
. ' File: ' . $path . ' Account: ' . $owner, ['app' => 'files_antivirus']);

throw new InvalidContentException(
$this->l10n->t(
Expand All @@ -202,4 +208,32 @@ private function handleInfected(string $path, Status $status): void {
)
);
}

/**
* @throws InvalidContentException
*/
protected function handleConnectionError(string $path): void {
//prevent from going to trashbin
if ($this->trashEnabled) {
/** @var ITrashManager $trashManager */
$trashManager = \OC::$server->query(ITrashManager::class);
$trashManager->pauseTrash();
}

$this->unlink($path);

if ($this->trashEnabled) {
/** @var ITrashManager $trashManager */
$trashManager = \OC::$server->query(ITrashManager::class);
$trashManager->resumeTrash();
}

throw new InvalidContentException(
$this->l10n->t(
'%s. Upload cannot be completed.',
['No connection to anti virus']
)
);
}

}
10 changes: 5 additions & 5 deletions lib/Command/Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ class Test extends Base {
private Crypto $crypto;

// This is the EICAR test file, encrypted using the password 'eicar' to prevent any AV scanner from picking up this file
public const EICAR_ENCRYPTED = 'f413c7d6bb75cb67d474a36f27e776b7b51a68b2a26746465b659c7cd' .
'f13d8dea5d5932bc1afe1e34aa28ce75127d6bd6918bbad07503d16257a843fb46ed3dff04b12' .
'34d9b112aa556d396dc3afa0c4|cfaa1a828814db5ceb96fd8ab8f2c9e9|0b97b04d59a91ca64' .
'73117bcec8672b64a8abf6e6dec8ae70dcc0c05d7639d3dc8329afae8480197fb6f5b366f2c89' .
'629a01502a56f72c3bcb7eff3aeb1a6426|3';
public const EICAR_ENCRYPTED = 'f413c7d6bb75cb67d474a36f27e776b7b51a68b2a26746465b659c7cd'
. 'f13d8dea5d5932bc1afe1e34aa28ce75127d6bd6918bbad07503d16257a843fb46ed3dff04b12'
. '34d9b112aa556d396dc3afa0c4|cfaa1a828814db5ceb96fd8ab8f2c9e9|0b97b04d59a91ca64'
. '73117bcec8672b64a8abf6e6dec8ae70dcc0c05d7639d3dc8329afae8480197fb6f5b366f2c89'
. '629a01502a56f72c3bcb7eff3aeb1a6426|3';

public function __construct(ScannerFactory $scannerFactory, Crypto $crypto) {
parent::__construct();
Expand Down
7 changes: 5 additions & 2 deletions lib/Controller/SettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 $avBlockUnreachable - block upload if scanner not reachable
* @param $avStreamMaxLength - reopen socket after bytes
* @param int $avMaxFileSize - file size limit
* @param int $avScanFirstBytes - scan size limit
Expand All @@ -63,6 +64,7 @@ public function save(
$avCmdOptions,
$avPath,
$avInfectedAction,
$avBlockUnreachable,
$avStreamMaxLength,
$avMaxFileSize,
$avScanFirstBytes,
Expand All @@ -87,6 +89,7 @@ public function save(
$this->settings->setAvIcapResponseHeader($avIcapResponseHeader);
$this->settings->setAvIcapTls((bool)$avIcapTls);
$this->settings->setAvBlockUnscannable((bool)$avBlockUnscannable);
$this->settings->setAvBlockUnreachable($avBlockUnreachable);

try {
$scanner = $this->scannerFactory->getScanner('/self-test.txt');
Expand All @@ -99,8 +102,8 @@ public function save(
}

return new JSONResponse(
['data' =>
['message' => $message],
['data'
=> ['message' => $message],
'status' => $success ? 'success' : 'error',
'settings' => $this->settings->getAllValues(),
]
Expand Down
1 change: 1 addition & 0 deletions lib/Event/ScanStateEvent.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
Expand Down
2 changes: 1 addition & 1 deletion lib/ICAP/ICAPClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ protected function connect() {
$errorMessage,
$this->connectTimeout
);

if (!$stream) {
throw new RuntimeException(
"Cannot connect to \"tcp://{$this->host}:{$this->port}\": $errorMessage (code $errorCode)"
Expand Down
2 changes: 1 addition & 1 deletion lib/Item.php
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ public function logDebug($message): void {
public function logNotice($message): void {
$this->logger->notice($message . $this->generateExtraInfo(), ['app' => 'files_antivirus']);
}

/**
* @param string $message
*/
Expand Down
1 change: 1 addition & 0 deletions lib/Migration/CleanupCronTask.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
Expand Down
1 change: 1 addition & 0 deletions lib/Migration/Install.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
Expand Down
1 change: 1 addition & 0 deletions lib/Migration/Version10400Date20180929132835.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
Expand Down
1 change: 1 addition & 0 deletions lib/Sabre/PropfindPlugin.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
Expand Down
6 changes: 5 additions & 1 deletion lib/Scanner/ExternalClam.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ public function initScanner() {

if ($this->useSocket) {
$avSocket = $this->appConfig->getAvSocket();
$this->writeHandle = stream_socket_client('unix://' . $avSocket, $errno, $errstr, 5);
if (str_starts_with($avSocket, 'tcp')) {
$this->writeHandle = stream_socket_client($avSocket, $errno, $errstr, 5);
} else {
$this->writeHandle = stream_socket_client('unix://' . $avSocket, $errno, $errstr, 5);
}
if (!$this->getWriteHandle()) {
throw new \RuntimeException('Cannot connect to "' . $avSocket . '": ' . $errstr . ' (code ' . $errno . ')');
}
Expand Down
69 changes: 42 additions & 27 deletions lib/Scanner/ICAP.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ class ICAP extends ScannerBase {
private int $chunkSize;
private bool $tls;

private ICertificateManager $certificateManager;
private int $avIcapConnectionTimeout;

public function __construct(
AppConfig $config,
LoggerInterface $logger,
Expand All @@ -36,52 +39,64 @@ public function __construct(
) {
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->getAvIcapChunkSize();
$this->mode = $config->getAvIcapMode();
$this->tls = $config->getAvIcapTls();
$this->certificateManager = $certificateManager;
$this->avIcapConnectionTimeout = (int)$config->getAvIcapConnectTimeout();

}

public function initScanner() {
parent::initScanner();
$this->writeHandle = fopen('php://temp', 'w+');
if ($this->writeHandle === false) {
throw new \RuntimeException('Failed to open temporary write handle.');
}

$avHost = $this->appConfig->getAvHost();
$avPort = $this->appConfig->getAvPort();
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);
$this->icapClient = new ICAPTlsClient($avHost, (int)$avPort, $this->avIcapConnectionTimeout, $this->certificateManager);
} else {
$this->icapClient = new ICAPClient($avHost, (int)$avPort, (int)$config->getAvIcapConnectTimeout());
$this->icapClient = new ICAPClient($avHost, (int)$avPort, $this->avIcapConnectionTimeout);
}
}

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 ? $this->request->getRemoteAddress() : null;
$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
]);

try {
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
]);
}
} catch (\Throwable $e) {
throw new \RuntimeException('Failed to initialize ICAP request: ' . $e->getMessage(), 0, $e);
}
}

Expand Down
Loading
Loading