-
Notifications
You must be signed in to change notification settings - Fork 3
/
plugin-api.html
298 lines (228 loc) · 11.4 KB
/
plugin-api.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
<html>
<body>
<h2><a href="mailfront.html">mailfront</a></h2>
<h1>Plugin API</h1>
<hr />
<h2>Overview</h2>
<p>Plugins hook into the mail system at 5 main points:</p>
<ol>
<li>when the system is started or reset,</li>
<li>when the sender address is received,</li>
<li>when a recipient address is received,</li>
<li>when data is received, and</li>
<li>when the message is completed.</li>
</ol>
<p>At each of these events, mailfront goes through the list of loaded
plugins. For each plugin that has a handler for such an event, mailfront
calls that handler. If the handler returns an error, no further
handlers are called; otherwise control passes to the next handler. The
return code passed back to the protocol is either the error response, if
any was encountered, or the first non-error response. If the sender or
recipient handlers of all the plugins return no response, the address is
considered rejected, and it is not passed on to the back end. This is
done to prevent the default configuration from being an open relay.
Plugins may modify the sender or recipient address, as well as the
message body.</p>
<p>A <a href="plugin-template.c">template</a> plugin is included as a
starting point for developing new plugins.</p>
<h2>Plugin Structure</h2>
<p>A mailfront plugin needs to define exactly one public symbol,
"<tt>plugin</tt>". All other public symbols are ignored. That symbol
is to be defined as follows:</p>
<blockquote><pre>
struct plugin plugin = {
.version = PLUGIN_VERSION,
.flags = 0,
.commands = commands,
.init = init,
.helo = helo,
.reset = reset,
.sender = sender,
.recipient = recipient,
.data_start = data_start,
.data_block = data_block,
.message_end = message_end,
};
</pre></blockquote>
<p>All items in this structure except for <tt>.version</tt> may be
omitted if they are not needed. The <tt>.version</tt> field is a
constant set to prevent loading of plugins that were built for an
incompatible API. The <tt>.flags</tt> field controls how certain parts
of the plugin are called, and may be zero or more flags values (see
below) ored together. The remainder of the fields are hook functions
which, if present, are called at the appropriate times in the message
handling process.</p>
<p>Note that backend modules have identical structure to plugins
described here, except that the single required public symbol is named
<tt>backend</tt> instead of <tt>plugin</tt>. The backend hook functions
are also always the last ones called (with the exception of
<tt>data_start</tt> described below). Protocol modules have an entirely
different structure.</p>
<h3>Flags</h3>
<dl>
<dt><tt>FLAG_NEED_FILE</tt></dt> <dd>If set, a temporary file is created
and all message data is written to it. The file descriptor for this
temporary file is passed to the <tt>.data_start</tt> and
<tt>.message_end</tt> hooks.</dd>
</dl>
<h3>Commands</h3>
<p>The <tt>commands</tt> entry allows for the definition of new SMTP
commands in a plugin. To add commands, set it to an array of <tt>struct
command</tt> containing:</p>
<blockquote><pre>
struct command
{
const char* name;
int (*fn_enabled)(void);
int (*fn_noparam)(void);
int (*fn_hasparam)(str* param);
};
</pre></blockquote>
<p>The last command in the array must be followed by a termination
record, with <tt>name</tt> set to <tt>NULL</tt>.</p>
<p>All of the commands provided by plugins are collected together in the
order they are found and passed to the protocol module. Commands in
plugins override any built-in commands of the same name.</p>
<p>The <tt>fn_enabled</tt> member is an optional pointer to a function
which returns non-zero if the command is available for use. If this
function is not present, the command is considered always enabled.</p>
<p>Two command functions are allowed: <tt>fn_noparam</tt> is for
commands that must not have a parameter, and <tt>fn_hasparam</tt> is for
commands that require a parameter. Since there is no regular grammar
for SMTP command parameters, the entire text following the command is
passed to the function with no modifications except for stripping
leading extraneous spaces and the trailing line ending.</p>
<h2>Hook Functions</h2>
<p>All hook functions return a <tt>response</tt> pointer or
<tt>NULL</tt>. This structure consists of two elements: an unsigned
SMTP response code <tt>number</tt> and an ASCII <tt>message</tt>. If
the plugin returns a NULL response, processing continues to the next
plugin in the chain (ie pass-through). If the plugin returns a response
and the response number is greater than or equal to 400 (ie an error),
or the number has the <tt>RESPONSE_FINAL</tt> bit set,
then no further hooks in the chain are called. Response numbers less
than 400 are treated as acceptance. The first acceptance response is
remembered, but subsequent plugins are still called unless the
<tt>RESPONSE_FINAL</tt> bit is set in the number. If the response
was an error, the error is passed back through the protocol, otherwise
processing continues to the backend. Protocols that do not use the SMTP
numbers (such as QMTP) will translate the number into something
appropriate. Error numbers between 400 and 499 inclusive are considered
"temporary" errors. All others are considered "permanent" failures (ie
reject).</p>
<p>All string parameters are passed as type <tt>str*</tt> and are
modifiable. If their value is changed, all subsequent plugins and the
backend will see the modified form, as will the protocol module. See
the <a href="http://untroubled.org/bglibs/docs/group__str.html">bglibs
str</a> documentation module for functions to use in manipulating these
objects.</p>
<p>Sender and recipient SMTP parameters are passed as a <tt>str*</tt>
containing a NUL delimited list of <tt>KEYWORD=VALUE</tt> pairs. If the
parameter keyword was not followed by a value in the SMTP conversation,
the <tt>=VALUE</tt> portion will not be present in the string.</tt>
<p>Be aware that the <tt>sender</tt> and <tt>recipient</tt> hooks may be
called before the message data is handled (as with the SMTP protocol) or
after (as with the QMQP and QMTP protocol). In either case, the
<tt>reset</tt> hook will always be called at least once before the
message is started, and the <tt>message_end</tt> hook is called after
the message has been completely transmitted.<p>
<dl>
<dt><tt>const response* init(void)</tt></dt> <dd>This hook is called
once after all the plugins have been loaded.</dd>
<dt><tt>const response* reset(void)</tt></dt> <dd>This hook is called
when preparing to start a new message, with the intent that all modules
will flush any data specific to the message, as well as after error
responses to the sender address or data, and after the SMTP
<tt>HELO</tt> command.</dd>
<dt><tt>const response* helo(str* hostname, str* capabilities)</tt></dt>
<dd>This hook is called when the SMTP <tt>HELO</tt> or <tt>EHLO</tt>
commands are issued. As yet nothing actually uses the <tt>hostname</tt>
string. Other protocols will not call this hook.
The <tt>capabilities</tt> variable contains a list of SMTP EHLO response
capabilities, each followed by a newline.</dd>
<dt><tt>const response* sender(str* address, str* params)</tt></dt>
<dd>This hook is called after a sender email address is transmitted by
the client, and is called exactly once per message.</dd>
<dt><tt>const response* recipient(str* address, str* params)</tt></dt>
<dd>This hook is called after a sender email address is transmitted by
the client, and may be called zero or more times per message.</dd>
<dt><tt>const response* data_start(int fd)</tt></dt> <dd>This hook is
called when the sender starts transmitting the message data. Note that
the backend is initialized <i>before</i> calling the plugin hooks, in
order that plugins may send extra header data to the backend in this
hook.</dd>
<dt><tt>const response* data_block(const char* bytes, unsigned long
len)</tt></dt> <dd>This hook is called as blocks of data are received
from the sender.</dd>
<dt><tt>const response* message_end(int fd)</tt></dt> <dd>This hook is
called when the message has been completely transmitted by the
sender.</dd>
</dl>
<h2>Session Data</h2>
<p>The <tt>session</tt> structure contains all the current session data,
including pointers to the protocol module, the backend module,
environment variables, temporary message file descriptor, and internal
named strings and numbers. Plugins may use these internal named data
items to store information for internal use or to pass to other plugins
with the following functions. Note that the string and number tables
are independent and may contain items with the same names without
conflicts. The named strings work like environment variables but are
not exposed when subprograms are executed. The numbers work similarly,
but the data type is <tt>unsigned long</tt> instead of a string
pointer.</p>
<dl>
<dt><tt>const char* session_protocol(void)</tt></dt> <dd>Returns the
name of the protocol front end module.</tt>
<dt><tt>void session_delnum(const char* name)</tt></dt> <dd>Delete the
named number from the session.</dd>
<dt><tt>void session_delstr(const char* name)</tt></dt> <dd>Delete the
named string from the session.</dd>
<dt><tt>unsigned long session_getnum(const char* name, unsigned long
dflt)</tt></dt> <dd>Get the named number from the session. If the name
is not present, <tt>dflt</tt> is returned.</dd>
<dt><tt>int session_hasnum(const char* name, unsigned long*
num)</tt></dt> <dd>Returns true if the named number is present in the
session.</dd>
<dt><tt>const char* session_getstr(const char* name)</tt></dt> <dd>Fetch
the named string from the session. If the name is not present,
<tt>NULL</tt> is returned.</dd>
<dt><tt>void session_setnum(const char* name, unsigned long
value)</tt></dt> <dd>Set the named number in the session.</dd>
<dt><tt>void session_setstr(const char* name, const char*
value)</tt></dt> <dd>Set the named string in the session.</dd>
</dl>
<h2>Library Functions</h2>
<dl>
<dt><tt>const response* backend_data_block(const char* data, unsigned
long len)</tt></dt> <dd>This routine writes a block of data directly to
the backend. It takes care of handling both writing to the temporary
file if it was created or writing directly to the backend module.</dd>
<dt><tt>const char* getprotoenv(const char* name)</tt></dt> <dd>Fetch
the environment variable with the given name prefixed by the value of
<tt>$PROTO</tt>. For example, if <tt>$PROTO</tt> is set to
"<tt>TCP</tt>" (as with <a
href="http://cr.yp.to/ucspi-tcp/tcpserver.html">tcpserver</a>), then
<tt>getprotoenv("LOCALIP")</tt> will get the environment variable named
"<tt>TCPLOCALIP</tt>".</dd>
<dt><tt>int scratchfile(void)</tt></dt> <dd>Create a new temporary file
descriptor opened for reading and writing. The temporary filename is
unlinked before returning so that the temporary file will be deleted as
soon as it is closed (by the plugin or when mailfront exits).</dd>
<!-- <dt><tt></tt></dt> <dd></dd> -->
</dl>
<h2>Hints</h2>
<h3>Rewriting the message body</h3>
<p>Plugins that need to rewrite the message of the body should do so in
the <tt>message_end</tt> hook. Create a new temporary file descriptor
with <tt>scratchfile()</tt> and write the complete new message to it.
Then move the new temporary file over to the existing one with the
following sequence:</p>
<blockquote><pre>
dup2(tmpfd, fd);
close(tmpfd);
</pre></blockquote>
<p>Be sure to rewind the original file descriptor with
<tt>lseek(fd,SEEK_SET,0)</tt> before using it, since the file position
will normally be at the very end of the data.</p>
</body>
</html>