!! REPLACED ClamAV BACKEND: clamdjs -> clamscan !!

Update your config file!
This commit is contained in:
Bobby Wibowo 2020-11-01 06:35:56 +07:00
parent 1902b1c668
commit 88f852584c
No known key found for this signature in database
GPG Key ID: 51C3A1E1E22D26CF
7 changed files with 55 additions and 45 deletions

View File

@ -112,12 +112,12 @@ You will also need to use this script to overwrite existing thumbnails if you wa
## ClamAV support ## ClamAV support
This fork has an optional virus scanning support using [ClamAV](https://www.clamav.net/), through [clamdjs](https://github.com/NingLin-P/clamdjs) library. This fork has an optional virus scanning support using [ClamAV](https://www.clamav.net/), utilizing [clamscan](https://github.com/kylefarris/clamscan) library (Linux and OS X only).
It will scan new files right after they are uploaded. It will then alert the uploaders of the virus names in ClamAV's database if their files are dirty. It will scan new files right after they are uploaded. It will then alert the uploaders of the virus names in ClamAV's database if their files are dirty.
Unfortunately, this will slow down uploads processing as it has to wait for scan results before responding the uploaders, however it's still highly recommended for public usage (or at least if you find Google Safe Search too annoying). Unfortunately, this will slow down uploads processing as it has to wait for the scans before responding the uploaders. However, it's still highly recommended for public usage, or if you're like me who find the constant buzzing from Google Safe Search too annoying.
To enable this, make sure you have ClamAV daemon running, then fill in the daemon's IP and port into your config file. To enable this, make sure you have [ClamAV installed](https://github.com/kylefarris/clamscan#to-use-local-binary-method-of-scanning), or additionally have [ClamAV daemon running](https://github.com/kylefarris/clamscan#to-use-clamav-using-tcp-sockets). Afterwards configure `uploads.scan` options, and more importantly its sub-option `clamOptions`. Read more about it in the `config.sample.js` file.
From the config file you can also choose to exclude certain extensions from being scanned to lessen the burden on your server. Additionally, you can also configure usergroups bypass, extensions whitelist, and max file size, to lessen the burden on your server.

View File

@ -348,13 +348,7 @@ module.exports = {
Example: 'moderator' = moderators, admins & superadmins. Example: 'moderator' = moderators, admins & superadmins.
*/ */
scan: { scan: {
enabled: false, enabled: true,
ip: '127.0.0.1',
port: 3310,
timeout: 180 * 1000,
chunkSize: 64 * 1024,
groupBypass: 'admin', // Other group names in controllers/permissionController.js groupBypass: 'admin', // Other group names in controllers/permissionController.js
whitelistExtensions: null, /* [ whitelistExtensions: null, /* [
'.webp', '.webp',
@ -372,7 +366,27 @@ module.exports = {
'.mov', '.mov',
'.mkv' '.mkv'
], */ ], */
maxSize: null // '25MB' // Needs to be in MB // Make sure maxSize is no bigger than the max size you configured for your ClamAV
maxSize: null, // Needs to be in MB
// https://github.com/kylefarris/clamscan/tree/v1.3.3#getting-started
// Breaking options (do not use): remove_infected, quarantine_infected
// Untested options (may work): scan_log, debug_mode, file_list, scan_recursively
// Supported options: clamscan, clamdscan, preference
clamOptions: {
// clamscan: {},
clamdscan: {
// When both socket and host+port are specified, it will only use socket
socket: '/var/run/clamav/clamd.ctl',
host: '127.0.0.1',
port: 3310,
timeout: 1 * 60 * 1000, // 1 minute
multiscan: true,
reload_db: false,
active: true
},
preference: 'clamdscan'
}
}, },
/* /*

View File

@ -337,7 +337,7 @@ self.actuallyUploadFiles = async (req, res, user, albumid, age) => {
throw 'Empty files are not allowed.' throw 'Empty files are not allowed.'
} }
if (utils.clamd.scanner) { if (utils.clamscan.instance) {
const scanResult = await self.scanFiles(req, user, infoMap) const scanResult = await self.scanFiles(req, user, infoMap)
if (scanResult) throw scanResult if (scanResult) throw scanResult
} }
@ -444,7 +444,7 @@ self.actuallyUploadUrls = async (req, res, user, albumid, age) => {
// If no errors encountered, clear cache of downloaded files // If no errors encountered, clear cache of downloaded files
downloaded.length = 0 downloaded.length = 0
if (utils.clamd.scanner) { if (utils.clamscan.instance) {
const scanResult = await self.scanFiles(req, user, infoMap) const scanResult = await self.scanFiles(req, user, infoMap)
if (scanResult) throw scanResult if (scanResult) throw scanResult
} }
@ -577,7 +577,7 @@ self.actuallyFinishChunks = async (req, res, user) => {
infoMap.push({ path: destination, data }) infoMap.push({ path: destination, data })
})) }))
if (utils.clamd.scanner) { if (utils.clamscan.instance) {
const scanResult = await self.scanFiles(req, user, infoMap) const scanResult = await self.scanFiles(req, user, infoMap)
if (scanResult) throw scanResult if (scanResult) throw scanResult
} }
@ -620,31 +620,30 @@ self.cleanUpChunks = async (uuid, onTimeout) => {
} }
self.scanFiles = async (req, user, infoMap) => { self.scanFiles = async (req, user, infoMap) => {
if (user && utils.clamd.groupBypass && perms.is(user, utils.clamd.groupBypass)) { if (user && utils.clamscan.groupBypass && perms.is(user, utils.clamscan.groupBypass)) {
// logger.log(`[ClamAV]: Skipping ${infoMap.length} file(s), ${utils.clamd.groupBypass} group bypass`) // logger.log(`[ClamAV]: Skipping ${infoMap.length} file(s), ${utils.clamscan.groupBypass} group bypass`)
return false return false
} }
const foundThreats = [] const foundThreats = []
const results = await Promise.all(infoMap.map(async info => { const results = await Promise.all(infoMap.map(async info => {
if (utils.clamd.whitelistExtensions && utils.clamd.whitelistExtensions.includes(info.data.extname)) { if (utils.clamscan.whitelistExtensions && utils.clamscan.whitelistExtensions.includes(info.data.extname)) {
return // logger.log(`[ClamAV]: Skipping ${info.data.filename}, extension whitelisted`) return // logger.log(`[ClamAV]: Skipping ${info.data.filename}, extension whitelisted`)
} }
if (utils.clamd.maxSize && info.data.size > utils.clamd.maxSize) { if (utils.clamscan.maxSize && info.data.size > utils.clamscan.maxSize) {
return // logger.log(`[ClamAV]: Skipping ${info.data.filename}, size ${info.data.size} > ${utils.clamd.maxSize}`) return // logger.log(`[ClamAV]: Skipping ${info.data.filename}, size ${info.data.size} > ${utils.clamscan.maxSize}`)
} }
const reply = await utils.clamd.scanner.scanFile(info.path, utils.clamd.timeout, utils.clamd.chunkSize) const response = await utils.clamscan.instance.is_infected(info.path)
if (!reply.includes('OK') || reply.includes('FOUND')) { if (response.is_infected) {
// eslint-disable-next-line no-control-regex logger.log(`[ClamAV]: ${info.data.filename}: ${response.viruses.join(', ')}`)
const foundThreat = reply.replace(/^stream: /, '').replace(/ FOUND\u0000$/, '') foundThreats.push(...response.viruses)
logger.log(`[ClamAV]: ${info.data.filename}: ${foundThreat} FOUND.`)
foundThreats.push(foundThreat)
} }
})).then(() => { })).then(() => {
if (foundThreats.length) { if (foundThreats.length) {
return `Threat found: ${foundThreats[0]}${foundThreats.length > 1 ? ', and more' : ''}.` const more = foundThreats.length > 1
return `Threat${more ? 's' : ''} detected: ${foundThreats[0]}${more ? ', and more' : ''}.`
} }
}).catch(error => { }).catch(error => {
logger.error(`[ClamAV]: ${error.toString()}`) logger.error(`[ClamAV]: ${error.toString()}`)

View File

@ -12,10 +12,9 @@ const logger = require('./../logger')
const db = require('knex')(config.database) const db = require('knex')(config.database)
const self = { const self = {
clamd: { clamscan: {
scanner: null, instance: null,
timeout: config.uploads.scan.timeout || 5000, version: null,
chunkSize: config.uploads.scan.chunkSize || 64 * 1024,
groupBypass: config.uploads.scan.groupBypass || null, groupBypass: config.uploads.scan.groupBypass || null,
whitelistExtensions: (Array.isArray(config.uploads.scan.whitelistExtensions) && whitelistExtensions: (Array.isArray(config.uploads.scan.whitelistExtensions) &&
config.uploads.scan.whitelistExtensions.length) config.uploads.scan.whitelistExtensions.length)

View File

@ -1,5 +1,5 @@
const bodyParser = require('body-parser') const bodyParser = require('body-parser')
const clamd = require('clamdjs') const ClamScan = require('clamscan')
const contentDisposition = require('content-disposition') const contentDisposition = require('content-disposition')
const express = require('express') const express = require('express')
const helmet = require('helmet') const helmet = require('helmet')
@ -245,16 +245,14 @@ safe.use('/api', api)
logger.log(`Git commit: ${utils.gitHash}`) logger.log(`Git commit: ${utils.gitHash}`)
} }
// Clamd scanner // ClamAV scanner
if (config.uploads.scan && config.uploads.scan.enabled) { if (config.uploads.scan && config.uploads.scan.enabled) {
const { ip, port } = config.uploads.scan if (!config.uploads.scan.clamOptions) {
const version = await clamd.version(ip, port) throw 'Missing object config.uploads.scan.clamOptions (check config.sample.js)'
logger.log(`${ip}:${port} ${version}`)
utils.clamd.scanner = clamd.createScanner(ip, port)
if (!utils.clamd.scanner) {
throw 'Could not create clamd scanner'
} }
utils.clamscan.instance = await new ClamScan().init(config.uploads.scan.clamOptions)
utils.clamscan.version = await utils.clamscan.instance.get_version().then(s => s.trim())
logger.log(`Connection established with ${utils.clamscan.version}`)
} }
// Cache file identifiers // Cache file identifiers

View File

@ -35,7 +35,7 @@
"bcrypt": "~5.0.0", "bcrypt": "~5.0.0",
"blake3": "~2.1.4", "blake3": "~2.1.4",
"body-parser": "~1.19.0", "body-parser": "~1.19.0",
"clamdjs": "~1.0.2", "clamscan": "^1.3.3",
"content-disposition": "~0.5.3", "content-disposition": "~0.5.3",
"express": "~4.17.1", "express": "~4.17.1",
"express-rate-limit": "~5.1.3", "express-rate-limit": "~5.1.3",

View File

@ -1183,10 +1183,10 @@ ci-info@^2.0.0:
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==
clamdjs@~1.0.2: clamscan@^1.3.3:
version "1.0.2" version "1.3.3"
resolved "https://registry.yarnpkg.com/clamdjs/-/clamdjs-1.0.2.tgz#42b3c36370979e0c19efd4e3fbc7e0bf0006a8da" resolved "https://registry.yarnpkg.com/clamscan/-/clamscan-1.3.3.tgz#0cf84dc3278cf7a3c81101d3e647842c34c1a825"
integrity sha512-gVnX5ySMULvwYL2ykZQnP4UK4nIK7ftG6z015drJyOFgWpsqXt1Hcq4fMyPwM8LLsxfgfYKLiZi288xuTfmZBQ== integrity sha512-AwaZeyECbqTWwrc5l7lLbA/cbYBTMW+VC77CnIjK3WCwTm1kGL6PHSXgDaEFsVkfrcRvPvSD1oxJpMzET9lqKg==
class-utils@^0.3.5: class-utils@^0.3.5:
version "0.3.6" version "0.3.6"