From da6cf26fce1826d53cb9cb63caceec4f937f6824 Mon Sep 17 00:00:00 2001 From: Paul Silvestri Date: Wed, 25 Dec 2024 13:46:07 -0500 Subject: [PATCH 1/6] updated how we bulid flags --- examples/scanWithClamScan.js | 73 +++++++ index.js | 407 +++++++++++++++++++++++++++-------- 2 files changed, 389 insertions(+), 91 deletions(-) create mode 100644 examples/scanWithClamScan.js diff --git a/examples/scanWithClamScan.js b/examples/scanWithClamScan.js new file mode 100644 index 0000000..d18587f --- /dev/null +++ b/examples/scanWithClamScan.js @@ -0,0 +1,73 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +const axios = require('axios'); +const fs = require('fs'); + +const fakeVirusUrl = 'https://www.eicar.org/download/eicar-com-2/?wpdmdl=8842&refresh=661ef9d576b211713306069'; +const tempDir = __dirname; +const scanFile = `${tempDir}/tmp_file.txt`; + + +// Initialize the clamscan module +const NodeClam = require('../index'); // Offically: require('clamscan'); +const { type } = require('os'); + +(async () => { + const clamscan = await new NodeClam().init({ + debugMode: true, + clamdscan: { + bypassTest: true, + host: 'localhost', + port: 3310, + // socket: '/var/run/clamd.scan/clamd.sock', + }, + clamscan: { + path: '/usr/local/bin/clamscan', + scanPdf: false + }, + preference: 'clamscan' + }); + + let body; + + // Request a test file from the internet... + try { + body = await axios.get(fakeVirusUrl, { + responseType: 'arraybuffer', // Ensure the file is returned as binary data + }); + //console.log(JSON.stringify(body.data)) + } catch (err) { + if (err.response) console.error(`${err.response.status}: Request Failed. `, err.response.data); + else if (err.request) console.error('Error with Request: ', err.request); + else console.error('Error: ', err.message); + process.exit(1); + } + + // Write the file to the filesystem + fs.writeFileSync(scanFile, body.data); + + // Scan the file + try { + const { file, isInfected, viruses } = await clamscan.scanWithClamScan(scanFile); + + // If `isInfected` is TRUE, file is a virus! + if (isInfected === true) { + console.log( + `You've downloaded a virus (${viruses.join( + '' + )})! Don't worry, it's only a test one and is not malicious...` + ); + } else if (isInfected === null) { + console.log("Something didn't work right..."); + } else if (isInfected === false) { + console.log(`The file (${file}) you downloaded was just fine... Carry on...`); + } + + // Remove the file (for good measure) + //if (fs.existsSync(scanFile)) fs.unlinkSync(scanFile); + process.exit(0); + } catch (err) { + console.error(`ERROR: ${err}`); + console.trace(err.stack); + process.exit(1); + } +})(); diff --git a/index.js b/index.js index d5afd4b..5c3f4bd 100755 --- a/index.js +++ b/index.js @@ -402,6 +402,7 @@ class NodeClam { * this._buildClamArgs('--version'); */ _buildClamArgs(item) { + let args = this.clamFlags.slice(); if (typeof item === 'string') args.push(item); @@ -423,18 +424,18 @@ class NodeClam { */ _buildClamFlags(scanner, settings) { const flagsArray = ['--no-summary']; - + // Flags specific to clamscan if (scanner === 'clamscan') { flagsArray.push('--stdout'); - + // Remove infected files if (settings.removeInfected === true) { flagsArray.push('--remove=yes'); } else { flagsArray.push('--remove=no'); } - + // Database file if ( 'clamscan' in settings && @@ -444,29 +445,188 @@ class NodeClam { typeof settings.clamscan.db === 'string' ) flagsArray.push(`--database=${settings.clamscan.db}`); - + // Scan archives if (settings.clamscan.scanArchives === true) { flagsArray.push('--scan-archive=yes'); } else { flagsArray.push('--scan-archive=no'); } - - // Recursive scanning (flag is specific, feature is not) + + // Recursive scanning if (settings.scanRecursively === true) { flagsArray.push('-r'); } else { flagsArray.push('--recursive=no'); } + + // Scan other options: these flags can be added based on settings + if (settings.clamscan.officialDbOnly !== undefined) { + flagsArray.push(`--official-db-only=${settings.clamscan.officialDbOnly ? 'yes' : 'no'}`); + } + + if (settings.clamscan.failIfCvdOlderThan) { + flagsArray.push(`--fail-if-cvd-older-than=${settings.clamscan.failIfCvdOlderThan}`); + } + + if (settings.clamscan.allmatch !== undefined) { + flagsArray.push(`--allmatch=${settings.clamscan.allmatch ? 'yes' : 'no'}`); + } + + if (settings.clamscan.crossFs !== undefined) { + flagsArray.push(`--cross-fs=${settings.clamscan.crossFs ? 'yes' : 'no'}`); + } + + if (settings.clamscan.followDirSymlinks !== undefined) { + flagsArray.push(`--follow-dir-symlinks=${settings.clamscan.followDirSymlinks}`); + } + + if (settings.clamscan.followFileSymlinks !== undefined) { + flagsArray.push(`--follow-file-symlinks=${settings.clamscan.followFileSymlinks}`); + } + + if (settings.clamscan.excludeRegex) { + flagsArray.push(`--exclude=${settings.clamscan.excludeRegex}`); + } + + if (settings.clamscan.excludeDirRegex) { + flagsArray.push(`--exclude-dir=${settings.clamscan.excludeDirRegex}`); + } + + if (settings.clamscan.includeRegex) { + flagsArray.push(`--include=${settings.clamscan.includeRegex}`); + } + + if (settings.clamscan.includeDirRegex) { + flagsArray.push(`--include-dir=${settings.clamscan.includeDirRegex}`); + } + + if (settings.clamscan.bytecode !== undefined) { + flagsArray.push(`--bytecode=${settings.clamscan.bytecode ? 'yes' : 'no'}`); + } + + if (settings.clamscan.bytecodeUnsigned !== undefined) { + flagsArray.push(`--bytecode-unsigned=${settings.clamscan.bytecodeUnsigned ? 'yes' : 'no'}`); + } + + if (settings.clamscan.bytecodeTimeout) { + flagsArray.push(`--bytecode-timeout=${settings.clamscan.bytecodeTimeout}`); + } + + if (settings.clamscan.statistics) { + flagsArray.push(`--statistics=${settings.clamscan.statistics}`); + } + + if (settings.clamscan.detectPua !== undefined) { + flagsArray.push(`--detect-pua=${settings.clamscan.detectPua ? 'yes' : 'no'}`); + } + + if (settings.clamscan.excludePua) { + flagsArray.push(`--exclude-pua=${settings.clamscan.excludePua}`); + } + + if (settings.clamscan.includePua) { + flagsArray.push(`--include-pua=${settings.clamscan.includePua}`); + } + + if (settings.clamscan.detectStructured !== undefined) { + flagsArray.push(`--detect-structured=${settings.clamscan.detectStructured ? 'yes' : 'no'}`); + } + + if (settings.clamscan.structuredSsnFormat) { + flagsArray.push(`--structured-ssn-format=${settings.clamscan.structuredSsnFormat}`); + } + + if (settings.clamscan.structuredSsnCount) { + flagsArray.push(`--structured-ssn-count=${settings.clamscan.structuredSsnCount}`); + } + + if (settings.clamscan.structuredCcCount) { + flagsArray.push(`--structured-cc-count=${settings.clamscan.structuredCcCount}`); + } + + if (settings.clamscan.structuredCcMode) { + flagsArray.push(`--structured-cc-mode=${settings.clamscan.structuredCcMode}`); + } + + if (settings.clamscan.scanMail !== undefined) { + flagsArray.push(`--scan-mail=${settings.clamscan.scanMail ? 'yes' : 'no'}`); + } + + if (settings.clamscan.phishingSigs !== undefined) { + flagsArray.push(`--phishing-sigs=${settings.clamscan.phishingSigs ? 'yes' : 'no'}`); + } + + if (settings.clamscan.phishingScanUrls !== undefined) { + flagsArray.push(`--phishing-scan-urls=${settings.clamscan.phishingScanUrls ? 'yes' : 'no'}`); + } + + if (settings.clamscan.heuristicAlerts !== undefined) { + flagsArray.push(`--heuristic-alerts=${settings.clamscan.heuristicAlerts ? 'yes' : 'no'}`); + } + + if (settings.clamscan.heuristicScanPrecedence !== undefined) { + flagsArray.push(`--heuristic-scan-precedence=${settings.clamscan.heuristicScanPrecedence ? 'yes' : 'no'}`); + } + + if (settings.clamscan.normalize !== undefined) { + flagsArray.push(`--normalize=${settings.clamscan.normalize ? 'yes' : 'no'}`); + } + + if (settings.clamscan.scanPe !== undefined) { + flagsArray.push(`--scan-pe=${settings.clamscan.scanPe ? 'yes' : 'no'}`); + } + + if (settings.clamscan.scanElf !== undefined) { + flagsArray.push(`--scan-elf=${settings.clamscan.scanElf ? 'yes' : 'no'}`); + } + + if (settings.clamscan.scanOle2 !== undefined) { + flagsArray.push(`--scan-ole2=${settings.clamscan.scanOle2 ? 'yes' : 'no'}`); + } + + if (settings.clamscan.scanPdf !== undefined) { + flagsArray.push(`--scan-pdf=${settings.clamscan.scanPdf ? 'yes' : 'no'}`); + } + + if (settings.clamscan.scanSwf !== undefined) { + flagsArray.push(`--scan-swf=${settings.clamscan.scanSwf ? 'yes' : 'no'}`); + } + + if (settings.clamscan.scanHtml !== undefined) { + flagsArray.push(`--scan-html=${settings.clamscan.scanHtml ? 'yes' : 'no'}`); + } + + if (settings.clamscan.scanXmldocs !== undefined) { + flagsArray.push(`--scan-xmldocs=${settings.clamscan.scanXmldocs ? 'yes' : 'no'}`); + } + + if (settings.clamscan.scanHwp3 !== undefined) { + flagsArray.push(`--scan-hwp3=${settings.clamscan.scanHwp3 ? 'yes' : 'no'}`); + } + + if (settings.clamscan.scanOnenote !== undefined) { + flagsArray.push(`--scan-onenote=${settings.clamscan.scanOnenote ? 'yes' : 'no'}`); + } + + if (settings.clamscan.alertBroken !== undefined) { + flagsArray.push(`--alert-broken=${settings.clamscan.alertBroken ? 'yes' : 'no'}`); + } + + if (settings.clamscan.alertEncrypted !== undefined) { + flagsArray.push(`--alert-encrypted=${settings.clamscan.alertEncrypted ? 'yes' : 'no'}`); + } + + // Add any other flags you want to handle in a similar way } - + // Flags specific to clamdscan else if (scanner === 'clamdscan') { flagsArray.push('--fdpass'); - + // Remove infected files if (settings.removeInfected === true) flagsArray.push('--remove'); - + // Specify a config file if ( 'clamdscan' in settings && @@ -476,18 +636,18 @@ class NodeClam { typeof settings.clamdscan.configFile === 'string' ) flagsArray.push(`--config-file=${settings.clamdscan.configFile}`); - + // Turn on multi-threaded scanning if (settings.clamdscan.multiscan === true) flagsArray.push('--multiscan'); - + // Reload the virus DB if (settings.clamdscan.reloadDb === true) flagsArray.push('--reload'); } - + // *************** // Common flags // *************** - + // Remove infected files if (settings.removeInfected !== true) { if ( @@ -498,15 +658,15 @@ class NodeClam { flagsArray.push(`--move=${settings.quarantineInfected}`); } } - + // Write info to a log if ('scanLog' in settings && settings.scanLog && typeof settings.scanLog === 'string') flagsArray.push(`--log=${settings.scanLog}`); - + // Read list of files to scan from a file if ('fileList' in settings && settings.fileList && typeof settings.fileList === 'string') flagsArray.push(`--file-list=${settings.fileList}`); - + // Build the String return flagsArray; } @@ -1016,96 +1176,96 @@ class NodeClam { return hasCb ? cb(err, file, null) : reject(err); } - // If user wants to scan via socket or TCP... - if (this.settings.clamdscan.socket || this.settings.clamdscan.port || this.settings.clamdscan.host) { - // Scan using local unix domain socket (much simpler/faster process--especially with MULTISCAN enabled) - if (this.settings.clamdscan.socket) { - let client; + // If user wants to scan via socket or TCP... + if (this.settings.clamdscan.socket || this.settings.clamdscan.port || this.settings.clamdscan.host) { + // Scan using local unix domain socket (much simpler/faster process--especially with MULTISCAN enabled) + if (this.settings.clamdscan.socket) { + let client; - try { - client = await this._initSocket('isInfected'); - if (this.settings.debugMode) - console.log(`${this.debugLabel}: scanning with local domain socket now.`); + try { + client = await this._initSocket('isInfected'); + if (this.settings.debugMode) + console.log(`${this.debugLabel}: scanning with local domain socket now.`); - if (this.settings.clamdscan.multiscan === true) { - // Use Multiple threads (faster) - client.write(`MULTISCAN ${file}`); - } else { - // Use single or default # of threads (potentially slower) - client.write(`SCAN ${file}`); - } + if (this.settings.clamdscan.multiscan === true) { + // Use Multiple threads (faster) + client.write(`MULTISCAN ${file}`); + } else { + // Use single or default # of threads (potentially slower) + client.write(`SCAN ${file}`); + } + + client.on('data', async (data) => { + if (this.settings.debugMode) + console.log( + `${this.debugLabel}: Received response from remote clamd service: `, + data.toString() + ); + try { + const result = this._processResult(data.toString(), file); + if (result instanceof Error) { + client.end(); + // Throw the error so that its caught and fallback is attempted + throw result; + } - client.on('data', async (data) => { - if (this.settings.debugMode) - console.log( - `${this.debugLabel}: Received response from remote clamd service: `, - data.toString() - ); - try { - const result = this._processResult(data.toString(), file); - if (result instanceof Error) { client.end(); - // Throw the error so that its caught and fallback is attempted - throw result; - } + const { isInfected, viruses } = result; + return hasCb + ? cb(null, file, isInfected, viruses) + : resolve({ file, isInfected, viruses }); + } catch (err) { + client.end(); - client.end(); - const { isInfected, viruses } = result; - return hasCb - ? cb(null, file, isInfected, viruses) - : resolve({ file, isInfected, viruses }); - } catch (err) { - client.end(); + // Fallback to local if that's an option + if (this.settings.clamdscan.localFallback === true) return localScan(); - // Fallback to local if that's an option - if (this.settings.clamdscan.localFallback === true) return localScan(); + return hasCb ? cb(err, file, null, []) : reject(err); + } + }); + } catch (err) { + if (client && 'readyState' in client && client.readyState) client.end(); - return hasCb ? cb(err, file, null, []) : reject(err); - } - }); - } catch (err) { - if (client && 'readyState' in client && client.readyState) client.end(); + // Fallback to local if that's an option + if (this.settings.clamdscan.localFallback === true) return localScan(); - // Fallback to local if that's an option - if (this.settings.clamdscan.localFallback === true) return localScan(); + return hasCb ? cb(err, file, null, []) : reject(err); + } + } - return hasCb ? cb(err, file, null, []) : reject(err); + // Scan using remote host/port and TCP protocol (must stream the file) + else { + // Convert file to stream + const stream = fs.createReadStream(file); + + // Attempt to scan the stream. + try { + const isInfected = await this.scanStream(stream); + return hasCb ? cb(null, file, isInfected, []) : resolve({ ...isInfected, file }); + } catch (e) { + // Fallback to local if that's an option + if (this.settings.clamdscan.localFallback === true) return await localScan(); + + // Otherwise, fail + const err = new NodeClamError({ err: e, file }, 'Could not scan file via TCP or locally!'); + return hasCb ? cb(err, file, null, []) : reject(err); + } finally { + // Kill file stream on response + stream.destroy(); + } } } - - // Scan using remote host/port and TCP protocol (must stream the file) + + // If the user just wants to scan locally... else { - // Convert file to stream - const stream = fs.createReadStream(file); - - // Attempt to scan the stream. try { - const isInfected = await this.scanStream(stream); - return hasCb ? cb(null, file, isInfected, []) : resolve({ ...isInfected, file }); - } catch (e) { - // Fallback to local if that's an option - if (this.settings.clamdscan.localFallback === true) return await localScan(); - - // Otherwise, fail - const err = new NodeClamError({ err: e, file }, 'Could not scan file via TCP or locally!'); - return hasCb ? cb(err, file, null, []) : reject(err); - } finally { - // Kill file stream on response - stream.destroy(); + return await localScan(); + } catch (err) { + return hasCb ? cb(err, file, null) : reject(err); } } - } - - // If the user just wants to scan locally... - else { - try { - return await localScan(); - } catch (err) { - return hasCb ? cb(err, file, null) : reject(err); - } - } - }); - } + }); + } /** * Returns a PassthroughStream object which allows you to @@ -2370,6 +2530,71 @@ class NodeClam { } }); } + + /** + * @description Used to call clamscan CLI command directly + * @param {string} pathToBinary is the path to the CLI command you would like to use + */ + scanWithClamScan(file, hasCb) { + + if (!this.settings.clamscan.path) { + throw new NodeClamError("No path provided for 'clamscan' binary"); + } + + if (this.settings.debugMode) console.log(`${this.debugLabel}: [Local Scan] Scanning ${file}`); + + // Build the actual command to run + let args = this._buildClamFlags('clamscan', this.settings); + args.push(file) + + + if (this.settings.debugMode) + console.log(`${this.debugLabel}: Configured clam command: ${this.settings.clamscan.path}`, args.join(' ')); + + + // Return a promise if no callback is passed + return new Promise((resolve, reject) => { + execFile(this.settings.clamscan.path, args, (err, stdout, stderr) => { + const { isInfected, viruses } = this._processResult(stdout, file); + + if (err) { + // Code 1 is when a virus is found... It's not really an "error", per se... + if (err.code === 1) { + return hasCb ? hasCb(null, file, true, viruses) : resolve({ file, isInfected, viruses }); + } + const error = new NodeClamError( + { file, err, isInfected: null }, + `There was an error scanning the file (ClamAV Error Code: ${err.code})` + ); + if (this.settings.debugMode) console.log(`${this.debugLabel}`, error); + return hasCb ? hasCb(error, file, null, []) : reject(error); + } + + // Not sure in what scenario a `stderr` would show up, but, it's worth handling here + if (stderr) { + const error = new NodeClamError( + { stderr, file }, + 'The file was scanned but ClamAV responded with an unexpected response.' + ); + if (this.settings.debugMode) console.log(`${this.debugLabel}: `, error); + return hasCb ? hasCb(error, file, null, viruses) : resolve({ file, isInfected, viruses }); + } + + // No viruses were found! + try { + return hasCb ? hasCb(null, file, isInfected, viruses) : resolve({ file, isInfected, viruses }); + } catch (e) { + const error = new NodeClamError( + { file, err: e, isInfected: null }, + 'There was an error processing the results from ClamAV' + ); + return hasCb ? hasCb(error, file, null, []) : reject(error); + } + }); + }); + } } + + module.exports = NodeClam; From 5518960c0b0b383c9a32d0e5f814d8c3071c5961 Mon Sep 17 00:00:00 2001 From: Paul Silvestri Date: Wed, 25 Dec 2024 14:03:42 -0500 Subject: [PATCH 2/6] updated flags --- examples/scanWithClamScan.js | 3 +- index.js | 98 +++++++++++++++++++++++++++++++++--- 2 files changed, 92 insertions(+), 9 deletions(-) diff --git a/examples/scanWithClamScan.js b/examples/scanWithClamScan.js index d18587f..cf356c3 100644 --- a/examples/scanWithClamScan.js +++ b/examples/scanWithClamScan.js @@ -22,7 +22,8 @@ const { type } = require('os'); }, clamscan: { path: '/usr/local/bin/clamscan', - scanPdf: false + scanPdf: false, + maxScanTime:120000 }, preference: 'clamscan' }); diff --git a/index.js b/index.js index 5c3f4bd..79674d2 100755 --- a/index.js +++ b/index.js @@ -443,8 +443,7 @@ class NodeClam { 'db' in settings.clamscan && settings.clamscan.db && typeof settings.clamscan.db === 'string' - ) - flagsArray.push(`--database=${settings.clamscan.db}`); + ) flagsArray.push(`--database=${settings.clamscan.db}`); // Scan archives if (settings.clamscan.scanArchives === true) { @@ -617,7 +616,78 @@ class NodeClam { flagsArray.push(`--alert-encrypted=${settings.clamscan.alertEncrypted ? 'yes' : 'no'}`); } - // Add any other flags you want to handle in a similar way + // Add the missing flags for max options + if (settings.clamscan.maxScanTime) { + flagsArray.push(`--max-scantime=${settings.clamscan.maxScanTime}`); + } + + if (settings.clamscan.maxFileSize) { + flagsArray.push(`--max-filesize=${settings.clamscan.maxFileSize}`); + } + + if (settings.clamscan.maxScanSize) { + flagsArray.push(`--max-scansize=${settings.clamscan.maxScanSize}`); + } + + if (settings.clamscan.maxFiles) { + flagsArray.push(`--max-files=${settings.clamscan.maxFiles}`); + } + + if (settings.clamscan.maxRecursion) { + flagsArray.push(`--max-recursion=${settings.clamscan.maxRecursion}`); + } + + if (settings.clamscan.maxDirRecursion) { + flagsArray.push(`--max-dir-recursion=${settings.clamscan.maxDirRecursion}`); + } + + if (settings.clamscan.maxEmbeddedPe) { + flagsArray.push(`--max-embeddedpe=${settings.clamscan.maxEmbeddedPe}`); + } + + if (settings.clamscan.maxHtmlnormalize) { + flagsArray.push(`--max-htmlnormalize=${settings.clamscan.maxHtmlnormalize}`); + } + + if (settings.clamscan.maxHtmlnotags) { + flagsArray.push(`--max-htmlnotags=${settings.clamscan.maxHtmlnotags}`); + } + + if (settings.clamscan.maxScriptnormalize) { + flagsArray.push(`--max-scriptnormalize=${settings.clamscan.maxScriptnormalize}`); + } + + if (settings.clamscan.maxZipTypercg) { + flagsArray.push(`--max-ziptypercg=${settings.clamscan.maxZipTypercg}`); + } + + if (settings.clamscan.maxPartitions) { + flagsArray.push(`--max-partitions=${settings.clamscan.maxPartitions}`); + } + + if (settings.clamscan.maxIconspe) { + flagsArray.push(`--max-iconspe=${settings.clamscan.maxIconspe}`); + } + + if (settings.clamscan.maxRechwp3) { + flagsArray.push(`--max-rechwp3=${settings.clamscan.maxRechwp3}`); + } + + if (settings.clamscan.pcreMatchLimit) { + flagsArray.push(`--pcre-match-limit=${settings.clamscan.pcreMatchLimit}`); + } + + if (settings.clamscan.pcreRecMatchLimit) { + flagsArray.push(`--pcre-recmatch-limit=${settings.clamscan.pcreRecMatchLimit}`); + } + + if (settings.clamscan.pcreMaxFilesize) { + flagsArray.push(`--pcre-max-filesize=${settings.clamscan.pcreMaxFilesize}`); + } + + if (settings.clamscan.disableCache !== undefined) { + flagsArray.push(`--disable-cache=${settings.clamscan.disableCache ? 'yes' : 'no'}`); + } } // Flags specific to clamdscan @@ -634,8 +704,7 @@ class NodeClam { 'configFile' in settings.clamdscan && settings.clamdscan.configFile && typeof settings.clamdscan.configFile === 'string' - ) - flagsArray.push(`--config-file=${settings.clamdscan.configFile}`); + ) flagsArray.push(`--config-file=${settings.clamdscan.configFile}`); // Turn on multi-threaded scanning if (settings.clamdscan.multiscan === true) flagsArray.push('--multiscan'); @@ -667,9 +736,20 @@ class NodeClam { if ('fileList' in settings && settings.fileList && typeof settings.fileList === 'string') flagsArray.push(`--file-list=${settings.fileList}`); - // Build the String + // Scan files specified via --file-list + if (settings.filesFromList) { + flagsArray.push('--file-from-list'); + } + + // Add timeout for scanning + if (settings.scanTimeout) { + flagsArray.push(`--timeout=${settings.scanTimeout}`); + } + + // Return the final flags return flagsArray; } + /** * Create socket connection to a remote(or local) clamav daemon. @@ -2532,8 +2612,10 @@ class NodeClam { } /** - * @description Used to call clamscan CLI command directly - * @param {string} pathToBinary is the path to the CLI command you would like to use + * @description Used to call clamscan CLI command directly. This is useful if you have files that need a + * specific configuration for certain files that differs from the base clamd.conf. You can specify CLI arguments + * like MaxScanSize or ScanPDF that are offered by the 'clamscan' command + * @param {string} file is the path to the file you would like to scan */ scanWithClamScan(file, hasCb) { From 6263dedf4196f976d01936a1d3f25abb1b0280b5 Mon Sep 17 00:00:00 2001 From: Paul Silvestri Date: Wed, 25 Dec 2024 20:29:20 -0500 Subject: [PATCH 3/6] updated example and removed unnecessary function --- examples/scanWithClamScan.js | 17 ++++++++-- index.js | 64 ------------------------------------ 2 files changed, 15 insertions(+), 66 deletions(-) diff --git a/examples/scanWithClamScan.js b/examples/scanWithClamScan.js index cf356c3..0d55d80 100644 --- a/examples/scanWithClamScan.js +++ b/examples/scanWithClamScan.js @@ -6,6 +6,17 @@ const fakeVirusUrl = 'https://www.eicar.org/download/eicar-com-2/?wpdmdl=8842&re const tempDir = __dirname; const scanFile = `${tempDir}/tmp_file.txt`; +/** + * This script demonstrates how to scan a file for viruses without requiring a permanent ClamAV daemon. + * It provides an example of configuring the 'clamscan' command for one-time file scans, including downloading + * a test virus file from the internet, scanning it with ClamAV, and handling the results. + * + * Configuration options for both 'clamscan' and 'clamdscan' are included to showcase different + * scanning setups, such as bypassing certain tests, defining timeouts, and setting file scan preferences. + * + * This example uses a test virus file from the EICAR website, which is safe and non-malicious, + * designed to verify that the virus scanning functionality works correctly. + */ // Initialize the clamscan module const NodeClam = require('../index'); // Offically: require('clamscan'); @@ -19,11 +30,13 @@ const { type } = require('os'); host: 'localhost', port: 3310, // socket: '/var/run/clamd.scan/clamd.sock', + active: false }, clamscan: { path: '/usr/local/bin/clamscan', scanPdf: false, - maxScanTime:120000 + maxScanTime: 120000, + active: true }, preference: 'clamscan' }); @@ -48,7 +61,7 @@ const { type } = require('os'); // Scan the file try { - const { file, isInfected, viruses } = await clamscan.scanWithClamScan(scanFile); + const { file, isInfected, viruses } = await clamscan.isInfected(scanFile); // If `isInfected` is TRUE, file is a virus! if (isInfected === true) { diff --git a/index.js b/index.js index 79674d2..36a5696 100755 --- a/index.js +++ b/index.js @@ -2611,70 +2611,6 @@ class NodeClam { }); } - /** - * @description Used to call clamscan CLI command directly. This is useful if you have files that need a - * specific configuration for certain files that differs from the base clamd.conf. You can specify CLI arguments - * like MaxScanSize or ScanPDF that are offered by the 'clamscan' command - * @param {string} file is the path to the file you would like to scan - */ - scanWithClamScan(file, hasCb) { - - if (!this.settings.clamscan.path) { - throw new NodeClamError("No path provided for 'clamscan' binary"); - } - - if (this.settings.debugMode) console.log(`${this.debugLabel}: [Local Scan] Scanning ${file}`); - - // Build the actual command to run - let args = this._buildClamFlags('clamscan', this.settings); - args.push(file) - - - if (this.settings.debugMode) - console.log(`${this.debugLabel}: Configured clam command: ${this.settings.clamscan.path}`, args.join(' ')); - - - // Return a promise if no callback is passed - return new Promise((resolve, reject) => { - execFile(this.settings.clamscan.path, args, (err, stdout, stderr) => { - const { isInfected, viruses } = this._processResult(stdout, file); - - if (err) { - // Code 1 is when a virus is found... It's not really an "error", per se... - if (err.code === 1) { - return hasCb ? hasCb(null, file, true, viruses) : resolve({ file, isInfected, viruses }); - } - const error = new NodeClamError( - { file, err, isInfected: null }, - `There was an error scanning the file (ClamAV Error Code: ${err.code})` - ); - if (this.settings.debugMode) console.log(`${this.debugLabel}`, error); - return hasCb ? hasCb(error, file, null, []) : reject(error); - } - - // Not sure in what scenario a `stderr` would show up, but, it's worth handling here - if (stderr) { - const error = new NodeClamError( - { stderr, file }, - 'The file was scanned but ClamAV responded with an unexpected response.' - ); - if (this.settings.debugMode) console.log(`${this.debugLabel}: `, error); - return hasCb ? hasCb(error, file, null, viruses) : resolve({ file, isInfected, viruses }); - } - - // No viruses were found! - try { - return hasCb ? hasCb(null, file, isInfected, viruses) : resolve({ file, isInfected, viruses }); - } catch (e) { - const error = new NodeClamError( - { file, err: e, isInfected: null }, - 'There was an error processing the results from ClamAV' - ); - return hasCb ? hasCb(error, file, null, []) : reject(error); - } - }); - }); - } } From 0cbd11362da67697c3a3d241ac9358b737b6bbf3 Mon Sep 17 00:00:00 2001 From: Paul Silvestri Date: Wed, 25 Dec 2024 20:43:21 -0500 Subject: [PATCH 4/6] updated basic async await example --- examples/basic_async_await.js | 56 +++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/examples/basic_async_await.js b/examples/basic_async_await.js index df49e4b..87c1fc6 100755 --- a/examples/basic_async_await.js +++ b/examples/basic_async_await.js @@ -2,40 +2,64 @@ const axios = require('axios'); const fs = require('fs'); -const fakeVirusUrl = 'https://secure.eicar.org/eicar_com.txt'; +//switch between these two for happy/sad path testing +const fakeVirusUrl = 'https://www.eicar.org/download/eicar-com-2/?wpdmdl=8842&refresh=661ef9d576b211713306069'; +const samplePDFUrl = 'https://pdfobject.com/pdf/sample.pdf'; const tempDir = __dirname; const scanFile = `${tempDir}/tmp_file.txt`; -const config = { - removeInfected: true, - debugMode: false, - scanRecursively: false, - clamdscan: { - path: '/usr/bin/clamdscan', - // config_file: '/etc/clamd.d/daemon.conf' - }, - preference: 'clamdscan', -}; +/** + * This script demonstrates how to scan a file for viruses without requiring a permanent ClamAV daemon. + * It provides an example of configuring the 'clamscan' command for one-time file scans, including downloading + * a test virus file from the internet, scanning it with ClamAV, and handling the results. + * + * Configuration options for both 'clamscan' and 'clamdscan' are included to showcase different + * scanning setups, such as bypassing certain tests, defining timeouts, and setting file scan preferences. + * + * This example uses a test virus file from the EICAR website, which is safe and non-malicious, + * designed to verify that the virus scanning functionality works correctly. + */ // Initialize the clamscan module const NodeClam = require('../index'); // Offically: require('clamscan'); +const { type } = require('os'); (async () => { - const clamscan = await new NodeClam().init(config); + const clamscan = await new NodeClam().init({ + debugMode: true, + clamdscan: { + bypassTest: true, + host: 'localhost', + port: 3310, + // socket: '/var/run/clamd.scan/clamd.sock', + active: false + }, + clamscan: { + path: '/usr/local/bin/clamscan', + scanPdf: false, + maxScanTime: 120000, + active: true + }, + preference: 'clamdscan' + }); + let body; // Request a test file from the internet... try { - body = await axios.get(fakeVirusUrl); + body = await axios.get(fakeVirusUrl, { + responseType: 'arraybuffer', // Ensure the file is returned as binary data + }); + //console.log(JSON.stringify(body.data)) } catch (err) { - if (err.response) console.err(`${err.response.status}: Request Failed. `, err.response.data); + if (err.response) console.error(`${err.response.status}: Request Failed. `, err.response.data); else if (err.request) console.error('Error with Request: ', err.request); else console.error('Error: ', err.message); process.exit(1); } // Write the file to the filesystem - fs.writeFileSync(scanFile, body); + fs.writeFileSync(scanFile, body.data); // Scan the file try { @@ -55,7 +79,7 @@ const NodeClam = require('../index'); // Offically: require('clamscan'); } // Remove the file (for good measure) - if (fs.existsSync(scanFile)) fs.unlinkSync(scanFile); + //if (fs.existsSync(scanFile)) fs.unlinkSync(scanFile); process.exit(0); } catch (err) { console.error(`ERROR: ${err}`); From 1365d68ff90606490dafa7b92e90ff2661b1b6bb Mon Sep 17 00:00:00 2001 From: Paul Silvestri Date: Wed, 25 Dec 2024 20:45:15 -0500 Subject: [PATCH 5/6] updated scan with clamscan example --- examples/scanWithClamScan.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/scanWithClamScan.js b/examples/scanWithClamScan.js index 0d55d80..8b8a1c7 100644 --- a/examples/scanWithClamScan.js +++ b/examples/scanWithClamScan.js @@ -2,7 +2,9 @@ const axios = require('axios'); const fs = require('fs'); +//switch between these two for happy/sad path testing const fakeVirusUrl = 'https://www.eicar.org/download/eicar-com-2/?wpdmdl=8842&refresh=661ef9d576b211713306069'; +const samplePDFUrl = 'https://pdfobject.com/pdf/sample.pdf'; const tempDir = __dirname; const scanFile = `${tempDir}/tmp_file.txt`; From 4bfd3b0d6fd7ba7dee2d9077818f05c3fe6bcd95 Mon Sep 17 00:00:00 2001 From: Paul Silvestri Date: Wed, 25 Dec 2024 20:52:02 -0500 Subject: [PATCH 6/6] ran prettier --- examples/basic_async_await.js | 20 +-- examples/scanWithClamScan.js | 20 +-- index.js | 313 +++++++++++++++++----------------- 3 files changed, 176 insertions(+), 177 deletions(-) diff --git a/examples/basic_async_await.js b/examples/basic_async_await.js index 87c1fc6..07d2439 100755 --- a/examples/basic_async_await.js +++ b/examples/basic_async_await.js @@ -12,11 +12,11 @@ const scanFile = `${tempDir}/tmp_file.txt`; * This script demonstrates how to scan a file for viruses without requiring a permanent ClamAV daemon. * It provides an example of configuring the 'clamscan' command for one-time file scans, including downloading * a test virus file from the internet, scanning it with ClamAV, and handling the results. - * - * Configuration options for both 'clamscan' and 'clamdscan' are included to showcase different + * + * Configuration options for both 'clamscan' and 'clamdscan' are included to showcase different * scanning setups, such as bypassing certain tests, defining timeouts, and setting file scan preferences. - * - * This example uses a test virus file from the EICAR website, which is safe and non-malicious, + * + * This example uses a test virus file from the EICAR website, which is safe and non-malicious, * designed to verify that the virus scanning functionality works correctly. */ @@ -32,24 +32,24 @@ const { type } = require('os'); host: 'localhost', port: 3310, // socket: '/var/run/clamd.scan/clamd.sock', - active: false + active: false, }, clamscan: { path: '/usr/local/bin/clamscan', scanPdf: false, maxScanTime: 120000, - active: true + active: true, }, - preference: 'clamdscan' + preference: 'clamdscan', }); - + let body; // Request a test file from the internet... try { body = await axios.get(fakeVirusUrl, { - responseType: 'arraybuffer', // Ensure the file is returned as binary data - }); + responseType: 'arraybuffer', // Ensure the file is returned as binary data + }); //console.log(JSON.stringify(body.data)) } catch (err) { if (err.response) console.error(`${err.response.status}: Request Failed. `, err.response.data); diff --git a/examples/scanWithClamScan.js b/examples/scanWithClamScan.js index 8b8a1c7..803a236 100644 --- a/examples/scanWithClamScan.js +++ b/examples/scanWithClamScan.js @@ -12,11 +12,11 @@ const scanFile = `${tempDir}/tmp_file.txt`; * This script demonstrates how to scan a file for viruses without requiring a permanent ClamAV daemon. * It provides an example of configuring the 'clamscan' command for one-time file scans, including downloading * a test virus file from the internet, scanning it with ClamAV, and handling the results. - * - * Configuration options for both 'clamscan' and 'clamdscan' are included to showcase different + * + * Configuration options for both 'clamscan' and 'clamdscan' are included to showcase different * scanning setups, such as bypassing certain tests, defining timeouts, and setting file scan preferences. - * - * This example uses a test virus file from the EICAR website, which is safe and non-malicious, + * + * This example uses a test virus file from the EICAR website, which is safe and non-malicious, * designed to verify that the virus scanning functionality works correctly. */ @@ -32,24 +32,24 @@ const { type } = require('os'); host: 'localhost', port: 3310, // socket: '/var/run/clamd.scan/clamd.sock', - active: false + active: false, }, clamscan: { path: '/usr/local/bin/clamscan', scanPdf: false, maxScanTime: 120000, - active: true + active: true, }, - preference: 'clamscan' + preference: 'clamscan', }); - + let body; // Request a test file from the internet... try { body = await axios.get(fakeVirusUrl, { - responseType: 'arraybuffer', // Ensure the file is returned as binary data - }); + responseType: 'arraybuffer', // Ensure the file is returned as binary data + }); //console.log(JSON.stringify(body.data)) } catch (err) { if (err.response) console.error(`${err.response.status}: Request Failed. `, err.response.data); diff --git a/index.js b/index.js index 36a5696..f2f86d4 100755 --- a/index.js +++ b/index.js @@ -402,7 +402,6 @@ class NodeClam { * this._buildClamArgs('--version'); */ _buildClamArgs(item) { - let args = this.clamFlags.slice(); if (typeof item === 'string') args.push(item); @@ -424,18 +423,18 @@ class NodeClam { */ _buildClamFlags(scanner, settings) { const flagsArray = ['--no-summary']; - + // Flags specific to clamscan if (scanner === 'clamscan') { flagsArray.push('--stdout'); - + // Remove infected files if (settings.removeInfected === true) { flagsArray.push('--remove=yes'); } else { flagsArray.push('--remove=no'); } - + // Database file if ( 'clamscan' in settings && @@ -443,260 +442,263 @@ class NodeClam { 'db' in settings.clamscan && settings.clamscan.db && typeof settings.clamscan.db === 'string' - ) flagsArray.push(`--database=${settings.clamscan.db}`); - + ) + flagsArray.push(`--database=${settings.clamscan.db}`); + // Scan archives if (settings.clamscan.scanArchives === true) { flagsArray.push('--scan-archive=yes'); } else { flagsArray.push('--scan-archive=no'); } - + // Recursive scanning if (settings.scanRecursively === true) { flagsArray.push('-r'); } else { flagsArray.push('--recursive=no'); } - + // Scan other options: these flags can be added based on settings if (settings.clamscan.officialDbOnly !== undefined) { flagsArray.push(`--official-db-only=${settings.clamscan.officialDbOnly ? 'yes' : 'no'}`); } - + if (settings.clamscan.failIfCvdOlderThan) { flagsArray.push(`--fail-if-cvd-older-than=${settings.clamscan.failIfCvdOlderThan}`); } - + if (settings.clamscan.allmatch !== undefined) { flagsArray.push(`--allmatch=${settings.clamscan.allmatch ? 'yes' : 'no'}`); } - + if (settings.clamscan.crossFs !== undefined) { flagsArray.push(`--cross-fs=${settings.clamscan.crossFs ? 'yes' : 'no'}`); } - + if (settings.clamscan.followDirSymlinks !== undefined) { flagsArray.push(`--follow-dir-symlinks=${settings.clamscan.followDirSymlinks}`); } - + if (settings.clamscan.followFileSymlinks !== undefined) { flagsArray.push(`--follow-file-symlinks=${settings.clamscan.followFileSymlinks}`); } - + if (settings.clamscan.excludeRegex) { flagsArray.push(`--exclude=${settings.clamscan.excludeRegex}`); } - + if (settings.clamscan.excludeDirRegex) { flagsArray.push(`--exclude-dir=${settings.clamscan.excludeDirRegex}`); } - + if (settings.clamscan.includeRegex) { flagsArray.push(`--include=${settings.clamscan.includeRegex}`); } - + if (settings.clamscan.includeDirRegex) { flagsArray.push(`--include-dir=${settings.clamscan.includeDirRegex}`); } - + if (settings.clamscan.bytecode !== undefined) { flagsArray.push(`--bytecode=${settings.clamscan.bytecode ? 'yes' : 'no'}`); } - + if (settings.clamscan.bytecodeUnsigned !== undefined) { flagsArray.push(`--bytecode-unsigned=${settings.clamscan.bytecodeUnsigned ? 'yes' : 'no'}`); } - + if (settings.clamscan.bytecodeTimeout) { flagsArray.push(`--bytecode-timeout=${settings.clamscan.bytecodeTimeout}`); } - + if (settings.clamscan.statistics) { flagsArray.push(`--statistics=${settings.clamscan.statistics}`); } - + if (settings.clamscan.detectPua !== undefined) { flagsArray.push(`--detect-pua=${settings.clamscan.detectPua ? 'yes' : 'no'}`); } - + if (settings.clamscan.excludePua) { flagsArray.push(`--exclude-pua=${settings.clamscan.excludePua}`); } - + if (settings.clamscan.includePua) { flagsArray.push(`--include-pua=${settings.clamscan.includePua}`); } - + if (settings.clamscan.detectStructured !== undefined) { flagsArray.push(`--detect-structured=${settings.clamscan.detectStructured ? 'yes' : 'no'}`); } - + if (settings.clamscan.structuredSsnFormat) { flagsArray.push(`--structured-ssn-format=${settings.clamscan.structuredSsnFormat}`); } - + if (settings.clamscan.structuredSsnCount) { flagsArray.push(`--structured-ssn-count=${settings.clamscan.structuredSsnCount}`); } - + if (settings.clamscan.structuredCcCount) { flagsArray.push(`--structured-cc-count=${settings.clamscan.structuredCcCount}`); } - + if (settings.clamscan.structuredCcMode) { flagsArray.push(`--structured-cc-mode=${settings.clamscan.structuredCcMode}`); } - + if (settings.clamscan.scanMail !== undefined) { flagsArray.push(`--scan-mail=${settings.clamscan.scanMail ? 'yes' : 'no'}`); } - + if (settings.clamscan.phishingSigs !== undefined) { flagsArray.push(`--phishing-sigs=${settings.clamscan.phishingSigs ? 'yes' : 'no'}`); } - + if (settings.clamscan.phishingScanUrls !== undefined) { flagsArray.push(`--phishing-scan-urls=${settings.clamscan.phishingScanUrls ? 'yes' : 'no'}`); } - + if (settings.clamscan.heuristicAlerts !== undefined) { flagsArray.push(`--heuristic-alerts=${settings.clamscan.heuristicAlerts ? 'yes' : 'no'}`); } - + if (settings.clamscan.heuristicScanPrecedence !== undefined) { - flagsArray.push(`--heuristic-scan-precedence=${settings.clamscan.heuristicScanPrecedence ? 'yes' : 'no'}`); + flagsArray.push( + `--heuristic-scan-precedence=${settings.clamscan.heuristicScanPrecedence ? 'yes' : 'no'}` + ); } - + if (settings.clamscan.normalize !== undefined) { flagsArray.push(`--normalize=${settings.clamscan.normalize ? 'yes' : 'no'}`); } - + if (settings.clamscan.scanPe !== undefined) { flagsArray.push(`--scan-pe=${settings.clamscan.scanPe ? 'yes' : 'no'}`); } - + if (settings.clamscan.scanElf !== undefined) { flagsArray.push(`--scan-elf=${settings.clamscan.scanElf ? 'yes' : 'no'}`); } - + if (settings.clamscan.scanOle2 !== undefined) { flagsArray.push(`--scan-ole2=${settings.clamscan.scanOle2 ? 'yes' : 'no'}`); } - + if (settings.clamscan.scanPdf !== undefined) { flagsArray.push(`--scan-pdf=${settings.clamscan.scanPdf ? 'yes' : 'no'}`); } - + if (settings.clamscan.scanSwf !== undefined) { flagsArray.push(`--scan-swf=${settings.clamscan.scanSwf ? 'yes' : 'no'}`); } - + if (settings.clamscan.scanHtml !== undefined) { flagsArray.push(`--scan-html=${settings.clamscan.scanHtml ? 'yes' : 'no'}`); } - + if (settings.clamscan.scanXmldocs !== undefined) { flagsArray.push(`--scan-xmldocs=${settings.clamscan.scanXmldocs ? 'yes' : 'no'}`); } - + if (settings.clamscan.scanHwp3 !== undefined) { flagsArray.push(`--scan-hwp3=${settings.clamscan.scanHwp3 ? 'yes' : 'no'}`); } - + if (settings.clamscan.scanOnenote !== undefined) { flagsArray.push(`--scan-onenote=${settings.clamscan.scanOnenote ? 'yes' : 'no'}`); } - + if (settings.clamscan.alertBroken !== undefined) { flagsArray.push(`--alert-broken=${settings.clamscan.alertBroken ? 'yes' : 'no'}`); } - + if (settings.clamscan.alertEncrypted !== undefined) { flagsArray.push(`--alert-encrypted=${settings.clamscan.alertEncrypted ? 'yes' : 'no'}`); } - + // Add the missing flags for max options if (settings.clamscan.maxScanTime) { flagsArray.push(`--max-scantime=${settings.clamscan.maxScanTime}`); } - + if (settings.clamscan.maxFileSize) { flagsArray.push(`--max-filesize=${settings.clamscan.maxFileSize}`); } - + if (settings.clamscan.maxScanSize) { flagsArray.push(`--max-scansize=${settings.clamscan.maxScanSize}`); } - + if (settings.clamscan.maxFiles) { flagsArray.push(`--max-files=${settings.clamscan.maxFiles}`); } - + if (settings.clamscan.maxRecursion) { flagsArray.push(`--max-recursion=${settings.clamscan.maxRecursion}`); } - + if (settings.clamscan.maxDirRecursion) { flagsArray.push(`--max-dir-recursion=${settings.clamscan.maxDirRecursion}`); } - + if (settings.clamscan.maxEmbeddedPe) { flagsArray.push(`--max-embeddedpe=${settings.clamscan.maxEmbeddedPe}`); } - + if (settings.clamscan.maxHtmlnormalize) { flagsArray.push(`--max-htmlnormalize=${settings.clamscan.maxHtmlnormalize}`); } - + if (settings.clamscan.maxHtmlnotags) { flagsArray.push(`--max-htmlnotags=${settings.clamscan.maxHtmlnotags}`); } - + if (settings.clamscan.maxScriptnormalize) { flagsArray.push(`--max-scriptnormalize=${settings.clamscan.maxScriptnormalize}`); } - + if (settings.clamscan.maxZipTypercg) { flagsArray.push(`--max-ziptypercg=${settings.clamscan.maxZipTypercg}`); } - + if (settings.clamscan.maxPartitions) { flagsArray.push(`--max-partitions=${settings.clamscan.maxPartitions}`); } - + if (settings.clamscan.maxIconspe) { flagsArray.push(`--max-iconspe=${settings.clamscan.maxIconspe}`); } - + if (settings.clamscan.maxRechwp3) { flagsArray.push(`--max-rechwp3=${settings.clamscan.maxRechwp3}`); } - + if (settings.clamscan.pcreMatchLimit) { flagsArray.push(`--pcre-match-limit=${settings.clamscan.pcreMatchLimit}`); } - + if (settings.clamscan.pcreRecMatchLimit) { flagsArray.push(`--pcre-recmatch-limit=${settings.clamscan.pcreRecMatchLimit}`); } - + if (settings.clamscan.pcreMaxFilesize) { flagsArray.push(`--pcre-max-filesize=${settings.clamscan.pcreMaxFilesize}`); } - + if (settings.clamscan.disableCache !== undefined) { flagsArray.push(`--disable-cache=${settings.clamscan.disableCache ? 'yes' : 'no'}`); } } - + // Flags specific to clamdscan else if (scanner === 'clamdscan') { flagsArray.push('--fdpass'); - + // Remove infected files if (settings.removeInfected === true) flagsArray.push('--remove'); - + // Specify a config file if ( 'clamdscan' in settings && @@ -704,19 +706,20 @@ class NodeClam { 'configFile' in settings.clamdscan && settings.clamdscan.configFile && typeof settings.clamdscan.configFile === 'string' - ) flagsArray.push(`--config-file=${settings.clamdscan.configFile}`); - + ) + flagsArray.push(`--config-file=${settings.clamdscan.configFile}`); + // Turn on multi-threaded scanning if (settings.clamdscan.multiscan === true) flagsArray.push('--multiscan'); - + // Reload the virus DB if (settings.clamdscan.reloadDb === true) flagsArray.push('--reload'); } - + // *************** // Common flags // *************** - + // Remove infected files if (settings.removeInfected !== true) { if ( @@ -727,29 +730,28 @@ class NodeClam { flagsArray.push(`--move=${settings.quarantineInfected}`); } } - + // Write info to a log if ('scanLog' in settings && settings.scanLog && typeof settings.scanLog === 'string') flagsArray.push(`--log=${settings.scanLog}`); - + // Read list of files to scan from a file if ('fileList' in settings && settings.fileList && typeof settings.fileList === 'string') flagsArray.push(`--file-list=${settings.fileList}`); - + // Scan files specified via --file-list if (settings.filesFromList) { flagsArray.push('--file-from-list'); } - + // Add timeout for scanning if (settings.scanTimeout) { flagsArray.push(`--timeout=${settings.scanTimeout}`); } - + // Return the final flags return flagsArray; } - /** * Create socket connection to a remote(or local) clamav daemon. @@ -1256,96 +1258,96 @@ class NodeClam { return hasCb ? cb(err, file, null) : reject(err); } - // If user wants to scan via socket or TCP... - if (this.settings.clamdscan.socket || this.settings.clamdscan.port || this.settings.clamdscan.host) { - // Scan using local unix domain socket (much simpler/faster process--especially with MULTISCAN enabled) - if (this.settings.clamdscan.socket) { - let client; - - try { - client = await this._initSocket('isInfected'); - if (this.settings.debugMode) - console.log(`${this.debugLabel}: scanning with local domain socket now.`); + // If user wants to scan via socket or TCP... + if (this.settings.clamdscan.socket || this.settings.clamdscan.port || this.settings.clamdscan.host) { + // Scan using local unix domain socket (much simpler/faster process--especially with MULTISCAN enabled) + if (this.settings.clamdscan.socket) { + let client; - if (this.settings.clamdscan.multiscan === true) { - // Use Multiple threads (faster) - client.write(`MULTISCAN ${file}`); - } else { - // Use single or default # of threads (potentially slower) - client.write(`SCAN ${file}`); - } + try { + client = await this._initSocket('isInfected'); + if (this.settings.debugMode) + console.log(`${this.debugLabel}: scanning with local domain socket now.`); - client.on('data', async (data) => { - if (this.settings.debugMode) - console.log( - `${this.debugLabel}: Received response from remote clamd service: `, - data.toString() - ); - try { - const result = this._processResult(data.toString(), file); - if (result instanceof Error) { - client.end(); - // Throw the error so that its caught and fallback is attempted - throw result; - } + if (this.settings.clamdscan.multiscan === true) { + // Use Multiple threads (faster) + client.write(`MULTISCAN ${file}`); + } else { + // Use single or default # of threads (potentially slower) + client.write(`SCAN ${file}`); + } + client.on('data', async (data) => { + if (this.settings.debugMode) + console.log( + `${this.debugLabel}: Received response from remote clamd service: `, + data.toString() + ); + try { + const result = this._processResult(data.toString(), file); + if (result instanceof Error) { client.end(); - const { isInfected, viruses } = result; - return hasCb - ? cb(null, file, isInfected, viruses) - : resolve({ file, isInfected, viruses }); - } catch (err) { - client.end(); + // Throw the error so that its caught and fallback is attempted + throw result; + } - // Fallback to local if that's an option - if (this.settings.clamdscan.localFallback === true) return localScan(); + client.end(); + const { isInfected, viruses } = result; + return hasCb + ? cb(null, file, isInfected, viruses) + : resolve({ file, isInfected, viruses }); + } catch (err) { + client.end(); - return hasCb ? cb(err, file, null, []) : reject(err); - } - }); - } catch (err) { - if (client && 'readyState' in client && client.readyState) client.end(); + // Fallback to local if that's an option + if (this.settings.clamdscan.localFallback === true) return localScan(); - // Fallback to local if that's an option - if (this.settings.clamdscan.localFallback === true) return localScan(); + return hasCb ? cb(err, file, null, []) : reject(err); + } + }); + } catch (err) { + if (client && 'readyState' in client && client.readyState) client.end(); - return hasCb ? cb(err, file, null, []) : reject(err); - } - } + // Fallback to local if that's an option + if (this.settings.clamdscan.localFallback === true) return localScan(); - // Scan using remote host/port and TCP protocol (must stream the file) - else { - // Convert file to stream - const stream = fs.createReadStream(file); - - // Attempt to scan the stream. - try { - const isInfected = await this.scanStream(stream); - return hasCb ? cb(null, file, isInfected, []) : resolve({ ...isInfected, file }); - } catch (e) { - // Fallback to local if that's an option - if (this.settings.clamdscan.localFallback === true) return await localScan(); - - // Otherwise, fail - const err = new NodeClamError({ err: e, file }, 'Could not scan file via TCP or locally!'); - return hasCb ? cb(err, file, null, []) : reject(err); - } finally { - // Kill file stream on response - stream.destroy(); - } + return hasCb ? cb(err, file, null, []) : reject(err); } } - - // If the user just wants to scan locally... + + // Scan using remote host/port and TCP protocol (must stream the file) else { + // Convert file to stream + const stream = fs.createReadStream(file); + + // Attempt to scan the stream. try { - return await localScan(); - } catch (err) { - return hasCb ? cb(err, file, null) : reject(err); + const isInfected = await this.scanStream(stream); + return hasCb ? cb(null, file, isInfected, []) : resolve({ ...isInfected, file }); + } catch (e) { + // Fallback to local if that's an option + if (this.settings.clamdscan.localFallback === true) return await localScan(); + + // Otherwise, fail + const err = new NodeClamError({ err: e, file }, 'Could not scan file via TCP or locally!'); + return hasCb ? cb(err, file, null, []) : reject(err); + } finally { + // Kill file stream on response + stream.destroy(); } } - }); - } + } + + // If the user just wants to scan locally... + else { + try { + return await localScan(); + } catch (err) { + return hasCb ? cb(err, file, null) : reject(err); + } + } + }); + } /** * Returns a PassthroughStream object which allows you to @@ -2610,9 +2612,6 @@ class NodeClam { } }); } - } - - module.exports = NodeClam;