-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathREADME
executable file
·449 lines (305 loc) · 14.3 KB
/
README
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
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
Python Local Scan for Exim 4.x
===========================
2003-07-05 Barry Pederson <[email protected]>
This software embeds a Python interpreter into Exim 4.x, for running a
Python-based local_scan function against incoming messages.
---------
COMPILING
---------
First, make sure you can build and run a plain Exim installation before
attempting to add Python support. Start by reading the toplevel Exim
README file.
Embedding Perl into Exim may cause linking conflicts with Python, you've
been warned.
Once you've successfully built Exim, you may try patching the Exim
Local/Makefile by running the patch_exim_makefile.py script included in
this distribution. The script takes one argument, the path to your
toplevel Exim build directory (that contains the "Local" directory the
Exim install docs told you to create). The script will patch the Local/Makefile
and symlink the C sourcefile for the local_scan function (which should live
in the same directory as the patch script).
Rebuild Exim using the patched Makefile, if you don't see any errors then
you should be in business. Install the new binary the same way as you did
with plain Exim.
----------------
CONFIGURING EXIM
----------------
There are a few options for this software that you may set in the
Exim 'configure' file, in the 'local_scan' section.
expy_enabled
Type: boolean
Default: true
Gives you a way to cause this software to not try and execute
any Python code, if you needed to disable it for some reason.
For example:
expy_enabled = false
expy_exim_module
Type: string
Default: exim
Specifies the name of the Python module that this software creates
to hold the builtin Exim functions, constants, and variables
described below.
expy_path_add
Type: string
Default: unset
Append one directory name to the Python sys.path list,
useful for specifying where your local_scan module resides.
For example:
expy_path_add = /foo/bar/mystuff
If this variable isn't set, you'll have to place your module
somewhere in the default Python path, such as the
site-packages directory.
expy_scan_module
Type: string
Default: exim_local_scan
Name of the Python module you're supplying that contains
a local_scan function.
expy_scan_function
Type: string
Default: local_scan
Name of the function within your module that this
software will try and execute to perform the local_scan.
expy_scan_failure
Type: string
Default: defer
Return code in case the local_scan functions fails. Possible values:
"accept", "defer", "deny".
expy_path_add is probably the only one you'll really need. The others
are handy if you don't care for their default values.
----------
RUNNING
----------
For every message that comes in, under the default Exim local_scan
configuration settings described above, Exim will now call on embedded
Python to import a module named 'exim_local_scan', and run a function
in that module named 'local_scan'.
Here is a sample "hello world" local scan module for Python:
------------
import exim
def local_scan():
exim.log('Hello from Python')
return exim.LOCAL_SCAN_ACCEPT
------------
If this works, you'll find a "Hello from Python" line added to each
message entry in your Exim mainlog.
------------------------------------
WRITING A PYTHON LOCAL_SCAN FUNCTION
------------------------------------
Your "local_scan" function is called without any arguments, and should
return one of the LOCAL_SCAN_* constants (see below). If you want to specify
some return_text, you may do so by returning a tuple containing the
LOCAL_SCAN_* constant, along with a string. For example:
def local_scan():
return exim.LOCAL_SCAN_TEMPREJECT, 'Come back later'
Several Exim functions, constants, and variables are available through
a module named 'exim' (that name is set by the expy_exim_module setting
described above), which user-supplied modules will want to import
(as in the "hello world" example above). (Most of these descriptions
are basically copied from the Exim Local Scan documentation chapter 38, but
altered for Python)
Functions
----------
add_header(string):
Adds a header to the message being scanned. A newline
is automatically added if necessary. Example:
exim.add_header('X-Python: scanned')
Please note that a header that's been folded into multiple
lines of text must be added with a single function call.
For example, instead of:
exim.add_header('x-foo: bar')
exim.add_header(' baz')
you'd need to use:
exim.add_header('x-foo: bar\n baz')
debug_print(string):
If this function is called when Exim is not in debugging mode, it
does nothing. In debugging mode, it adds to the debugging output.
If the "pid" and/or "timestamp" debugging selectors are set, the pid
and/or timestamp are automatically added to each debug output line.
Newlines are NOT added automatically.
exim.debug_print('some debug message\n')
expand(string):
Perform an Exim string-expansion. For example:
spooldir = exim.expand('$spool_directory')
If the expansion fails, a ValueError exception is raised and
the exception's error message includes the string Exim returns
in C through expand_string_message.
log(string [, which=LOG_MAIN]):
Add a string to the specified log (defaults to LOG_MAIN).
For example:
exim.log('Scanned by Python')
exim.log('Rejected by Python', exim.LOG_REJECT)
exim.log("We're freaking out here!', exim.LOG_PANIC)
child_open(argv, envp, umask[, make_leader=False]):
Create a child process that runs the command specified.
"argv" and "envp" must be tuples of strings.
The return value is (stdout, stdin, pid), where stdout and stdin are
file descriptors to the appropriate pipes (stderr is joined with
stdout). The environment may be specified, and a new umask supplied.
child_open_exim(message[, sender="", sender_authentication=None])
Submit a new message to Exim, returning the PID of the subprocess.
Essentially, this is running 'exim -t -oem -oi -f sender -oMas auth'
(-oMas is omitted if no authentication is provided).
child_close(pid[, timeout=0])
Wait for a child process to terminate, or for a timeout (in seconds)
to expire. A timeout of zero (the default) means wait as long as it
takes. The return value is the process ending status.
Constants
----------
(Python doesn't really have constants, you can assign other values to
these names if you really want to confuse yourself)
LOG_MAIN
LOG_PANIC
LOG_REJECTLOG
Used as the second argument to the log() function to specify
which Exim log you want to write to. Example:
exim.log('Python was here', exim.LOG_MAIN)
LOCAL_SCAN_ACCEPT
LOCAL_SCAN_ACCEPT_FREEZE
LOCAL_SCAN_ACCEPT_QUEUE
LOCAL_SCAN_REJECT
LOCAL_SCAN_REJECT_NOLOGHDR
LOCAL_SCAN_TEMPREJECT
LOCAL_SCAN_TEMPREJECT_NOLOGHDR
Used one of these for the return value of your local scan function
D_v
D_local_scan
Bitmasks for checking the debug_selector variable (see below)
MESSAGE_ID_LENGTH
The length of message identification strings. This is the id used internally
by exim. The external version for use in Received: strings has a leading 'E'
added to ensure it starts with a letter.
SPOOL_DATA_START_OFFSET
The offset to the start of the data in the data file - this allows for
the name of the data file to be present in the first line.
Variables
----------
debug_selector (an integer)
zero when no debugging is taking place. Otherwise, it is a bitmap
of debugging selectors. Two bits are identified for use in local_scan()
and defined as constants (see above):
The D_v bit is set when -v was present on the command line. This is a
testing option that is not privileged - any caller may set it. All the
other selector bits can be set only by admin users.
The D_local_scan bit is provided for use by local_scan(); it is set by
the +local_scan debug selector. It is not included in the default set
of debugging bits.
fd (an integer)
The file descriptor for the file that contains the body of the
message (the -D file). The descriptor is positioned at the first
character of the body itself, having skipped over the beginning
of the file which contains the message_id or the name of the data
file in the first line.
The file is open for reading and writing, but updating it
is not recommended.
Here is an example snippet of code that copies the body of the
message being scanned to a file named 'my_body':
import os
f = open('my_body', 'w')
while 1:
s = os.read(exim.fd, 16384)
if not s:
break
f.write(s)
sender_address (a string)
The envelope sender address. For bounce messages this is
the empty string.
headers
A tuple of header_line objects. Each header_line object has two
attributes: '.text', and '.type'.
The .text attribute is the entire text of the header line, which
may contain internal newlines, and should end in a newline. It is
not changable.
The .type attribute is the one-character code Exim uses to identify
certain header lines (see chapter 48 of Exim manual). It is changable,
but only to single-character values. Normally you'd set it to '*' to
mark a header line as being deleted.
Here's an example bit of code that deletes headers beginning with 'x-spam':
for h in exim.headers:
if h.type != '*' and h.text.lower().startswith('x-spam'):
h.type = '*'
Use the add_header() function (see above) to add new header lines, although
you won't see them appear in this tuple.
host_checking (an integer)
true during a -bh session
interface_address (a string)
The IP address of the interface that received the message, as
a string. This is None for locally submitted messages.
interface_port (an integer)
The port on which this message was received.
message_id (a string)
Message id of the message we're scanning
received_protocol (a string)
The name of the protocol by which the message was received.
recipients
The list of accepted recipients. You may modify or replace
this list. To blackhole a message you can use any of these
methods:
exim.recipients = None
exim.recipients = []
del exim.recipients
You could append a new address with:
exim.recipients.append('[email protected]')
Or replace the list alltogether with:
exim.recipients = ['[email protected]', '[email protected]']
sender_host_address (a string)
The IP address of the sending host, as a string. This is None for
locally-submitted messages.
sender_host_authenticated (a string)
The name of the authentication mechanism that was used, or None if
the message was not received over an authenticated SMTP connection.
sender_host_name (a string)
The name of the sending host, if known.
sender_host_port (an integer)
The port on the sending host.
Please note that the 'recipients' variable is the only one for which
modifications have any effect on Exim.
------------------------
MORE ELABORATE EXAMPLES
------------------------
Here is a fancier local_scan function, that calls a
separate 'scanner' module and catches any exceptions
raised and logs them in the Exim rejectlog
-----------------------------------
import sys
import exim
# Wrap up the real scanning function in a tight
# try-except block, dump any raised python
# exceptions into the paniclog, and temporarily
# reject the message
#
def local_scan():
try:
import scanner
return scanner.local_scan()
except:
pass
#
# Get the exception and traceback, format and convert
# to individual lines, feed each line into exim log
#
import traceback
einfo = sys.exc_info()
lines = traceback.format_exception(einfo[0], einfo[1], einfo[2])
lines = ''.join(lines).split('\n')
for line in lines:
exim.log(line, exim.LOG_PANIC)
return exim.LOCAL_SCAN_TEMPREJECT
------------------------------------
Another useful bit of code for writing out a complete copy of a message
to a given filename (perhaps to feed into antivirus or spam-recognition
software).
-----------
def copy_message(msg_filename):
f = open(msg_filename, 'w')
for h in exim.headers:
if h.type != '*':
f.write(h.text)
f.write('\n')
while 1:
s = os.read(exim.fd, 16384)
if not s:
break
f.write(s)
f.close()
--------------------
### EOF ###