Skip to content
Permalink
Browse files

Added group bypass to virus scanning

Also better-ish scan results handling again, I guess

Updated dependency knex: 0.20.0 -> 0.20.1
  • Loading branch information...
BobbyWibowo committed Nov 5, 2019
1 parent e10ce78 commit a28d862c141f100410450b27d05d2c2dc7d2376b
Showing with 180 additions and 179 deletions.
  1. +8 βˆ’1 config.sample.js
  2. +1 βˆ’0 controllers/permissionController.js
  3. +26 βˆ’33 controllers/uploadController.js
  4. +2 βˆ’1 controllers/utilsController.js
  5. +1 βˆ’1 package.json
  6. +142 βˆ’143 yarn.lock
@@ -277,13 +277,20 @@ module.exports = {

/*
Scan files using ClamAV through clamd.
https://github.com/NingLin-P/clamdjs#scannerscanfilepath-timeout-chunksize
groupBypass: Name of the lowest ranked group whose files will not be scanned.
Lowest ranked meanning that group AND any groups higher than it are included.
Example: 'moderator' = moderators, admins & superadmins.
More about groups at controllers/permissionController.js.
*/
scan: {
enabled: false,
ip: '127.0.0.1',
port: 3310,
timeout: 180 * 1000,
chunkSize: 64 * 1024
chunkSize: 64 * 1024,
groupBypass: 'admin'
},

/*
@@ -8,6 +8,7 @@ self.permissions = {
// Groups will inherit permissions from groups which have lower value
}

// returns true if user is in the group OR higher
self.is = (user, group) => {
// root bypass
if (user.username === 'root')
@@ -269,7 +269,7 @@ self.actuallyUploadFiles = async (req, res, user, albumid, age) => {
}

if (utils.clamd.scanner) {
const scanResult = await self.scanFiles(req, infoMap)
const scanResult = await self.scanFiles(req, user, infoMap)
if (scanResult) throw scanResult
}

@@ -349,7 +349,7 @@ self.actuallyUploadUrls = async (req, res, user, albumid, age) => {
downloaded.length = 0

if (utils.clamd.scanner) {
const scanResult = await self.scanFiles(req, infoMap)
const scanResult = await self.scanFiles(req, user, infoMap)
if (scanResult) throw scanResult
}

@@ -461,7 +461,7 @@ self.actuallyFinishChunks = async (req, res, user) => {
}))

if (utils.clamd.scanner) {
const scanResult = await self.scanFiles(req, infoMap)
const scanResult = await self.scanFiles(req, user, infoMap)
if (scanResult) throw scanResult
}

@@ -515,42 +515,35 @@ self.cleanUpChunks = async (uuid) => {
delete chunksData[uuid]
}

self.scanFiles = async (req, infoMap) => {
let foundThreat
let lastIteration
let errorString
// TODO: Should these be processed concurrently?
// Not sure if it'll be too much load on ClamAV.
for (let i = 0; i < infoMap.length; i++) {
let reply
try {
reply = await utils.clamd.scanner.scanFile(infoMap[i].path, utils.clamd.timeout, utils.clamd.chunkSize)
} catch (error) {
logger.error(`[ClamAV]: ${error.toString()}.`)
errorString = `[ClamAV]: ${error.code !== undefined ? `${error.code}, p` : 'P'}lease contact the site owner.`
break
}
self.scanFiles = async (req, user, infoMap) => {
if (user && utils.clamd.groupBypass && perms.is(user, utils.clamd.groupBypass))
return false

const foundThreats = []
const results = await Promise.all(infoMap.map(async info => {
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
foundThreat = reply.replace(/^stream: /, '').replace(/ FOUND\u0000$/, '')
logger.log(`[ClamAV]: ${infoMap[i].data.filename}: ${foundThreat} FOUND.`)
lastIteration = i === infoMap.length - 1
break
const foundThreat = reply.replace(/^stream: /, '').replace(/ FOUND\u0000$/, '')
logger.log(`[ClamAV]: ${info.data.filename}: ${foundThreat} FOUND.`)
foundThreats.push(foundThreat)
}
}

if (!foundThreat && !errorString)
return false
})).then(() => {
if (foundThreats.length)
return `Threat found: ${foundThreats[0]}${foundThreats.length > 1 ? ', and more' : ''}.`
}).catch(error => {
logger.error(`[ClamAV]: ${error.toString()}`)
return 'An unexpected error occurred with ClamAV, please contact the site owner.'
})

// Unlink all files when at least one threat is found
// Should ontinue even when encountering errors
await Promise.all(infoMap.map(info =>
utils.unlinkFile(info.data.filename).catch(logger.error)
))
if (results)
// Unlink all files when at least one threat is found OR any errors occurred
// Should continue even when encountering errors
await Promise.all(infoMap.map(info =>
utils.unlinkFile(info.data.filename).catch(logger.error)
))

return errorString ||
`Threat found: ${foundThreat}${lastIteration ? '' : ', and maybe more'}.`
return results
}

self.storeFilesToDb = async (req, res, user, infoMap) => {
@@ -15,7 +15,8 @@ const self = {
clamd: {
scanner: null,
timeout: config.uploads.scan.timeout || 5000,
chunkSize: config.uploads.scan.chunkSize || 64 * 1024
chunkSize: config.uploads.scan.chunkSize || 64 * 1024,
groupBypass: config.uploads.scan.groupBypass || null
},
gitHash: null,
idSet: null,
@@ -37,7 +37,7 @@
"fluent-ffmpeg": "^2.1.2",
"helmet": "^3.21.2",
"jszip": "^3.2.2",
"knex": "^0.20.0",
"knex": "^0.20.1",
"multer": "^1.4.2",
"node-fetch": "^2.6.0",
"nunjucks": "^3.2.0",

0 comments on commit a28d862

Please sign in to comment.
You can’t perform that action at this time.