Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 323 lines (238 sloc) 11.681 kb
1e41927 @baudehlo Documentation. Woot!
authored
1 Writing Haraka Plugins
2 =======
3
c3e78f4 @msimerson connection: plugins.js: add connect_init hook
msimerson authored
4 All aspects of receiving an email in Haraka are controlled via plugins. No
5 mail can even be received unless at least a 'rcpt' and 'queue' plugin are
6 enabled.
1e41927 @baudehlo Documentation. Woot!
authored
7
c3e78f4 @msimerson connection: plugins.js: add connect_init hook
msimerson authored
8 'rcpt' plugins determine if a particular recipient is allowed to be relayed
9 or received for. A 'queue' plugin queue's the email somewhere - normally to
10 disk or to an another SMTP server.
1e41927 @baudehlo Documentation. Woot!
authored
11
c3e78f4 @msimerson connection: plugins.js: add connect_init hook
msimerson authored
12 Get a list of built-in plugins by running:
ba4a25d @smfreegard Improve plugin documentation
smfreegard authored
13
14 `haraka -l -c /path/to/config`
15
c3e78f4 @msimerson connection: plugins.js: add connect_init hook
msimerson authored
16 Display the help text for a plugin by running:
ba4a25d @smfreegard Improve plugin documentation
smfreegard authored
17
18 `haraka -h <name> -c /path/to/config`
19
c3e78f4 @msimerson connection: plugins.js: add connect_init hook
msimerson authored
20 Omit the `-c /path/to/config` to see only the plugins supplied with Haraka
21 (not your local plugins in your `config` directory).
ba4a25d @smfreegard Improve plugin documentation
smfreegard authored
22
1e41927 @baudehlo Documentation. Woot!
authored
23 Anatomy of a Plugin
24 ------
25
c3e78f4 @msimerson connection: plugins.js: add connect_init hook
msimerson authored
26 Plugins in Haraka are Javascript files in the plugins/ directory.
1e41927 @baudehlo Documentation. Woot!
authored
27
c3e78f4 @msimerson connection: plugins.js: add connect_init hook
msimerson authored
28 To enable a plugin, add its name to `config/plugins`.
1e41927 @baudehlo Documentation. Woot!
authored
29
c3e78f4 @msimerson connection: plugins.js: add connect_init hook
msimerson authored
30 To hook into the "rcpt" event, create a method in exports
1e41927 @baudehlo Documentation. Woot!
authored
31 to hook it:
32
76f30eb Renamed the callback() to next() everywhere.
Matt Sergeant authored
33 exports.hook_rcpt = function (next, connection, params) {
1e41927 @baudehlo Documentation. Woot!
authored
34 // email address is in params[0]
35 // do something with the address... then call:
e61262a Re-apply doc changes
Matt Sergeant authored
36 next();
1e41927 @baudehlo Documentation. Woot!
authored
37 };
38
c3e78f4 @msimerson connection: plugins.js: add connect_init hook
msimerson authored
39 That hook introduces a couple of new concepts so let's go through them:
1e41927 @baudehlo Documentation. Woot!
authored
40
c3e78f4 @msimerson connection: plugins.js: add connect_init hook
msimerson authored
41 * next - call this when done processing or Haraka will hang.
42 * exports - the plugin is an object (with access to "this" if you need it)
1e41927 @baudehlo Documentation. Woot!
authored
43 but methods go directly into exports.
44
c3e78f4 @msimerson connection: plugins.js: add connect_init hook
msimerson authored
45 The next() method is the most critical thing here - since Haraka is event
46 based, we may need to go fetch network information before returning a
47 result. This is doneasynchronously and we run next() when we are done,
48 allowing Haraka to go process other clients while we wait for information.
1e41927 @baudehlo Documentation. Woot!
authored
49
76f30eb Renamed the callback() to next() everywhere.
Matt Sergeant authored
50 See "The Next Function" below for more details.
4cc0dd9 @baudehlo Ported to new plugin system which auto-inserts constants for you
authored
51
1e41927 @baudehlo Documentation. Woot!
authored
52 Logging
53 ------
54
e61262a Re-apply doc changes
Matt Sergeant authored
55 Plugins inherit all the logging methods of `logger.js`, which are:
1e41927 @baudehlo Documentation. Woot!
authored
56
419c3a1 @baudehlo Support hooks that don't need a _respond method, and run hooks for deny
authored
57 * logprotocol
1e41927 @baudehlo Documentation. Woot!
authored
58 * logdebug
59 * loginfo
60 * lognotice
61 * logwarn
62 * logerror
63 * logcrit
64 * logalert
65 * logemerg
66
c3e78f4 @msimerson connection: plugins.js: add connect_init hook
msimerson authored
67 If plugins throw an exception when in a hook, the exception will be caught
68 and generate a logcrit level error. However, exceptions will not be caught
69 as gracefully when plugins are running async code. Use error codes for that,
70 log the error, and run your next() function appropriately.
1e41927 @baudehlo Documentation. Woot!
authored
71
72 Multiple Hooks
73 -----
74
c3e78f4 @msimerson connection: plugins.js: add connect_init hook
msimerson authored
75 To hook the same event multiple times, provide a register()
1e41927 @baudehlo Documentation. Woot!
authored
76 method and hook it:
77
78 exports.register = function() {
79 this.register_hook('queue', 'try_queue_my_way');
80 this.register_hook('queue', 'try_queue_highway');
81 };
82
c3e78f4 @msimerson connection: plugins.js: add connect_init hook
msimerson authored
83 When the earlier hook calls `next()` (without parameters) it continues
84 to the next registered hook.
1e41927 @baudehlo Documentation. Woot!
authored
85
76f30eb Renamed the callback() to next() everywhere.
Matt Sergeant authored
86 The Next Function
87 =================
4cc0dd9 @baudehlo Ported to new plugin system which auto-inserts constants for you
authored
88
e110c5b @baudehlo Documented new hooks
authored
89 The next() function takes two optional parameters: `code` and `msg`
4cc0dd9 @baudehlo Ported to new plugin system which auto-inserts constants for you
authored
90
91 The code is one of the below listed return values. The msg corresponds with
76f30eb Renamed the callback() to next() everywhere.
Matt Sergeant authored
92 the string to send to the client in case of a failure. Use an Array if you need
93 to send back a multi-line response. The msg should NOT contain the code number
94 - that is taken care of by the Haraka internals.
4cc0dd9 @baudehlo Ported to new plugin system which auto-inserts constants for you
authored
95
76f30eb Renamed the callback() to next() everywhere.
Matt Sergeant authored
96 Return Values
97 -------------
1e41927 @baudehlo Documentation. Woot!
authored
98
4cc0dd9 @baudehlo Ported to new plugin system which auto-inserts constants for you
authored
99 These constants are compiled into your plugin when it is loaded, you do not
100 need to define them:
1e41927 @baudehlo Documentation. Woot!
authored
101
4cc0dd9 @baudehlo Ported to new plugin system which auto-inserts constants for you
authored
102 * CONT
1e41927 @baudehlo Documentation. Woot!
authored
103
76f30eb Renamed the callback() to next() everywhere.
Matt Sergeant authored
104 Continue and let other plugins handle this particular hook. This is the
105 default if no parameters are given.
1e41927 @baudehlo Documentation. Woot!
authored
106
4cc0dd9 @baudehlo Ported to new plugin system which auto-inserts constants for you
authored
107 * DENY
1e41927 @baudehlo Documentation. Woot!
authored
108
4cc0dd9 @baudehlo Ported to new plugin system which auto-inserts constants for you
authored
109 Reject the mail with a 5xx error.
1e41927 @baudehlo Documentation. Woot!
authored
110
4cc0dd9 @baudehlo Ported to new plugin system which auto-inserts constants for you
authored
111 * DENYSOFT
1e41927 @baudehlo Documentation. Woot!
authored
112
4cc0dd9 @baudehlo Ported to new plugin system which auto-inserts constants for you
authored
113 Reject the mail with a 4xx error.
1e41927 @baudehlo Documentation. Woot!
authored
114
4cc0dd9 @baudehlo Ported to new plugin system which auto-inserts constants for you
authored
115 * DENYDISCONNECT
1e41927 @baudehlo Documentation. Woot!
authored
116
4cc0dd9 @baudehlo Ported to new plugin system which auto-inserts constants for you
authored
117 Reject the mail with a 5xx error and immediately disconnect.
1e41927 @baudehlo Documentation. Woot!
authored
118
4cc0dd9 @baudehlo Ported to new plugin system which auto-inserts constants for you
authored
119 * DISCONNECT
1e41927 @baudehlo Documentation. Woot!
authored
120
4cc0dd9 @baudehlo Ported to new plugin system which auto-inserts constants for you
authored
121 Simply immediately disconnect
1e41927 @baudehlo Documentation. Woot!
authored
122
4cc0dd9 @baudehlo Ported to new plugin system which auto-inserts constants for you
authored
123 * OK
1e41927 @baudehlo Documentation. Woot!
authored
124
e110c5b @baudehlo Documented new hooks
authored
125 Required by rcpt and queue plugins if we are to allow the email to be
8d47b7a @smfreegard Finally get hook_deny logic right and documented, remove clamdscan pl…
smfreegard authored
126 accepted, or the queue was successful, respectively.
1e41927 @baudehlo Documentation. Woot!
authored
127
8d47b7a @smfreegard Finally get hook_deny logic right and documented, remove clamdscan pl…
smfreegard authored
128 This also has a special meaning when used on deny hook. Returning OK
129 on the deny hook will override the result to CONT.
130
131 Once a plugin calls next(OK) no further plugins on the same hook will
c3e78f4 @msimerson connection: plugins.js: add connect_init hook
msimerson authored
132 run after it. (excepting for connect_init and disconnect).
1e41927 @baudehlo Documentation. Woot!
authored
133
d765edf @ImLostInSpace Fix all underscores (to be escaped).
ImLostInSpace authored
134 * HOOK\_NEXT
b6d4cec @smfreegard Add new NEXT_HOOK return code, add XCLIENT and numerous fixes to smtp…
smfreegard authored
135
136 This is a special return value that is currently only available on the
d765edf @ImLostInSpace Fix all underscores (to be escaped).
ImLostInSpace authored
137 `unrecognized_command` hook. It instructs Haraka to run a different plugin
b6d4cec @smfreegard Add new NEXT_HOOK return code, add XCLIENT and numerous fixes to smtp…
smfreegard authored
138 hook instead of responding normally. The `msg` argument is required and
139 must be set to the name of the hook that is to be run.
140
141
1e41927 @baudehlo Documentation. Woot!
authored
142 Available Hooks
143 -------------
144
145 These are just the name of the hook, with any parameter sent to it:
146
d765edf @ImLostInSpace Fix all underscores (to be escaped).
ImLostInSpace authored
147 * init\_master - called when the main (master) process is started
148 * init\_child - called whenever a child process is started when using multiple "nodes"
c3e78f4 @msimerson connection: plugins.js: add connect_init hook
msimerson authored
149 * connect\_init - used to init data structures, called for *every* connection
d765edf @ImLostInSpace Fix all underscores (to be escaped).
ImLostInSpace authored
150 * lookup\_rdns - called to look up the rDNS - return the rDNS via `next(OK, rdns)`
1e41927 @baudehlo Documentation. Woot!
authored
151 * connect - called after we got rDNS
e110c5b @baudehlo Documented new hooks
authored
152 * capabilities - called to get the ESMTP capabilities (such as STARTTLS)
d765edf @ImLostInSpace Fix all underscores (to be escaped).
ImLostInSpace authored
153 * unrecognized\_command - called when the remote end sends a command we don't recognise
1e41927 @baudehlo Documentation. Woot!
authored
154 * disconnect - called upon disconnect
155 * helo (hostname)
156 * ehlo (hostname)
157 * quit
158 * vrfy
159 * noop
b6d4cec @smfreegard Add new NEXT_HOOK return code, add XCLIENT and numerous fixes to smtp…
smfreegard authored
160 * rset
4cc0dd9 @baudehlo Ported to new plugin system which auto-inserts constants for you
authored
161 * mail ([from, esmtp\_params])
162 * rcpt ([to, esmtp\_params])
d765edf @ImLostInSpace Fix all underscores (to be escaped).
ImLostInSpace authored
163 * rcpt\_ok (to)
e110c5b @baudehlo Documented new hooks
authored
164 * data - called at the DATA command
d765edf @ImLostInSpace Fix all underscores (to be escaped).
ImLostInSpace authored
165 * data\_post - called at the end-of-data marker
166 * max\_data\_exceeded - called if the message is bigger than connection.max\_bytes
e110c5b @baudehlo Documented new hooks
authored
167 * queue - called to queue the mail
d765edf @ImLostInSpace Fix all underscores (to be escaped).
ImLostInSpace authored
168 * queue\_outbound - called to queue the mail when connection.relaying is set
169 * queue\_ok - called when a mail has been queued successfully
170 * reset\_transaction - called before the transaction is reset (via RSET, or MAIL)
419c3a1 @baudehlo Support hooks that don't need a _respond method, and run hooks for deny
authored
171 * deny - called if a plugin returns one of DENY, DENYSOFT or DENYDISCONNECT
d765edf @ImLostInSpace Fix all underscores (to be escaped).
ImLostInSpace authored
172 * get\_mx (hmail, domain) - called when sending outbound mail to lookup the MX record
aaf3119 @pedroaxl deferred hook
pedroaxl authored
173 * deferred (hmail, params) - called when sending outbound mail if the mail was deferred
e4c9a03 @smfreegard Add hook_reset_transaction + hook documentation
smfreegard authored
174 * bounce (hmail, err) - called when sending outbound mail if the mail would bounce
a9ee8a8 @smfreegard Add AUTH support to outbound
smfreegard authored
175 * delivered (hmail, [host, ip, response, delay, port, mode, ok_recips, secured, authenticated]) -
867b746 @smfreegard Fix docs
smfreegard authored
176 called when outbound mail is delivered to the destination
d765edf @ImLostInSpace Fix all underscores (to be escaped).
ImLostInSpace authored
177 * send\_email (hmail) - called when outbound is about to be sent
1e41927 @baudehlo Documentation. Woot!
authored
178
e61262a Re-apply doc changes
Matt Sergeant authored
179 The `rcpt` hook is slightly special. If we have a plugin (prior to rcpt) that
180 sets the `connection.relaying = true` flag, then we do not need any rcpt
181 hooks, or if we do, none of them need call `next(OK)`. However if
182 `connection.relaying` remains `false` (as is the default - you don't want an
183 open relay!), then one rcpt plugin MUST return `next(OK)` or your sender
2efa511 @msimerson new plugin: limit, with limits for:
msimerson authored
184 will receive the error message "I cannot deliver for that user". The default
185 plugin for this is `rcpt_to.in_host_list`, which
e61262a Re-apply doc changes
Matt Sergeant authored
186 lists the domains for which you wish to receive email.
187
188 If a rcpt plugin DOES call `next(OK)` then the `rcpt_ok` hook is run. This
d765edf @ImLostInSpace Fix all underscores (to be escaped).
ImLostInSpace authored
189 is primarily used by the `queue/smtp_proxy` plugin which needs to run after
e61262a Re-apply doc changes
Matt Sergeant authored
190 all rcpt hooks.
191
c3e78f4 @msimerson connection: plugins.js: add connect_init hook
msimerson authored
192 The `connect_init` hook is also special in that all return codes are ignored.
193 This is so that plugins that need to do initialization for every connection
194 can be assured they will run. To accomplish this, return values are ignored.
195
ba4a25d @smfreegard Improve plugin documentation
smfreegard authored
196 Hook Run Order
197 --------------
198
199 The ordering of hooks is determined by the SMTP protocol, some knowledge of
200 RFC5321 is required.
201
202 ##### Typical Inbound mail
203
c3e78f4 @msimerson connection: plugins.js: add connect_init hook
msimerson authored
204 - hook_connect_init
ba4a25d @smfreegard Improve plugin documentation
smfreegard authored
205 - hook_lookup_rdns
206 - hook_connect
207 - hook_helo **OR** hook_ehlo (EHLO is sent when ESMTP is desired which allows extensions
208 such as STARTTLS, AUTH, SIZE etc.)
209 - hook_helo
210 - hook_ehlo
211 - hook_capabilities
212 - *hook_unrecognized_command* will run for each ESMTP extension the client requests
213 e.g. STARTTLS, AUTH etc.)
214 - hook_mail
215 - hook_rcpt (this will run once per-recipient)
216 - hook_rcpt_ok (this will run for every recipient that hook_rcpt returned `next(OK)` for)
217 - hook_data
218 - *[attachment hooks]*
219 - hook_data_post
220 - hook_queue **OR** hook_queue_outbound
221 - hook_queue_ok (called if hook_queue or hook_queue_outbound returns `next(OK)`)
222 - hook_quit **OR** hook_rset **OR** hook_helo **OR** hook_ehlo (the client can either
223 disconnect once a message has been sent or it can start a new transaction by sending RSET, EHLO
224 or HELO to reset the transaction and then start a new transaction by starting with MAIL again)
225 - hook_reset_transaction
226 - hook_disconnect
227
228 ##### Typical Outbound mail
229
c3e78f4 @msimerson connection: plugins.js: add connect_init hook
msimerson authored
230 By 'outbound' we mean messages that use Haraka's built-in queueing and delivery
231 mechanism. This is used when `connection.relaying = true` is set during the message transaction
ba4a25d @smfreegard Improve plugin documentation
smfreegard authored
232 and `hook_queue_outbound` is called to queue the message.
233
c3e78f4 @msimerson connection: plugins.js: add connect_init hook
msimerson authored
234 The Outbound hook ordering will mirror the Inbound mail order above until after `hook_queue_outbound`, which is followed by:
ba4a25d @smfreegard Improve plugin documentation
smfreegard authored
235
236 - hook_send_email
237 - hook_get_mx
238 - hook_delivered **OR** hook_deferred **OR** hook_bounce
239 - hook_delivered (called once per delivery domain with at least one successfull recipient)
240 - hook_deferred (called once per delivery domain where at least one recipient or connection was deferred)
241 - hook_bounce (called once per delivery domain where the recipient(s) or message was rejected by the destination)
242
243 Plugin Run Order
244 ----------------
245
c3e78f4 @msimerson connection: plugins.js: add connect_init hook
msimerson authored
246 Plugins are run on each hook in the order that they are specified in
247 `config/plugins`. When a plugin returns anything other than `next()` on a hook,
248 all subsequent plugins due to run on that hook are skipped.
ba4a25d @smfreegard Improve plugin documentation
smfreegard authored
249
250 This is important as some plugins might rely on `results` or `notes` that have
c3e78f4 @msimerson connection: plugins.js: add connect_init hook
msimerson authored
251 been set by plugins that need to run before them. This should be noted in the
252 plugins documentation. Make sure to read it.
ba4a25d @smfreegard Improve plugin documentation
smfreegard authored
253
c3e78f4 @msimerson connection: plugins.js: add connect_init hook
msimerson authored
254 If you are writing a complex plugin, you may have to split it into multiple
255 plugins to run in a specific order e.g. you want hook_deny to run last after
24d15b6 @smfreegard Plugin ordering
smfreegard authored
256 all other plugins and hook_lookup_rdns to run first, then you can explicitly
257 register your hooks and provide a `priority` value which is an integer between
258 -100 (highest priority) to 100 (lowest priority) which defaults to 0 (zero) if
259 not supplied. You can apply a priority to your hook in the following way:
260
c3e78f4 @msimerson connection: plugins.js: add connect_init hook
msimerson authored
261 ````
24d15b6 @smfreegard Plugin ordering
smfreegard authored
262 exports.register = function() {
263 var plugin = this;
264 plugin.register_hook('connect', 'hook_connect', -100);
265 }
c3e78f4 @msimerson connection: plugins.js: add connect_init hook
msimerson authored
266 ````
24d15b6 @smfreegard Plugin ordering
smfreegard authored
267
268 This would ensure that your hook_connect function will run before any other
269 plugins registered on the `connect` hook, regardless of the order it was
270 specified in `config/plugins`.
ba4a25d @smfreegard Improve plugin documentation
smfreegard authored
271
272 You can check the order that the plugins will run on each hook by running:
273
274 `haraka -o -c /path/to/config`
275
d3e278e @smfreegard Improve plugin documentation
smfreegard authored
276 Sharing State
277 -------------
278
279 There are several cases where you might need to share information between
280 plugins. This is done using `notes` - there are three types available:
281
282 * server.notes
283
284 Available in all plugins. This is created at PID start-up and is shared
285 amongst all plugins on the same PID and listener.
286 Typical uses for notes at this level would be to share database
287 connections between multiple plugins or connection pools etc.
288
289 * connection.notes
290
291 Available on any hook that passes 'connection' as a function parameter.
292 This is shared amongst all plugins for a single connection and is
293 destroyed after the client disconnects.
294 Typical uses for notes at this level would be to store information
295 about the connected client e.g. rDNS names, HELO/EHLO, white/black
296 list status etc.
297
298 * connection.transaction.notes
299
300 Available on any hook that passes 'connection' as a function parameter
d765edf @ImLostInSpace Fix all underscores (to be escaped).
ImLostInSpace authored
301 between hook\_mail and hook\_data\_post.
d3e278e @smfreegard Improve plugin documentation
smfreegard authored
302 This is shared amongst all plugins for this transaction (e.g. MAIL FROM
303 through until a message is received or the connection is reset).
304 Typical uses for notes at this level would be to store information
305 on things like greylisting which uses client, sender and recipient
306 information etc.
307
308 All of these notes are simply a Javascript object underneath - so you use
309 them like a simple key/value store e.g.
310
311 connection.transaction.notes.test = 'testing';
312
2efa511 @msimerson new plugin: limit, with limits for:
msimerson authored
313 ## See also, [Results](Results)
314
315
1e41927 @baudehlo Documentation. Woot!
authored
316 Further Reading
317 --------------
318
c3e78f4 @msimerson connection: plugins.js: add connect_init hook
msimerson authored
319 Now you want to read about the [Connection](Connection.md) object.
94f3153 Implement fixes to deferred hooks and better docs.
Matt Sergeant authored
320
c3e78f4 @msimerson connection: plugins.js: add connect_init hook
msimerson authored
321 Outbound hooks are [also documented](Outbound.md).
1e41927 @baudehlo Documentation. Woot!
authored
322
Something went wrong with that request. Please try again.