From 88f852584cb35538521007d8492293f231b51c48 Mon Sep 17 00:00:00 2001 From: Bobby Wibowo Date: Sun, 1 Nov 2020 06:35:56 +0700 Subject: [PATCH] !! REPLACED ClamAV BACKEND: clamdjs -> clamscan !! Update your config file! --- README.md | 8 ++++---- config.sample.js | 30 ++++++++++++++++++++++-------- controllers/uploadController.js | 29 ++++++++++++++--------------- controllers/utilsController.js | 7 +++---- lolisafe.js | 16 +++++++--------- package.json | 2 +- yarn.lock | 8 ++++---- 7 files changed, 55 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index a19c13f..a6a495e 100644 --- a/README.md +++ b/README.md @@ -112,12 +112,12 @@ You will also need to use this script to overwrite existing thumbnails if you wa ## 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. -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. diff --git a/config.sample.js b/config.sample.js index 4046c7b..de1a13f 100644 --- a/config.sample.js +++ b/config.sample.js @@ -348,13 +348,7 @@ module.exports = { Example: 'moderator' = moderators, admins & superadmins. */ scan: { - enabled: false, - - ip: '127.0.0.1', - port: 3310, - timeout: 180 * 1000, - chunkSize: 64 * 1024, - + enabled: true, groupBypass: 'admin', // Other group names in controllers/permissionController.js whitelistExtensions: null, /* [ '.webp', @@ -372,7 +366,27 @@ module.exports = { '.mov', '.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' + } }, /* diff --git a/controllers/uploadController.js b/controllers/uploadController.js index 9001461..1c5c8e0 100644 --- a/controllers/uploadController.js +++ b/controllers/uploadController.js @@ -337,7 +337,7 @@ self.actuallyUploadFiles = async (req, res, user, albumid, age) => { throw 'Empty files are not allowed.' } - if (utils.clamd.scanner) { + if (utils.clamscan.instance) { const scanResult = await self.scanFiles(req, user, infoMap) 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 downloaded.length = 0 - if (utils.clamd.scanner) { + if (utils.clamscan.instance) { const scanResult = await self.scanFiles(req, user, infoMap) if (scanResult) throw scanResult } @@ -577,7 +577,7 @@ self.actuallyFinishChunks = async (req, res, user) => { infoMap.push({ path: destination, data }) })) - if (utils.clamd.scanner) { + if (utils.clamscan.instance) { const scanResult = await self.scanFiles(req, user, infoMap) if (scanResult) throw scanResult } @@ -620,31 +620,30 @@ self.cleanUpChunks = async (uuid, onTimeout) => { } self.scanFiles = async (req, user, infoMap) => { - if (user && utils.clamd.groupBypass && perms.is(user, utils.clamd.groupBypass)) { - // logger.log(`[ClamAV]: Skipping ${infoMap.length} file(s), ${utils.clamd.groupBypass} group bypass`) + if (user && utils.clamscan.groupBypass && perms.is(user, utils.clamscan.groupBypass)) { + // logger.log(`[ClamAV]: Skipping ${infoMap.length} file(s), ${utils.clamscan.groupBypass} group bypass`) return false } const foundThreats = [] 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`) } - if (utils.clamd.maxSize && info.data.size > utils.clamd.maxSize) { - return // logger.log(`[ClamAV]: Skipping ${info.data.filename}, size ${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.clamscan.maxSize}`) } - const reply = await utils.clamd.scanner.scanFile(info.path, utils.clamd.timeout, utils.clamd.chunkSize) - if (!reply.includes('OK') || reply.includes('FOUND')) { - // eslint-disable-next-line no-control-regex - const foundThreat = reply.replace(/^stream: /, '').replace(/ FOUND\u0000$/, '') - logger.log(`[ClamAV]: ${info.data.filename}: ${foundThreat} FOUND.`) - foundThreats.push(foundThreat) + const response = await utils.clamscan.instance.is_infected(info.path) + if (response.is_infected) { + logger.log(`[ClamAV]: ${info.data.filename}: ${response.viruses.join(', ')}`) + foundThreats.push(...response.viruses) } })).then(() => { 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 => { logger.error(`[ClamAV]: ${error.toString()}`) diff --git a/controllers/utilsController.js b/controllers/utilsController.js index f7bc141..71f51a7 100644 --- a/controllers/utilsController.js +++ b/controllers/utilsController.js @@ -12,10 +12,9 @@ const logger = require('./../logger') const db = require('knex')(config.database) const self = { - clamd: { - scanner: null, - timeout: config.uploads.scan.timeout || 5000, - chunkSize: config.uploads.scan.chunkSize || 64 * 1024, + clamscan: { + instance: null, + version: null, groupBypass: config.uploads.scan.groupBypass || null, whitelistExtensions: (Array.isArray(config.uploads.scan.whitelistExtensions) && config.uploads.scan.whitelistExtensions.length) diff --git a/lolisafe.js b/lolisafe.js index 09685a1..7f50921 100644 --- a/lolisafe.js +++ b/lolisafe.js @@ -1,5 +1,5 @@ const bodyParser = require('body-parser') -const clamd = require('clamdjs') +const ClamScan = require('clamscan') const contentDisposition = require('content-disposition') const express = require('express') const helmet = require('helmet') @@ -245,16 +245,14 @@ safe.use('/api', api) logger.log(`Git commit: ${utils.gitHash}`) } - // Clamd scanner + // ClamAV scanner if (config.uploads.scan && config.uploads.scan.enabled) { - const { ip, port } = config.uploads.scan - const version = await clamd.version(ip, port) - logger.log(`${ip}:${port} ${version}`) - - utils.clamd.scanner = clamd.createScanner(ip, port) - if (!utils.clamd.scanner) { - throw 'Could not create clamd scanner' + if (!config.uploads.scan.clamOptions) { + throw 'Missing object config.uploads.scan.clamOptions (check config.sample.js)' } + 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 diff --git a/package.json b/package.json index e839cca..931e3ee 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "bcrypt": "~5.0.0", "blake3": "~2.1.4", "body-parser": "~1.19.0", - "clamdjs": "~1.0.2", + "clamscan": "^1.3.3", "content-disposition": "~0.5.3", "express": "~4.17.1", "express-rate-limit": "~5.1.3", diff --git a/yarn.lock b/yarn.lock index 5ba039f..ea5b155 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1183,10 +1183,10 @@ ci-info@^2.0.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== -clamdjs@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/clamdjs/-/clamdjs-1.0.2.tgz#42b3c36370979e0c19efd4e3fbc7e0bf0006a8da" - integrity sha512-gVnX5ySMULvwYL2ykZQnP4UK4nIK7ftG6z015drJyOFgWpsqXt1Hcq4fMyPwM8LLsxfgfYKLiZi288xuTfmZBQ== +clamscan@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/clamscan/-/clamscan-1.3.3.tgz#0cf84dc3278cf7a3c81101d3e647842c34c1a825" + integrity sha512-AwaZeyECbqTWwrc5l7lLbA/cbYBTMW+VC77CnIjK3WCwTm1kGL6PHSXgDaEFsVkfrcRvPvSD1oxJpMzET9lqKg== class-utils@^0.3.5: version "0.3.6"