]>
Commit | Line | Data |
---|---|---|
ac0f354f AM |
1 | /** |
2 | * kSpam plugin for Exim Local Scan. | |
3 | * Copyright (C) 2005 James Kibblewhite <kibble@aproxity.com> | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or | |
6 | * modify it under the terms of the GNU General Public License | |
7 | * as published by the Free Software Foundation; either version 2 | |
8 | * of the License, or (at your option) any later version. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU General Public License | |
16 | * along with this program; if not, write to the Free Software | |
17 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
18 | * | |
19 | * ----------------------------------------------------------------------------- | |
20 | * $Id$ | |
21 | * ----------------------------------------------------------------------------- | |
22 | * | |
23 | * This local_scan.c file compiles in with exim4. | |
24 | * Exim: http://www.exim.org/ | |
25 | * MySql: http://www.mysql.com/ | |
26 | * ClamAV: http://www.clamav.org/ | |
27 | * DSpam: http://www.nuclearelephant.com/projects/dspam/ | |
28 | * | |
29 | * Changes: | |
30 | * Version 0.8: Bug fixes further improved. No crashes can be replicated. Futher testing | |
31 | * required. Removed debugging exim_mainlog output. Will try and get user | |
32 | * rules implemented... | |
33 | * | |
34 | * Version 0.7: Bugs all fixed, seems to be a fully working system, need to test | |
35 | * all features before full public release can be made... | |
36 | * Will remove all debugging output in 0.9... public release v1.0 | |
37 | * | |
38 | * Version 0.6: Improved email validation. Added `cleanitup` as a cleanup routeen | |
39 | * ClamAV lib updated [now 0.8x & above required as min requirement] | |
40 | * Bleeding Edge CVS of dspam is also required | |
41 | * | |
42 | * Version 0.5: Started ruleset loading support from database. | |
43 | * | |
44 | * Version 0.4: Got DSpam working and aliases sorted. First beta release. | |
45 | * | |
46 | * Version 0.3: Included a config file reader to take variables from the main configuration file. | |
47 | * Also integrated the last of MySQL support and tidy duties on the code done. | |
48 | * | |
49 | * Version 0.2: Fixes to scan mbox style flatfiles with compressed files... | |
50 | * thanks to ClamAV for: CL_ARCHIVE & CL_MAIL | |
51 | * you've saved me the bother of extracting the mime base64 encoded stuff !! yay | |
52 | * | |
53 | * Version 0.1: For personal testing. | |
54 | * | |
55 | * Ideas: Can block email totally if probability & confidence is very high [like +95% (=>0.95)] | |
56 | * | |
57 | * Known bugs: [X] This is no longer a bug, all bugs fixed, testing is required... | |
58 | * | |
59 | * If you submit 'many' new emails all at once it has a tendancy to die but tell | |
60 | * you that it has failed... Maybe by controlling the flow of emails will help | |
61 | * [although will slow thru-put]. Will crash at 'mysql_real_connect()' in 'mysql_setup()' | |
62 | * while trying to establish a connection on a second pass... [This is | |
63 | * | |
64 | * Donations: To paypal: jelly_bean_junky@hotmail.com 'if' you use this code commercially, | |
65 | * ask your boss for it! And for a pay rise while your at it too... Otherwise I don't | |
66 | * expect anything, unless you wanna send some cool stuff to me... | |
67 | * | |
68 | * Notes: This line may need appending to the end of the local_scan.o line: | |
69 | * -I/usr/include/mysql -I/usr/include/dspam -DHAVE_CONFIG_H -DCONFIG_DEFAULT=/etc/sysconf.d/dspam.conf | |
70 | */ | |
71 | ||
72 | /** include required by exim */ | |
73 | #include "local_scan.h" | |
74 | ||
75 | /** include required by clamav for antivirus checking */ | |
76 | #include "clamav.h" | |
77 | ||
78 | /** include required by mysql for access to the database */ | |
79 | #include "mysql.h" | |
80 | ||
81 | /** include required by dspam for spam filtering */ | |
82 | #include "libdspam.h" | |
83 | ||
84 | /** the usual suspects */ | |
85 | #include <stdio.h> | |
86 | #include <stdlib.h> | |
87 | #include <unistd.h> | |
88 | #include <signal.h> | |
89 | #include <string.h> | |
90 | #include <fcntl.h> | |
91 | ||
92 | #define SPAMREPT 2 | |
93 | #define FALSEPOS 4 | |
94 | #define SPAMFLAG 8 | |
95 | #define TOGBLACK 16 | |
96 | #define TOGWHITE 32 | |
97 | #define SMBYTE 64 | |
98 | #define EMBYTE 128 | |
99 | #define QMBYTE 256 | |
100 | #define HMBYTE 512 | |
101 | #define WMBYTE 1024 | |
102 | #define BUFFER_SIZE 2048 | |
103 | ||
104 | /** blocks hosts & ips & emails & headers */ | |
105 | typedef struct bhosts_s { /** _lscan._lusers_s.bhosts->next */ | |
106 | struct bhosts_s * next; | |
107 | char * sender_hostname; | |
108 | char * sender_ipaddr; | |
109 | char * sender_logics; /** OR | AND -> hostname - ipaddr */ | |
110 | char * email; | |
111 | char * email_mtype; /** contains | exact match */ | |
112 | char * header_field; | |
113 | char * header_value; | |
114 | char * header_mtype; /** contains | exact match -> header_value if header_value == NULL || "" use header_field */ | |
115 | char * logics; /** OR | AND -> all values */ | |
116 | } _bhosts_s; | |
117 | ||
118 | /** linked list of local users requiring filtering */ | |
119 | typedef struct lusers_s { /** _lscan._lusers_s.username */ | |
120 | struct lusers_s * next; | |
121 | _bhosts_s * bhosts; | |
122 | int mailuser_id; /** refers to database id */ | |
123 | int enabled; /** is filtering enabled... */ | |
124 | char rcptname[EMBYTE]; /** rcpt name as appears in recipients_list */ | |
125 | char realemail[EMBYTE]; /** if rcptname is an alias, this will be the real email | |
126 | for loading dpsma rules with, else set the same as rcptname */ | |
127 | } _lusers_s; | |
128 | ||
129 | typedef struct email_struct { | |
130 | char localpart[SMBYTE]; | |
131 | char domain[SMBYTE]; | |
132 | } _email_struct; | |
133 | ||
134 | /** varaibles of mass instructions */ | |
135 | typedef struct lscan_structure { | |
136 | MYSQL * mysql; | |
137 | MYSQL_RES * result; | |
138 | MYSQL_ROW row; | |
139 | _lusers_s * l_users; | |
140 | _email_struct lpart_domain; | |
141 | struct cl_limits limits; | |
142 | struct cl_node * root; | |
143 | header_line * hl_ptr; | |
144 | char * virname; | |
145 | char emailaddy[HMBYTE]; | |
146 | char querystr[BUFFER_SIZE]; | |
147 | char buffer[BUFFER_SIZE]; | |
148 | char scanpath[BUFFER_SIZE]; | |
149 | int i; | |
150 | int iNo; | |
151 | int spamflag; | |
152 | int writefd; | |
153 | } _lscan; | |
154 | ||
155 | _lscan lscan; | |
156 | ||
157 | /** | |
158 | * Remember to set LOCAL_SCAN_HAS_OPTIONS=yes in Local/Makefile | |
159 | * otherwise you get stuck with the compile-time defaults | |
160 | */ | |
161 | /** Al our variables we draw in from the 'exim-localscan.conf' file */ | |
162 | static uschar * database = US"socket_aproxity"; | |
163 | static uschar * hostname = US"localhost"; | |
164 | static uschar * password = US"password"; | |
165 | static uschar * poolpath = US"/home/mail/spool"; | |
166 | static uschar * spamflag = US"X-KD-Spam"; | |
167 | static uschar * username = US"mail"; | |
168 | ||
169 | optionlist local_scan_options[] = { /** alphabetical order */ | |
170 | { "database", opt_stringptr, &database }, | |
171 | { "hostname", opt_stringptr, &hostname }, | |
172 | { "password", opt_stringptr, &password }, | |
173 | { "poolpath", opt_stringptr, &poolpath }, | |
174 | { "spamflag", opt_stringptr, &spamflag }, | |
175 | { "username", opt_stringptr, &username } | |
176 | }; | |
177 | ||
178 | int local_scan_options_count = sizeof(local_scan_options) / sizeof(optionlist); | |
179 | ||
180 | #ifdef DLOPEN_LOCAL_SCAN | |
181 | /** Return the verion of the local_scan ABI, if being compiled as a .so */ | |
182 | int local_scan_version_major(void) { | |
183 | return(LOCAL_SCAN_ABI_VERSION_MAJOR); | |
184 | } | |
185 | ||
186 | int local_scan_version_minor(void) { | |
187 | return(LOCAL_SCAN_ABI_VERSION_MINOR); | |
188 | } | |
189 | ||
190 | /** | |
191 | * Left over for compatilibility with old patched exims that didn't have | |
192 | * a version number with minor an major. Keep in mind that it will not work | |
193 | * with older exim4s (I think 4.11 and above is required) | |
194 | */ | |
195 | ||
196 | #ifdef DLOPEN_LOCAL_SCAN_OLD_API | |
197 | int local_scan_version(void) { | |
198 | return(1); | |
199 | } | |
200 | #endif | |
201 | #endif | |
202 | ||
203 | /** delete our cached file */ | |
204 | void del_cachef() { | |
205 | if (unlink(lscan.scanpath)) { | |
206 | debug_printf("file [%s] not removed", lscan.scanpath); | |
207 | } | |
208 | return; | |
209 | } /** del_cachef */ | |
210 | ||
211 | /** | |
212 | * Scan email for virus. Returns 1 if virus | |
213 | * detected or 0 if no virus is detected. Sets | |
214 | * lscan.virname to virua or error output... | |
215 | */ | |
216 | int scan_clamav(char * scanpath) { | |
217 | ||
218 | sprintf(lscan.scanpath, "%s", scanpath); | |
219 | lscan.iNo = 0; | |
220 | ||
221 | /** lets load all our virus defs database's into memory */ | |
222 | lscan.root = NULL; /** without this line, the dbload will crash... */ | |
223 | if((lscan.i = cl_loaddbdir(cl_retdbdir(), &lscan.root, &lscan.iNo))) { | |
224 | sprintf(lscan.virname, "error: [%s]", cl_perror(lscan.i)); | |
225 | } else { | |
226 | if((lscan.i = cl_build(lscan.root))) { | |
227 | sprintf(lscan.virname, "database initialization error: [%s]", cl_perror(lscan.i)); | |
228 | cl_free(lscan.root); | |
229 | } | |
230 | memset(&lscan.limits, 0x0, sizeof(struct cl_limits)); | |
231 | lscan.limits.maxfiles = 1000; /** max files */ | |
232 | lscan.limits.maxfilesize = 10 * 1048576; /** maximal archived file size == 10 Mb */ | |
233 | lscan.limits.maxreclevel = 12; /** maximal recursion level */ | |
234 | lscan.limits.maxratio = 200; /** maximal compression ratio */ | |
235 | lscan.limits.archivememlim = 0; /** disable memory limit for bzip2 scanner */ | |
236 | ||
237 | if ((lscan.i = cl_scanfile(lscan.scanpath, (const char **)&lscan.virname, NULL, lscan.root, | |
238 | &lscan.limits, CL_SCAN_ARCHIVE | CL_SCAN_MAIL | CL_SCAN_OLE2 | CL_SCAN_BLOCKBROKEN | CL_SCAN_HTML | CL_SCAN_PE)) != CL_VIRUS) { | |
239 | if (lscan.i != CL_CLEAN) { | |
240 | sprintf(lscan.virname, "error: [%s]", cl_perror(lscan.i)); | |
241 | } else { | |
242 | lscan.virname = NULL; | |
243 | } | |
244 | } | |
245 | if (lscan.root != NULL) { | |
246 | cl_free(lscan.root); | |
247 | } | |
248 | memset(&lscan.limits, 0x0, sizeof(struct cl_limits)); | |
249 | } | |
250 | ||
251 | /** lets delete the spool message as we don't need it any more */ | |
252 | if (lscan.virname != NULL) { /** remove the file if we have a virus as we are going to reject it */ | |
253 | return(1); | |
254 | } else { /** else keep the file for spam filtering */ | |
255 | return(0); | |
256 | } | |
257 | ||
258 | return(1); | |
259 | ||
260 | } /** scan_clamav */ | |
261 | ||
262 | void cache_mesg(int fd) { | |
263 | ||
264 | fd = fd; | |
265 | ||
266 | sprintf(lscan.scanpath, "%s/%s", poolpath, message_id); | |
267 | ||
268 | /** create the file handler */ | |
269 | lscan.writefd = creat(lscan.scanpath, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); | |
270 | ||
271 | /** lets make this thing look like an email mbox structured thing or clamav won't work !! */ | |
272 | memset(lscan.buffer, 0x0, BUFFER_SIZE); | |
273 | sprintf(lscan.buffer, "From %s Mon Jan 00 00:00:00 0000\n", sender_address); | |
274 | lscan.i = write(lscan.writefd, lscan.buffer, strlen(lscan.buffer)); | |
275 | ||
276 | lscan.hl_ptr = header_list; | |
277 | while (lscan.hl_ptr != NULL) { | |
278 | /** type '*' means the header is internal, don't print it, or if the variable is NULL, what's the point...? */ | |
279 | if ((lscan.hl_ptr->type != '*') || (lscan.hl_ptr->text != NULL)) { | |
280 | lscan.i = write(lscan.writefd, lscan.hl_ptr->text, strlen(lscan.hl_ptr->text)); | |
281 | } | |
282 | lscan.hl_ptr = lscan.hl_ptr->next; | |
283 | } | |
284 | ||
285 | memset(lscan.buffer, 0x0, BUFFER_SIZE); | |
286 | sprintf(lscan.buffer, "\n"); | |
287 | lscan.i = write(lscan.writefd, lscan.buffer, strlen(lscan.buffer)); | |
288 | ||
289 | /** output all the data, read from orignal and write to spool */ | |
290 | while ((lscan.i = read(fd, lscan.buffer, BUFFER_SIZE)) > 0) { | |
291 | lscan.i = write(lscan.writefd, lscan.buffer, lscan.i); | |
292 | } | |
293 | ||
294 | /** close the handle */ | |
295 | lscan.i = close(lscan.writefd); | |
296 | debug_printf("path of cached file [%s]", lscan.scanpath); | |
297 | ||
298 | return; | |
299 | ||
300 | } /** cache_mesg */ | |
301 | ||
302 | void remove_headers(char * hfield) { | |
303 | ||
304 | lscan.hl_ptr = header_list; | |
305 | while (lscan.hl_ptr != NULL) { | |
306 | if ( ((lscan.hl_ptr->type != '*')) && (!strncmp(lscan.hl_ptr->text, hfield, strlen(hfield))) ) { | |
307 | lscan.hl_ptr->type = '*'; | |
308 | } | |
309 | lscan.hl_ptr = (struct header_line *)lscan.hl_ptr->next; | |
310 | } | |
311 | } /** remove_headers */ | |
312 | ||
313 | /** | |
314 | * If the supplied email address is syntactically valid, | |
315 | * spc_email_isvalid() will return 1; otherwise, it will | |
316 | * return 0. Need to check that there is at least one '@' | |
317 | * symbol and only one in the whole email address, else | |
318 | * `getlocalp_domain` function won't work correctly... | |
319 | */ | |
320 | int spc_email_isvalid(const char *address) { | |
321 | ||
322 | int count = 0; | |
323 | const char *c, *domain; | |
324 | static char *rfc822_specials = "()<>@,;:\\\"[]/"; | |
325 | ||
326 | /** first we validate the name portion (name@domain) */ | |
327 | for (c = address; *c; c++) { | |
328 | if ((*c == '\"') && (c == address || *(c - 1) == '.' || *(c - 1) == '\"')) { | |
329 | while (*++c) { | |
330 | if (*c == '\"') { | |
331 | break; | |
332 | } | |
333 | if ((*c == '\\') && (*++c == ' ')) { | |
334 | continue; | |
335 | } | |
336 | if (*c < ' ' || *c >= 127) { | |
337 | return(0); | |
338 | } | |
339 | } | |
340 | if (!*c++) { | |
341 | return(0); | |
342 | } | |
343 | if (*c == '@') { | |
344 | break; | |
345 | } | |
346 | if (*c != '.') { | |
347 | return(0); | |
348 | } | |
349 | continue; | |
350 | } | |
351 | if (*c == '@') { | |
352 | break; | |
353 | } | |
354 | if (*c <= ' ' || *c >= 127) { | |
355 | return(0); | |
356 | } | |
357 | if (strchr(rfc822_specials, *c)) { | |
358 | return(0); | |
359 | } | |
360 | } | |
361 | if (c == address || *(c - 1) == '.') { | |
362 | return(0); | |
363 | } | |
364 | ||
365 | /** next we validate the domain portion (name@domain) */ | |
366 | if (!*(domain = ++c)) { | |
367 | return(0); | |
368 | } | |
369 | ||
370 | do { | |
371 | if (*c == '.') { | |
372 | if (c == domain || *(c - 1) == '.') { | |
373 | return(0); | |
374 | } | |
375 | count++; | |
376 | } | |
377 | if (*c <= ' ' || *c >= 127) { | |
378 | return(0); | |
379 | } | |
380 | if (strchr(rfc822_specials, *c)) { | |
381 | return(0); | |
382 | } | |
383 | } while (*++c); | |
384 | return(count >= 1); | |
385 | } /** spc_email_isvalid */ | |
386 | ||
387 | /** this function returns the localpart and the domain section of an email in any given string */ | |
388 | _email_struct getlocalp_domain(char * emailaddr, _email_struct lpart_domain) { | |
389 | ||
390 | memset(lscan.emailaddy, 0x0, HMBYTE); | |
391 | memset(lpart_domain.localpart, 0x0, SMBYTE); | |
392 | memset(lpart_domain.domain, 0x0, SMBYTE); | |
393 | sprintf(lscan.emailaddy, "%s", emailaddr); | |
394 | ||
395 | if (spc_email_isvalid(lscan.emailaddy)) { | |
396 | sprintf(lpart_domain.localpart, "%s", strtok(lscan.emailaddy, "@")); | |
397 | sprintf(lpart_domain.domain, "%s", strtok(NULL, "@")); | |
398 | } | |
399 | return(lpart_domain); | |
400 | } /** getlocalp_domain */ | |
401 | ||
402 | /** mysql results and rows cleanup routine */ | |
403 | void mysqlrr_cleanup() { | |
404 | ||
405 | lscan.row = NULL; | |
406 | ||
407 | if (lscan.result != NULL) { | |
408 | mysql_free_result(lscan.result); | |
409 | lscan.result = NULL; | |
410 | } | |
411 | ||
412 | } /** mysqlrr_cleanup */ | |
413 | ||
414 | /** mysql results and rows cleanup routine */ | |
415 | void mysql_cleanup() { | |
416 | ||
417 | mysqlrr_cleanup(); | |
418 | ||
419 | mysql_close(lscan.mysql); | |
420 | memset(&lscan.mysql, 0x0, sizeof(lscan.mysql)); | |
421 | free(lscan.mysql); | |
422 | ||
423 | } /** mysql_cleanup */ | |
424 | ||
425 | int mysql_setup() { | |
426 | ||
427 | mysql_cleanup(); | |
428 | ||
429 | if (!(lscan.mysql = mysql_init(NULL))) { | |
430 | log_write(0, LOG_MAIN, "mysql_init [%s]", mysql_error(lscan.mysql)); | |
431 | return(1); | |
432 | } | |
433 | ||
434 | /** we are always connecting to localhost!! to slow otherwise... */ | |
435 | if (!mysql_real_connect(lscan.mysql, hostname, username, password, database, 0, NULL, 0)) { | |
436 | log_write(0, LOG_MAIN, "mysql_real_connect [%s]", mysql_error(lscan.mysql)); | |
437 | mysql_close(lscan.mysql); | |
438 | return(1); | |
439 | } | |
440 | ||
441 | if (mysql_select_db(lscan.mysql, database)) { | |
442 | log_write(0, LOG_MAIN, "mysql_select_db [%s]", mysql_error(lscan.mysql)); | |
443 | mysql_close(lscan.mysql); | |
444 | return(1); | |
445 | } | |
446 | ||
447 | return(0); | |
448 | ||
449 | } /** mysql_setup */ | |
450 | ||
451 | /** | |
452 | * Instead of returning a row of data, I've decided to return | |
453 | * the results to obtain the rows, incase I need more than one | |
454 | * set of rows from the results. This basically runs the current | |
455 | * sql query in lscan.querystr. | |
456 | */ | |
457 | MYSQL_RES * get_mysqlres() { | |
458 | ||
459 | /** clean up result and row if required */ | |
460 | mysqlrr_cleanup(); | |
461 | ||
462 | debug_printf("running query:\n\t[%s]\n", lscan.querystr); | |
463 | ||
464 | if (mysql_real_query(lscan.mysql, lscan.querystr, strlen(lscan.querystr))) { | |
465 | log_write(0, LOG_MAIN, "mysql_real_query [%s]", mysql_error(lscan.mysql)); | |
466 | return((MYSQL_RES * )NULL); | |
467 | } | |
468 | ||
469 | if (!(lscan.result = mysql_store_result(lscan.mysql))) { | |
470 | log_write(0, LOG_MAIN, "mysql_store_result [%s]", mysql_error(lscan.mysql)); | |
471 | return((MYSQL_RES * )NULL); | |
472 | } | |
473 | ||
474 | if (mysql_num_rows(lscan.result) != 0) { | |
475 | return(lscan.result); | |
476 | } | |
477 | ||
478 | return((MYSQL_RES * )NULL); | |
479 | } /** get_mysqlres */ | |
480 | ||
481 | /** add user and rulesets */ | |
482 | _lusers_s * add_userset(_lusers_s * l_users, int mailuser_id, int enabled, char * rcptname, _email_struct lpart_domain) { | |
483 | ||
484 | _lusers_s * lp = l_users; | |
485 | ||
486 | if (enabled == 0) { | |
487 | return(l_users); | |
488 | } | |
489 | ||
490 | /** remove any duplicates of users in linked list... */ | |
491 | ||
492 | if (l_users != NULL) { | |
493 | while (l_users->next != NULL) { | |
494 | l_users = (_lusers_s *)l_users->next; | |
495 | } | |
496 | l_users->next = (struct lusers_s *)malloc(sizeof(_lusers_s)); | |
497 | l_users = (_lusers_s *)l_users->next; | |
498 | ||
499 | l_users->mailuser_id = mailuser_id; | |
500 | l_users->enabled = enabled; | |
501 | memset(l_users->rcptname, 0x0, EMBYTE); | |
502 | memset(l_users->realemail, 0x0, EMBYTE); | |
503 | sprintf(l_users->rcptname, "%s", rcptname); | |
504 | sprintf(l_users->realemail, "%s@%s", (char *)lpart_domain.localpart, (char *)lpart_domain.domain); | |
505 | ||
506 | l_users->next = NULL; | |
507 | l_users = lp; | |
508 | } else { | |
509 | l_users = (_lusers_s *)(struct lusers_s *)malloc(sizeof(_lusers_s)); | |
510 | ||
511 | l_users->mailuser_id = mailuser_id; | |
512 | l_users->enabled = enabled; | |
513 | memset(l_users->rcptname, 0x0, EMBYTE); | |
514 | memset(l_users->realemail, 0x0, EMBYTE); | |
515 | sprintf(l_users->rcptname, "%s", rcptname); | |
516 | sprintf(l_users->realemail, "%s@%s", (char *)lpart_domain.localpart, (char *)lpart_domain.domain); | |
517 | ||
518 | l_users->next = NULL; | |
519 | l_users = l_users; | |
520 | } | |
521 | ||
522 | /** we should now do a look up for the rules and add them to 'l_users->bhosts' */ | |
523 | ||
524 | return(l_users); | |
525 | } | |
526 | ||
527 | int load_realuser(char * emailaddr) { | |
528 | ||
529 | lscan.lpart_domain = getlocalp_domain(emailaddr, lscan.lpart_domain); /** lscan.lpart_domain.localpart && lscan.lpart_domain.domain */ | |
530 | memset(lscan.querystr, 0x0, BUFFER_SIZE); | |
531 | sprintf(lscan.querystr, "SELECT mailuser_id, enabled FROM mail_mailusers WHERE local_part = '%s' AND domain = '%s'", lscan.lpart_domain.localpart, lscan.lpart_domain.domain); | |
532 | ||
533 | if (get_mysqlres()) { /** sets lscan.result to results returned from db */ | |
534 | if (!(lscan.row = mysql_fetch_row(lscan.result))) { /** lscan.row[0] */ | |
535 | log_write(0, LOG_MAIN, "mysql_fetch_row [%s]", mysql_error(lscan.mysql)); | |
536 | return(1); | |
537 | } | |
538 | } else { | |
539 | return(1); | |
540 | } | |
541 | ||
542 | /** add user to results & rules... */ | |
543 | if ((int)atoi(lscan.row[1]) != 0) { | |
544 | log_write(0, LOG_MAIN, "adding user ..."); | |
545 | lscan.l_users = add_userset(lscan.l_users, (int)atoi(lscan.row[0]), (int)atoi(lscan.row[1]), (char *)recipients_list[lscan.i].address, lscan.lpart_domain); | |
546 | } | |
547 | ||
548 | return(0); | |
549 | } /** load_realuser */ | |
550 | ||
551 | int load_aliases(char * emailaddr) { | |
552 | ||
553 | lscan.lpart_domain = getlocalp_domain(emailaddr, lscan.lpart_domain); /** lscan.lpart_domain.localpart && lscan.lpart_domain.domain */ | |
554 | memset(lscan.querystr, 0x0, BUFFER_SIZE); | |
555 | sprintf(lscan.querystr, "SELECT alias FROM mail_aliases WHERE local_part = '%s' AND domain = '%s'", lscan.lpart_domain.localpart, lscan.lpart_domain.domain); | |
556 | ||
557 | if (get_mysqlres()) { /** sets lscan.result to results returned from db */ | |
558 | if (!(lscan.row = mysql_fetch_row(lscan.result))) { /** lscan.row[0] */ | |
559 | log_write(0, LOG_MAIN, "mysql_fetch_row [%s]", mysql_error(lscan.mysql)); | |
560 | return(1); | |
561 | } | |
562 | if (load_realuser((char *)lscan.row[0])) { /** failed to load alias as real user */ | |
563 | return(1); | |
564 | } | |
565 | } else { | |
566 | /** check for wildcard localpart */ | |
567 | memset(lscan.querystr, 0x0, BUFFER_SIZE); | |
568 | sprintf(lscan.querystr, "SELECT alias FROM mail_aliases WHERE local_part = '*' AND domain = '%s'", lscan.lpart_domain.domain); | |
569 | ||
570 | if (get_mysqlres()) { /** sets lscan.result to results returned from db */ | |
571 | if (!(lscan.row = mysql_fetch_row(lscan.result))) { /** lscan.row[0] */ | |
572 | log_write(0, LOG_MAIN, "mysql_fetch_row [%s]", mysql_error(lscan.mysql)); | |
573 | return(1); | |
574 | } | |
575 | if (load_realuser((char *)lscan.row[0])) { /** failed to load alias as real user */ | |
576 | return(1); | |
577 | } | |
578 | } | |
579 | } | |
580 | ||
581 | return(0); | |
582 | } /** load_aliases */ | |
583 | ||
584 | /** this loads all users settings into memory */ | |
585 | int init_users() { | |
586 | ||
587 | if (mysql_setup()) { | |
588 | return(1); | |
589 | } | |
590 | ||
591 | for (lscan.i = 0; lscan.i != recipients_count; lscan.i++) { | |
592 | if (load_realuser(recipients_list[lscan.i].address)) { | |
593 | debug_printf("cound not find user [%s]", recipients_list[lscan.i].address); | |
594 | if (load_aliases(recipients_list[lscan.i].address)) { | |
595 | debug_printf("alias [%s] not found check your sql schema", recipients_list[lscan.i].address); | |
596 | } | |
597 | } | |
598 | } | |
599 | ||
600 | mysql_cleanup(); | |
601 | ||
602 | return(0); | |
603 | } /** init_users */ | |
604 | ||
605 | char * read_emailmem(char * message) { | |
606 | ||
607 | long len = 1; | |
608 | long oCount = 0; | |
609 | int fd = 0; | |
610 | ||
611 | memset(lscan.buffer, 0x0, BUFFER_SIZE); | |
612 | fd = open(lscan.scanpath, O_RDONLY); | |
613 | /* read in the message from stdin */ | |
614 | message[0] = 0; | |
615 | while ((oCount = read(fd, lscan.buffer, sizeof(lscan.buffer))) > 0) { | |
616 | len += strlen(lscan.buffer); | |
617 | message = realloc(message, len); | |
618 | strcat(message, lscan.buffer); | |
619 | } | |
620 | close(fd); | |
621 | ||
622 | return(message); | |
623 | } /** read_emailmem */ | |
624 | ||
625 | /** | |
626 | * Usage: | |
627 | * CTX = attach_ctx_dbaccess(CTX); | |
628 | * | |
629 | 29903: [3/2/2005 22:34:24] bailing on error 22 | |
630 | 29903: [3/2/2005 22:34:24] received invalid result (! DSR_ISSPAM || DSR_INNOCENT || DSR_ISWHITELISTED): 22 | |
631 | -> happened because read_emailmem() was not returning the message ptr correctly... | |
632 | */ | |
633 | DSPAM_CTX * attach_ctx_dbaccess(DSPAM_CTX * CTX) { | |
634 | ||
635 | if (dspam_clearattributes(CTX)) { | |
636 | log_write(0, LOG_MAIN, "dspam_clearattributes failed!"); | |
637 | } | |
638 | ||
639 | dspam_addattribute(CTX, "MySQLServer", (const char *)hostname); | |
640 | dspam_addattribute(CTX, "MySQLPort", (const char *)"3306"); | |
641 | dspam_addattribute(CTX, "MySQLUser", (const char *)username); | |
642 | dspam_addattribute(CTX, "MySQLPass", (const char *)password); | |
643 | dspam_addattribute(CTX, "MySQLDb", (const char *)database); | |
644 | dspam_addattribute(CTX, "IgnoreHeader", (const char *)spamflag); | |
645 | ||
646 | if (dspam_attach(CTX, (void *)NULL)) { | |
647 | log_write(0, LOG_MAIN, "dspam_attach failed!"); | |
648 | CTX = NULL; | |
649 | } | |
650 | ||
651 | return(CTX); | |
652 | } /** attach_ctx_dbaccess */ | |
653 | ||
654 | void load_usersrs(_lusers_s * l_users) { | |
655 | ||
656 | _lusers_s * tmp = l_users; | |
657 | char * message = malloc(1); | |
658 | DSPAM_CTX * CTX = NULL; /** DSPAM Context */ | |
659 | struct _ds_spam_signature SIG; /** Example signature */ | |
660 | ||
661 | if (tmp == NULL) { | |
662 | memset(message, 0x0, sizeof(message)); | |
663 | free(message); | |
664 | return; | |
665 | } | |
666 | ||
667 | message = read_emailmem(message); | |
668 | ||
669 | while (tmp != NULL) { | |
670 | ||
671 | CTX = dspam_create((char *)tmp->realemail, NULL, NULL, DSM_PROCESS, DSF_CHAINED | DSF_SIGNATURE | DSF_NOISE); | |
672 | CTX = attach_ctx_dbaccess(CTX); | |
673 | if (CTX == NULL) { | |
674 | log_write(0, LOG_MAIN, "dspam_create failed!"); | |
675 | break; | |
676 | } | |
677 | if (dspam_process(CTX, message) != 0) { /** Call DSPAM's processor with the message text */ | |
678 | log_write(0, LOG_MAIN, "dspam_process failed!"); | |
679 | dspam_destroy(CTX); | |
680 | break; | |
681 | } | |
682 | if (CTX->result == DSR_ISSPAM) { /** Print processing results */ | |
683 | log_write(0, LOG_MAIN, "spam->[%s]:\n\tProbability:\t[%2.4f]\n\tConfidence:\t[%2.4f]", | |
684 | (char *)tmp->realemail, CTX->probability, CTX->confidence); | |
685 | header_add(' ', "%s: %s\n", (char *)spamflag, (char *)tmp->realemail); | |
686 | lscan.spamflag = SPAMFLAG; | |
687 | } else { | |
688 | log_write(0, LOG_MAIN, "not spam->[%s]", (char *)tmp->realemail); | |
689 | lscan.spamflag = 0; | |
690 | } | |
691 | ||
692 | if (CTX->signature != NULL) { | |
693 | SIG.data = malloc(CTX->signature->length); | |
694 | if (SIG.data != NULL) { | |
695 | memcpy(SIG.data, CTX->signature->data, CTX->signature->length); | |
696 | } | |
697 | } | |
698 | SIG.length = CTX->signature->length; | |
699 | ||
700 | if (dspam_destroy(CTX) != 0) { /** Destroy the context */ | |
701 | log_write(0, LOG_MAIN, "dspam_destroy failed!"); | |
702 | break; | |
703 | } | |
704 | ||
705 | tmp = (_lusers_s *)tmp->next; /** Move on to next user */ | |
706 | } | |
707 | ||
708 | memset(message, 0x0, sizeof(message)); | |
709 | free(message); | |
710 | memset(&CTX, 0x0, sizeof(CTX)); | |
711 | free(CTX); | |
712 | ||
713 | return; | |
714 | } /** load_usersrs */ | |
715 | ||
716 | int report_spam(int spamflag) { | |
717 | ||
718 | int sflag = 0; | |
719 | char * message = malloc(1); | |
720 | DSPAM_CTX * CTX = NULL; /** DSPAM Context */ | |
721 | struct _ds_spam_signature SIG; /** Example signature */ | |
722 | ||
723 | message = read_emailmem(message); | |
724 | ||
725 | switch (spamflag) { | |
726 | case SPAMREPT: /** set up the context for error correction as spam */ | |
727 | CTX = dspam_create((char *)sender_address, NULL, NULL, DSM_PROCESS, DSF_CHAINED); | |
728 | CTX = attach_ctx_dbaccess(CTX); | |
729 | if (CTX == NULL) { | |
730 | log_write(0, LOG_MAIN, "ERROR: dspam_create failed!\n"); | |
731 | sflag = 1; | |
732 | } | |
733 | CTX->classification = DSR_ISSPAM; | |
734 | CTX->source = DSS_ERROR; | |
735 | break; | |
736 | case FALSEPOS: /** set up the context for error correction as innocent */ | |
737 | CTX = dspam_create((char *)sender_address, NULL, NULL, DSM_PROCESS, DSF_CHAINED | DSF_SIGNATURE); | |
738 | CTX = attach_ctx_dbaccess(CTX); | |
739 | if (CTX == NULL) { | |
740 | log_write(0, LOG_MAIN, "ERROR: dspam_create failed!\n"); | |
741 | sflag = 1; | |
742 | } | |
743 | CTX->classification = DSR_ISINNOCENT; | |
744 | CTX->source = DSS_ERROR; | |
745 | CTX->signature = &SIG; /** Attach the signature to the context */ | |
746 | break; | |
747 | default: /** no reporting required, scan for spam perhaps ? */ | |
748 | log_write(0, LOG_MAIN, "report_spam -> no reporting required"); | |
749 | break; | |
750 | } | |
751 | ||
752 | if (dspam_process(CTX, message) != 0) { /** Call DSPAM */ | |
753 | log_write(0, LOG_MAIN, "ERROR: dspam_process failed!"); | |
754 | sflag = 1; | |
755 | } | |
756 | ||
757 | if (dspam_destroy(CTX) != 0) { /** Destroy the context */ | |
758 | log_write(0, LOG_MAIN, "ERROR: dspam_destroy failed!"); | |
759 | sflag = 1; | |
760 | } | |
761 | ||
762 | memset(message, 0x0, sizeof(message)); | |
763 | free(message); | |
764 | memset(&CTX, 0x0, sizeof(CTX)); | |
765 | free(CTX); | |
766 | ||
767 | if (sflag == 0) { | |
768 | log_write(0, LOG_MAIN, "<= %s [%s] P=%s A=%s:%s", | |
769 | (char *)sender_address, (char *)sender_host_address, (char *)received_protocol, (char *)sender_host_authenticated, (char *)sender_address); | |
770 | log_write(0, LOG_MAIN, "=> (null) <%s> R=system_localuser T=local_delivery", (char *)recipients_list[0].address); | |
771 | } | |
772 | ||
773 | /** if we are reporting, which is pretty much so if you reach here, we blackhole the email */ | |
774 | recipients_count = 0; | |
775 | ||
776 | return(sflag); | |
777 | } /** report_spam */ | |
778 | ||
779 | int check_spamflag(char * subspamdom) { | |
780 | ||
781 | memset(lscan.emailaddy, 0x0, HMBYTE); | |
782 | sprintf(lscan.emailaddy, "%s@%s.%s", lscan.lpart_domain.localpart, subspamdom, lscan.lpart_domain.domain); | |
783 | ||
784 | if (strcmp(lscan.emailaddy, recipients_list[0].address)) { | |
785 | return(0); | |
786 | } | |
787 | ||
788 | return(1); | |
789 | } /** check_spamflag */ | |
790 | ||
791 | int getrept_type() { | |
792 | ||
793 | if (check_spamflag("spamrept")) { | |
794 | return(SPAMREPT); | |
795 | } | |
796 | ||
797 | if (check_spamflag("falsepos")) { | |
798 | return(FALSEPOS); | |
799 | } | |
800 | ||
801 | return(0); | |
802 | } /** getrept_type */ | |
803 | ||
804 | /** | |
805 | * Providing some sort of clean up routeen... | |
806 | */ | |
807 | int cleanitup(int return_flag) { | |
808 | ||
809 | /** log_write(0, LOG_MAIN, "shutting down driver..."); | |
810 | dspam_shutdown_driver(NULL); | |
811 | log_write(0, LOG_MAIN, "dspam has been shut down!"); */ | |
812 | ||
813 | mysql_cleanup(); | |
814 | del_cachef(); | |
815 | ||
816 | return(return_flag); | |
817 | } /** cleanitup */ | |
818 | ||
819 | void inititial_spam_filtering() { | |
820 | ||
821 | remove_headers(spamflag); | |
822 | ||
823 | if (init_users()) { | |
824 | log_write(0, LOG_MAIN, "Opps!!"); | |
825 | return; | |
826 | } | |
827 | ||
828 | /** lets start dspam filtering... */ | |
829 | load_usersrs(lscan.l_users); | |
830 | ||
831 | return; | |
832 | } /** inititial_spam_filtering */ | |
833 | ||
834 | /** | |
835 | * Note to self: need to provide a cleanup function for '_lusers_s' | |
836 | */ | |
837 | int local_scan(volatile int fd, uschar **return_text) { | |
838 | ||
839 | fd = fd; | |
840 | return_text = return_text; | |
841 | ||
842 | /** log_write(0, LOG_MAIN, "init dspam driver..."); | |
843 | dspam_init_driver(NULL); | |
844 | log_write(0, LOG_MAIN, "dspam driver init completed!"); */ | |
845 | ||
846 | cache_mesg(fd); | |
847 | if (scan_clamav(lscan.scanpath)) { | |
848 | log_write(0, LOG_MAIN, "rejecting email [%s] contains known virus [%s]", message_id, lscan.virname); | |
849 | return(cleanitup(LOCAL_SCAN_REJECT)); | |
850 | } | |
851 | ||
852 | /** check is spam reporting [spamrept.domain.tld|falsepos.domain.tld] */ | |
853 | lscan.lpart_domain = getlocalp_domain((char *)sender_address, lscan.lpart_domain); | |
854 | if ((recipients_count == 1) && (sender_host_authenticated != NULL)) { /** must be authenticated with only one recipient */ | |
855 | ||
856 | switch (getrept_type()) { | |
857 | case SPAMREPT: /** report spam email */ | |
858 | if (report_spam(SPAMREPT)) { | |
859 | log_write(0, LOG_MAIN, "it all went wrong... [spamrept]"); | |
860 | } | |
861 | break; | |
862 | case FALSEPOS: /** report false positive */ | |
863 | log_write(0, LOG_MAIN, "reporting [falsepos]"); | |
864 | if (report_spam(FALSEPOS)) { | |
865 | log_write(0, LOG_MAIN, "it all went wrong... [falsepos]"); | |
866 | } | |
867 | break; | |
868 | default: /** no reporting required, scan for spam perhaps ? */ | |
869 | log_write(0, LOG_MAIN, "single user rcpt, scanning..."); | |
870 | break; | |
871 | } | |
872 | } else if ( (check_spamflag("falsepos")) || (check_spamflag("spamrept")) ) { | |
873 | /** user's can not report spam if they are not authenticated */ | |
874 | return(cleanitup(LOCAL_SCAN_REJECT)); | |
875 | } | |
876 | ||
877 | if (sender_host_authenticated == NULL) { | |
878 | inititial_spam_filtering(); | |
879 | /** | |
880 | * some very simple method of slowing spammers down... [teergrube??] | |
881 | * maybe some indication on 'probability' & 'confidence' we can lenghten time | |
882 | * perhaps even a record on blacklists... ? Rejection here too ? | |
883 | */ | |
884 | /** if (lscan.spamflag == SPAMFLAG) { | |
885 | alarm(0); | |
886 | sleep(30); | |
887 | } */ | |
888 | } | |
889 | ||
890 | return(cleanitup(LOCAL_SCAN_ACCEPT)); | |
891 | } /** local_scan */ | |
892 |