generated from haraka/haraka-plugin-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
234 additions
and
154 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,10 @@ | ||
engines: | ||
eslint: | ||
enabled: true | ||
channel: "eslint-8" | ||
channel: 'eslint-8' | ||
config: | ||
config: ".eslintrc.yaml" | ||
config: '.eslintrc.yaml' | ||
|
||
ratings: | ||
paths: | ||
- "**.js" | ||
- '**.js' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,4 +4,4 @@ env: | |
mocha: true | ||
es2022: true | ||
|
||
extends: ["@haraka"] | ||
extends: ['@haraka'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
singleQuote: true | ||
semi: false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,61 +7,45 @@ | |
|
||
Clone me, to create a new Haraka plugin! | ||
|
||
# Template Instructions | ||
# avg - Anti-Virus scanner | ||
|
||
These instructions will not self-destruct after use. Use and destroy. | ||
Implement virus scanning with AVG's TCPD daemon, available for Linux/FreeBSD. AVG linux is [free for personal or commercial use](http://www.avg.com/gb-en/faq.pnuid-faq_v3_linux) and can be downloaded from [free.avg.com](http://free.avg.com/gb-en/download.prd-alf). | ||
|
||
See also, [How to Write a Plugin](https://github.com/haraka/Haraka/wiki/Write-a-Plugin) and [Plugins.md](https://github.com/haraka/Haraka/blob/master/docs/Plugins.md) for additional plugin writing information. | ||
Messages that AVG detects as infected are rejected. Errors will cause the plugin to return temporary failures unless the defer options are changed (see below). | ||
|
||
## Create a new repo for your plugin | ||
## Configuration | ||
|
||
Haraka plugins are named like `haraka-plugin-something`. All the namespace after `haraka-plugin-` is yours for the taking. Please check the [Plugins](https://github.com/haraka/Haraka/blob/master/Plugins.md) page and a Google search to see what plugins already exist. | ||
The following options can be set in avg.ini: | ||
|
||
Once you've settled on a name, create the GitHub repo. On the repo's main page, click the _Clone or download_ button and copy the URL. Then paste that URL into a local ENV variable with a command like this: | ||
- port (default: 54322) | ||
|
||
```sh | ||
export MY_GITHUB_ORG=haraka | ||
export MY_PLUGIN_NAME=haraka-plugin-SOMETHING | ||
``` | ||
|
||
Clone and rename the avg repo: | ||
|
||
```sh | ||
git clone [email protected]:haraka/haraka-plugin-avg.git | ||
mv haraka-plugin-avg $MY_PLUGIN_NAME | ||
cd $MY_PLUGIN_NAME | ||
git remote rm origin | ||
git remote add origin "[email protected]:$MY_GITHUB_ORG/$MY_PLUGIN_NAME.git" | ||
``` | ||
TCP port to communicate with the AVG TCPD on. | ||
|
||
Now you'll have a local git repo to begin authoring your plugin | ||
- tmpdir (default: /tmp) | ||
|
||
## rename boilerplate | ||
AVG TCPD requires that the message be written to disk and scanned. This setting configures where any temporary files are written to. After scanning, the temporary files are automatically removed. | ||
|
||
Replaces all uses of the word `avg` with your plugin's name. | ||
- connect_timeout (default: 10) | ||
|
||
./redress.sh [something] | ||
Maximum seconds to wait for the socket to connect. Connections taking longer will cause a temporary failure to be sent to the remote MTA. | ||
|
||
You'll then be prompted to update package.json and then force push this repo onto the GitHub repo you've created earlier. | ||
- session_timeout | ||
|
||
# Add your content here | ||
Maximum number of seconds to wait for a reply to a command before failing. A timeout will cause a temporary failure to be sent to the remote MTA. | ||
|
||
## INSTALL | ||
- [defer] | ||
|
||
```sh | ||
cd /path/to/local/haraka | ||
npm install haraka-plugin-avg | ||
echo "avg" >> config/plugins | ||
service haraka restart | ||
``` | ||
|
||
### Configuration | ||
By default, this plugin defers when errors or timeouts are encountered. To | ||
fail open (let messages pass when errors are enounctered), set the error | ||
and/or timeout values to false. | ||
|
||
If the default configuration is not sufficient, copy the config file from the distribution into your haraka config dir and then modify it: | ||
[defer] | ||
error=true | ||
timeout=true | ||
|
||
```sh | ||
cp node_modules/haraka-plugin-avg/config/avg.ini config/avg.ini | ||
$EDITOR config/avg.ini | ||
|
||
``` | ||
## USAGE | ||
|
@@ -74,3 +58,4 @@ $EDITOR config/avg.ini | |
[clim-url]: https://codeclimate.com/github/haraka/haraka-plugin-avg | ||
[npm-img]: https://nodei.co/npm/haraka-plugin-avg.png | ||
[npm-url]: https://www.npmjs.com/package/haraka-plugin-avg | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,6 @@ | ||
|
||
[main] | ||
;host= | ||
;port=54322 | ||
;tmpdir=/tmp | ||
;connect_timeout=10 | ||
;session_timeout=30 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,161 @@ | ||
"use strict"; | ||
// avg - AVG virus scanner | ||
'use strict' | ||
|
||
exports.register = function () { | ||
this.load_avg_ini(); | ||
const fs = require('node:fs') | ||
const net = require('node:net') | ||
const path = require('node:path') | ||
|
||
const net_utils = require('haraka-net-utils') | ||
|
||
// register hooks here. More info at https://haraka.github.io/core/Plugins/ | ||
// this.register_hook('data_post', 'do_stuff_with_message') | ||
}; | ||
const smtp_regexp = /^(\d{3})([ -])(.*)/ | ||
|
||
exports.register = function () { | ||
this.load_avg_ini() | ||
} | ||
|
||
exports.load_avg_ini = function () { | ||
this.cfg = this.config.get( | ||
"avg.ini", | ||
'avg.ini', | ||
{ | ||
booleans: [ | ||
"+enabled", // this.cfg.main.enabled=true | ||
"-disabled", // this.cfg.main.disabled=false | ||
"+feature_section.yes", // this.cfg.feature_section.yes=true | ||
], | ||
booleans: ['+defer.timeout', '+defer.error'], | ||
}, | ||
() => { | ||
this.load_example_ini(); | ||
this.load_avg_ini() | ||
}, | ||
); | ||
}; | ||
) | ||
} | ||
|
||
exports.get_tmp_file = function (transaction) { | ||
const tmpdir = this.cfg.main.tmpdir || '/tmp' | ||
return path.join(tmpdir, `${transaction.uuid}.tmp`) | ||
} | ||
|
||
exports.hook_data_post = function (next, connection) { | ||
if (!connection?.transaction) return next() | ||
|
||
const plugin = this | ||
const tmpfile = plugin.get_tmp_file(connection.transaction) | ||
const ws = fs.createWriteStream(tmpfile) | ||
|
||
ws.once('error', (err) => { | ||
connection.results.add(plugin, { | ||
err: `Error writing temporary file: ${err.message}`, | ||
}) | ||
if (!plugin.cfg.defer.error) return next() | ||
return next(DENYSOFT, 'Virus scanner error (AVG)') | ||
}) | ||
|
||
ws.once('close', () => { | ||
const start_time = Date.now() | ||
const socket = new net.Socket() | ||
net_utils.add_line_processor(socket) | ||
socket.setTimeout((plugin.cfg.main.connect_timeout || 10) * 1000) | ||
let connected = false | ||
let command = 'connect' | ||
let response = [] | ||
|
||
function do_next(code, msg) { | ||
fs.unlink(tmpfile, () => {}) | ||
return next(code, msg) | ||
} | ||
|
||
socket.send_command = function (cmd, data) { | ||
const line = cmd + (data ? ` ${data}` : '') | ||
connection.logprotocol(plugin, `> ${line}`) | ||
this.write(`${line}\r\n`) | ||
command = cmd.toLowerCase() | ||
response = [] | ||
} | ||
|
||
socket.on('timeout', () => { | ||
const msg = `${connected ? 'connection' : 'session'} timed out` | ||
connection.results.add(plugin, { err: msg }) | ||
if (!plugin.cfg.defer.timeout) return do_next() | ||
return do_next(DENYSOFT, 'Virus scanner timeout (AVG)') | ||
}) | ||
|
||
socket.on('error', (err) => { | ||
connection.results.add(plugin, { err: err.message }) | ||
if (!plugin.cfg.defer.error) return do_next() | ||
return do_next(DENYSOFT, 'Virus scanner error (AVG)') | ||
}) | ||
|
||
socket.on('connect', function () { | ||
connected = true | ||
this.setTimeout((plugin.cfg.main.session_timeout || 30) * 1000) | ||
}) | ||
|
||
socket.on('line', (line) => { | ||
const matches = smtp_regexp.exec(line) | ||
connection.logprotocol(plugin, `< ${line}`) | ||
if (!matches) { | ||
connection.results.add(plugin, { | ||
err: `Unrecognized response: ${line}`, | ||
}) | ||
socket.end() | ||
if (!plugin.cfg.defer.error) return do_next() | ||
return do_next(DENYSOFT, 'Virus scanner error (AVG)') | ||
} | ||
|
||
const code = matches[1] | ||
const cont = matches[2] | ||
const rest = matches[3] | ||
response.push(rest) | ||
if (cont !== ' ') return | ||
|
||
switch (command) { | ||
case 'connect': | ||
if (code !== '220') { | ||
// Error | ||
connection.results.add(plugin, { | ||
err: `Unrecognized response: ${line}`, | ||
}) | ||
if (!plugin.cfg.defer.timeout) return do_next() | ||
return do_next(DENYSOFT, 'Virus scanner error (AVG)') | ||
} else { | ||
socket.send_command('SCAN', tmpfile) | ||
} | ||
break | ||
case 'scan': { | ||
const elapsed = Date.now() - start_time | ||
connection.loginfo(plugin, { | ||
time: `${elapsed}ms`, | ||
code, | ||
response: `"${response.join(' ')}"`, | ||
}) | ||
// Check code | ||
switch (code) { | ||
case '200': // 200 ok | ||
// Message did not contain a virus | ||
connection.results.add(plugin, { pass: 'clean' }) | ||
socket.send_command('QUIT') | ||
return do_next() | ||
case '403': | ||
// File 'eicar.com', 'Virus identified EICAR_Test' | ||
connection.results.add(plugin, { | ||
fail: response.join(' '), | ||
}) | ||
socket.send_command('QUIT') | ||
return do_next(DENY, response.join(' ')) | ||
default: | ||
// Any other result is an error | ||
connection.results.add(plugin, { | ||
err: `Bad response: ${response.join(' ')}`, | ||
}) | ||
} | ||
socket.send_command('QUIT') | ||
if (!plugin.cfg.defer.error) return do_next() | ||
return do_next(DENYSOFT, 'Virus scanner error (AVG)') | ||
} | ||
case 'quit': | ||
socket.end() | ||
break | ||
default: | ||
throw new Error(`Unknown command: ${command}`) | ||
} | ||
}) | ||
socket.connect(plugin.cfg.main.port || 54322, plugin.cfg.main.host) | ||
}) | ||
|
||
connection.transaction.message_stream.pipe(ws) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.