SOURCES: dspam_exim.c (NEW) - new
arekm
arekm at pld-linux.org
Thu Sep 1 19:29:29 CEST 2005
Author: arekm Date: Thu Sep 1 17:29:29 2005 GMT
Module: SOURCES Tag: HEAD
---- Log message:
- new
---- Files affected:
SOURCES:
dspam_exim.c (NONE -> 1.1) (NEW)
---- Diffs:
================================================================
Index: SOURCES/dspam_exim.c
diff -u /dev/null SOURCES/dspam_exim.c:1.1
--- /dev/null Thu Sep 1 19:29:29 2005
+++ SOURCES/dspam_exim.c Thu Sep 1 19:29:24 2005
@@ -0,0 +1,892 @@
+ /**
+ * kSpam plugin for Exim Local Scan.
+ * Copyright (C) 2005 James Kibblewhite <kibble at aproxity.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * -----------------------------------------------------------------------------
+ * $Id$
+ * -----------------------------------------------------------------------------
+ *
+ * This local_scan.c file compiles in with exim4.
+ * Exim: http://www.exim.org/
+ * MySql: http://www.mysql.com/
+ * ClamAV: http://www.clamav.org/
+ * DSpam: http://www.nuclearelephant.com/projects/dspam/
+ *
+ * Changes:
+ * Version 0.8: Bug fixes further improved. No crashes can be replicated. Futher testing
+ * required. Removed debugging exim_mainlog output. Will try and get user
+ * rules implemented...
+ *
+ * Version 0.7: Bugs all fixed, seems to be a fully working system, need to test
+ * all features before full public release can be made...
+ * Will remove all debugging output in 0.9... public release v1.0
+ *
+ * Version 0.6: Improved email validation. Added `cleanitup` as a cleanup routeen
+ * ClamAV lib updated [now 0.8x & above required as min requirement]
+ * Bleeding Edge CVS of dspam is also required
+ *
+ * Version 0.5: Started ruleset loading support from database.
+ *
+ * Version 0.4: Got DSpam working and aliases sorted. First beta release.
+ *
+ * Version 0.3: Included a config file reader to take variables from the main configuration file.
+ * Also integrated the last of MySQL support and tidy duties on the code done.
+ *
+ * Version 0.2: Fixes to scan mbox style flatfiles with compressed files...
+ * thanks to ClamAV for: CL_ARCHIVE & CL_MAIL
+ * you've saved me the bother of extracting the mime base64 encoded stuff !! yay
+ *
+ * Version 0.1: For personal testing.
+ *
+ * Ideas: Can block email totally if probability & confidence is very high [like +95% (=>0.95)]
+ *
+ * Known bugs: [X] This is no longer a bug, all bugs fixed, testing is required...
+ *
+ * If you submit 'many' new emails all at once it has a tendancy to die but tell
+ * you that it has failed... Maybe by controlling the flow of emails will help
+ * [although will slow thru-put]. Will crash at 'mysql_real_connect()' in 'mysql_setup()'
+ * while trying to establish a connection on a second pass... [This is
+ *
+ * Donations: To paypal: jelly_bean_junky at hotmail.com 'if' you use this code commercially,
+ * ask your boss for it! And for a pay rise while your at it too... Otherwise I don't
+ * expect anything, unless you wanna send some cool stuff to me...
+ *
+ * Notes: This line may need appending to the end of the local_scan.o line:
+ * -I/usr/include/mysql -I/usr/include/dspam -DHAVE_CONFIG_H -DCONFIG_DEFAULT=/etc/sysconf.d/dspam.conf
+ */
+
+ /** include required by exim */
+ #include "local_scan.h"
+
+ /** include required by clamav for antivirus checking */
+ #include "clamav.h"
+
+ /** include required by mysql for access to the database */
+ #include "mysql.h"
+
+ /** include required by dspam for spam filtering */
+ #include "libdspam.h"
+
+ /** the usual suspects */
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <unistd.h>
+ #include <signal.h>
+ #include <string.h>
+ #include <fcntl.h>
+
+ #define SPAMREPT 2
+ #define FALSEPOS 4
+ #define SPAMFLAG 8
+ #define TOGBLACK 16
+ #define TOGWHITE 32
+ #define SMBYTE 64
+ #define EMBYTE 128
+ #define QMBYTE 256
+ #define HMBYTE 512
+ #define WMBYTE 1024
+ #define BUFFER_SIZE 2048
+
+ /** blocks hosts & ips & emails & headers */
+ typedef struct bhosts_s { /** _lscan._lusers_s.bhosts->next */
+ struct bhosts_s * next;
+ char * sender_hostname;
+ char * sender_ipaddr;
+ char * sender_logics; /** OR | AND -> hostname - ipaddr */
+ char * email;
+ char * email_mtype; /** contains | exact match */
+ char * header_field;
+ char * header_value;
+ char * header_mtype; /** contains | exact match -> header_value if header_value == NULL || "" use header_field */
+ char * logics; /** OR | AND -> all values */
+ } _bhosts_s;
+
+ /** linked list of local users requiring filtering */
+ typedef struct lusers_s { /** _lscan._lusers_s.username */
+ struct lusers_s * next;
+ _bhosts_s * bhosts;
+ int mailuser_id; /** refers to database id */
+ int enabled; /** is filtering enabled... */
+ char rcptname[EMBYTE]; /** rcpt name as appears in recipients_list */
+ char realemail[EMBYTE]; /** if rcptname is an alias, this will be the real email
+ for loading dpsma rules with, else set the same as rcptname */
+ } _lusers_s;
+
+ typedef struct email_struct {
+ char localpart[SMBYTE];
+ char domain[SMBYTE];
+ } _email_struct;
+
+ /** varaibles of mass instructions */
+ typedef struct lscan_structure {
+ MYSQL * mysql;
+ MYSQL_RES * result;
+ MYSQL_ROW row;
+ _lusers_s * l_users;
+ _email_struct lpart_domain;
+ struct cl_limits limits;
+ struct cl_node * root;
+ header_line * hl_ptr;
+ char * virname;
+ char emailaddy[HMBYTE];
+ char querystr[BUFFER_SIZE];
+ char buffer[BUFFER_SIZE];
+ char scanpath[BUFFER_SIZE];
+ int i;
+ int iNo;
+ int spamflag;
+ int writefd;
+ } _lscan;
+
+ _lscan lscan;
+
+ /**
+ * Remember to set LOCAL_SCAN_HAS_OPTIONS=yes in Local/Makefile
+ * otherwise you get stuck with the compile-time defaults
+ */
+ /** Al our variables we draw in from the 'exim-localscan.conf' file */
+ static uschar * database = US"socket_aproxity";
+ static uschar * hostname = US"localhost";
+ static uschar * password = US"password";
+ static uschar * poolpath = US"/home/mail/spool";
+ static uschar * spamflag = US"X-KD-Spam";
+ static uschar * username = US"mail";
+
+ optionlist local_scan_options[] = { /** alphabetical order */
+ { "database", opt_stringptr, &database },
+ { "hostname", opt_stringptr, &hostname },
+ { "password", opt_stringptr, &password },
+ { "poolpath", opt_stringptr, &poolpath },
+ { "spamflag", opt_stringptr, &spamflag },
+ { "username", opt_stringptr, &username }
+ };
+
+ int local_scan_options_count = sizeof(local_scan_options) / sizeof(optionlist);
+
+ #ifdef DLOPEN_LOCAL_SCAN
+ /** Return the verion of the local_scan ABI, if being compiled as a .so */
+ int local_scan_version_major(void) {
+ return(LOCAL_SCAN_ABI_VERSION_MAJOR);
+ }
+
+ int local_scan_version_minor(void) {
+ return(LOCAL_SCAN_ABI_VERSION_MINOR);
+ }
+
+ /**
+ * Left over for compatilibility with old patched exims that didn't have
+ * a version number with minor an major. Keep in mind that it will not work
+ * with older exim4s (I think 4.11 and above is required)
+ */
+
+ #ifdef DLOPEN_LOCAL_SCAN_OLD_API
+ int local_scan_version(void) {
+ return(1);
+ }
+ #endif
+ #endif
+
+ /** delete our cached file */
+ void del_cachef() {
+ if (unlink(lscan.scanpath)) {
+ debug_printf("file [%s] not removed", lscan.scanpath);
+ }
+ return;
+ } /** del_cachef */
+
+ /**
+ * Scan email for virus. Returns 1 if virus
+ * detected or 0 if no virus is detected. Sets
+ * lscan.virname to virua or error output...
+ */
+ int scan_clamav(char * scanpath) {
+
+ sprintf(lscan.scanpath, "%s", scanpath);
+ lscan.iNo = 0;
+
+ /** lets load all our virus defs database's into memory */
+ lscan.root = NULL; /** without this line, the dbload will crash... */
+ if((lscan.i = cl_loaddbdir(cl_retdbdir(), &lscan.root, &lscan.iNo))) {
+ sprintf(lscan.virname, "error: [%s]", cl_perror(lscan.i));
+ } else {
+ if((lscan.i = cl_build(lscan.root))) {
+ sprintf(lscan.virname, "database initialization error: [%s]", cl_perror(lscan.i));
+ cl_free(lscan.root);
+ }
+ memset(&lscan.limits, 0x0, sizeof(struct cl_limits));
+ lscan.limits.maxfiles = 1000; /** max files */
+ lscan.limits.maxfilesize = 10 * 1048576; /** maximal archived file size == 10 Mb */
+ lscan.limits.maxreclevel = 12; /** maximal recursion level */
+ lscan.limits.maxratio = 200; /** maximal compression ratio */
+ lscan.limits.archivememlim = 0; /** disable memory limit for bzip2 scanner */
+
+ if ((lscan.i = cl_scanfile(lscan.scanpath, (const char **)&lscan.virname, NULL, lscan.root,
+ &lscan.limits, CL_SCAN_ARCHIVE | CL_SCAN_MAIL | CL_SCAN_OLE2 | CL_SCAN_BLOCKBROKEN | CL_SCAN_HTML | CL_SCAN_PE)) != CL_VIRUS) {
+ if (lscan.i != CL_CLEAN) {
+ sprintf(lscan.virname, "error: [%s]", cl_perror(lscan.i));
+ } else {
+ lscan.virname = NULL;
+ }
+ }
+ if (lscan.root != NULL) {
+ cl_free(lscan.root);
+ }
+ memset(&lscan.limits, 0x0, sizeof(struct cl_limits));
+ }
+
+ /** lets delete the spool message as we don't need it any more */
+ if (lscan.virname != NULL) { /** remove the file if we have a virus as we are going to reject it */
+ return(1);
+ } else { /** else keep the file for spam filtering */
+ return(0);
+ }
+
+ return(1);
+
+ } /** scan_clamav */
+
+ void cache_mesg(int fd) {
+
+ fd = fd;
+
+ sprintf(lscan.scanpath, "%s/%s", poolpath, message_id);
+
+ /** create the file handler */
+ lscan.writefd = creat(lscan.scanpath, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
+
+ /** lets make this thing look like an email mbox structured thing or clamav won't work !! */
+ memset(lscan.buffer, 0x0, BUFFER_SIZE);
+ sprintf(lscan.buffer, "From %s Mon Jan 00 00:00:00 0000\n", sender_address);
+ lscan.i = write(lscan.writefd, lscan.buffer, strlen(lscan.buffer));
+
+ lscan.hl_ptr = header_list;
+ while (lscan.hl_ptr != NULL) {
+ /** type '*' means the header is internal, don't print it, or if the variable is NULL, what's the point...? */
+ if ((lscan.hl_ptr->type != '*') || (lscan.hl_ptr->text != NULL)) {
+ lscan.i = write(lscan.writefd, lscan.hl_ptr->text, strlen(lscan.hl_ptr->text));
+ }
+ lscan.hl_ptr = lscan.hl_ptr->next;
+ }
+
+ memset(lscan.buffer, 0x0, BUFFER_SIZE);
+ sprintf(lscan.buffer, "\n");
+ lscan.i = write(lscan.writefd, lscan.buffer, strlen(lscan.buffer));
+
+ /** output all the data, read from orignal and write to spool */
+ while ((lscan.i = read(fd, lscan.buffer, BUFFER_SIZE)) > 0) {
+ lscan.i = write(lscan.writefd, lscan.buffer, lscan.i);
+ }
+
+ /** close the handle */
+ lscan.i = close(lscan.writefd);
+ debug_printf("path of cached file [%s]", lscan.scanpath);
+
+ return;
+
+ } /** cache_mesg */
+
+ void remove_headers(char * hfield) {
+
+ lscan.hl_ptr = header_list;
+ while (lscan.hl_ptr != NULL) {
+ if ( ((lscan.hl_ptr->type != '*')) && (!strncmp(lscan.hl_ptr->text, hfield, strlen(hfield))) ) {
+ lscan.hl_ptr->type = '*';
+ }
+ lscan.hl_ptr = (struct header_line *)lscan.hl_ptr->next;
+ }
+ } /** remove_headers */
+
+ /**
+ * If the supplied email address is syntactically valid,
+ * spc_email_isvalid() will return 1; otherwise, it will
+ * return 0. Need to check that there is at least one '@'
+ * symbol and only one in the whole email address, else
+ * `getlocalp_domain` function won't work correctly...
+ */
+ int spc_email_isvalid(const char *address) {
+
+ int count = 0;
+ const char *c, *domain;
+ static char *rfc822_specials = "()<>@,;:\\\"[]/";
+
+ /** first we validate the name portion (name at domain) */
+ for (c = address; *c; c++) {
+ if ((*c == '\"') && (c == address || *(c - 1) == '.' || *(c - 1) == '\"')) {
+ while (*++c) {
+ if (*c == '\"') {
+ break;
+ }
+ if ((*c == '\\') && (*++c == ' ')) {
+ continue;
+ }
+ if (*c < ' ' || *c >= 127) {
+ return(0);
+ }
+ }
+ if (!*c++) {
+ return(0);
+ }
+ if (*c == '@') {
+ break;
+ }
+ if (*c != '.') {
+ return(0);
+ }
+ continue;
+ }
+ if (*c == '@') {
+ break;
+ }
+ if (*c <= ' ' || *c >= 127) {
+ return(0);
+ }
+ if (strchr(rfc822_specials, *c)) {
+ return(0);
+ }
+ }
+ if (c == address || *(c - 1) == '.') {
+ return(0);
+ }
+
+ /** next we validate the domain portion (name at domain) */
+ if (!*(domain = ++c)) {
+ return(0);
+ }
+
+ do {
+ if (*c == '.') {
+ if (c == domain || *(c - 1) == '.') {
+ return(0);
+ }
+ count++;
+ }
+ if (*c <= ' ' || *c >= 127) {
+ return(0);
+ }
+ if (strchr(rfc822_specials, *c)) {
+ return(0);
+ }
+ } while (*++c);
+ return(count >= 1);
+ } /** spc_email_isvalid */
+
+ /** this function returns the localpart and the domain section of an email in any given string */
+ _email_struct getlocalp_domain(char * emailaddr, _email_struct lpart_domain) {
+
+ memset(lscan.emailaddy, 0x0, HMBYTE);
+ memset(lpart_domain.localpart, 0x0, SMBYTE);
+ memset(lpart_domain.domain, 0x0, SMBYTE);
+ sprintf(lscan.emailaddy, "%s", emailaddr);
+
+ if (spc_email_isvalid(lscan.emailaddy)) {
+ sprintf(lpart_domain.localpart, "%s", strtok(lscan.emailaddy, "@"));
+ sprintf(lpart_domain.domain, "%s", strtok(NULL, "@"));
+ }
+ return(lpart_domain);
+ } /** getlocalp_domain */
+
+ /** mysql results and rows cleanup routine */
+ void mysqlrr_cleanup() {
+
+ lscan.row = NULL;
+
+ if (lscan.result != NULL) {
+ mysql_free_result(lscan.result);
+ lscan.result = NULL;
+ }
+
+ } /** mysqlrr_cleanup */
+
+ /** mysql results and rows cleanup routine */
+ void mysql_cleanup() {
+
+ mysqlrr_cleanup();
+
+ mysql_close(lscan.mysql);
+ memset(&lscan.mysql, 0x0, sizeof(lscan.mysql));
+ free(lscan.mysql);
+
+ } /** mysql_cleanup */
+
+ int mysql_setup() {
+
+ mysql_cleanup();
+
+ if (!(lscan.mysql = mysql_init(NULL))) {
+ log_write(0, LOG_MAIN, "mysql_init [%s]", mysql_error(lscan.mysql));
+ return(1);
+ }
+
+ /** we are always connecting to localhost!! to slow otherwise... */
+ if (!mysql_real_connect(lscan.mysql, hostname, username, password, database, 0, NULL, 0)) {
+ log_write(0, LOG_MAIN, "mysql_real_connect [%s]", mysql_error(lscan.mysql));
+ mysql_close(lscan.mysql);
+ return(1);
+ }
+
+ if (mysql_select_db(lscan.mysql, database)) {
+ log_write(0, LOG_MAIN, "mysql_select_db [%s]", mysql_error(lscan.mysql));
+ mysql_close(lscan.mysql);
+ return(1);
+ }
+
+ return(0);
+
+ } /** mysql_setup */
+
+ /**
+ * Instead of returning a row of data, I've decided to return
+ * the results to obtain the rows, incase I need more than one
+ * set of rows from the results. This basically runs the current
+ * sql query in lscan.querystr.
+ */
+ MYSQL_RES * get_mysqlres() {
+
+ /** clean up result and row if required */
+ mysqlrr_cleanup();
+
+ debug_printf("running query:\n\t[%s]\n", lscan.querystr);
+
+ if (mysql_real_query(lscan.mysql, lscan.querystr, strlen(lscan.querystr))) {
+ log_write(0, LOG_MAIN, "mysql_real_query [%s]", mysql_error(lscan.mysql));
+ return((MYSQL_RES * )NULL);
+ }
+
+ if (!(lscan.result = mysql_store_result(lscan.mysql))) {
+ log_write(0, LOG_MAIN, "mysql_store_result [%s]", mysql_error(lscan.mysql));
+ return((MYSQL_RES * )NULL);
+ }
+
+ if (mysql_num_rows(lscan.result) != 0) {
+ return(lscan.result);
+ }
+
+ return((MYSQL_RES * )NULL);
+ } /** get_mysqlres */
+
+ /** add user and rulesets */
+ _lusers_s * add_userset(_lusers_s * l_users, int mailuser_id, int enabled, char * rcptname, _email_struct lpart_domain) {
+
+ _lusers_s * lp = l_users;
+
+ if (enabled == 0) {
+ return(l_users);
+ }
+
+ /** remove any duplicates of users in linked list... */
+
+ if (l_users != NULL) {
+ while (l_users->next != NULL) {
+ l_users = (_lusers_s *)l_users->next;
+ }
+ l_users->next = (struct lusers_s *)malloc(sizeof(_lusers_s));
+ l_users = (_lusers_s *)l_users->next;
+
+ l_users->mailuser_id = mailuser_id;
+ l_users->enabled = enabled;
+ memset(l_users->rcptname, 0x0, EMBYTE);
+ memset(l_users->realemail, 0x0, EMBYTE);
+ sprintf(l_users->rcptname, "%s", rcptname);
+ sprintf(l_users->realemail, "%s@%s", (char *)lpart_domain.localpart, (char *)lpart_domain.domain);
+
+ l_users->next = NULL;
+ l_users = lp;
+ } else {
+ l_users = (_lusers_s *)(struct lusers_s *)malloc(sizeof(_lusers_s));
+
+ l_users->mailuser_id = mailuser_id;
+ l_users->enabled = enabled;
+ memset(l_users->rcptname, 0x0, EMBYTE);
+ memset(l_users->realemail, 0x0, EMBYTE);
+ sprintf(l_users->rcptname, "%s", rcptname);
+ sprintf(l_users->realemail, "%s@%s", (char *)lpart_domain.localpart, (char *)lpart_domain.domain);
+
+ l_users->next = NULL;
+ l_users = l_users;
+ }
+
+ /** we should now do a look up for the rules and add them to 'l_users->bhosts' */
+
+ return(l_users);
+ }
+
+ int load_realuser(char * emailaddr) {
+
+ lscan.lpart_domain = getlocalp_domain(emailaddr, lscan.lpart_domain); /** lscan.lpart_domain.localpart && lscan.lpart_domain.domain */
+ memset(lscan.querystr, 0x0, BUFFER_SIZE);
+ 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);
+
+ if (get_mysqlres()) { /** sets lscan.result to results returned from db */
+ if (!(lscan.row = mysql_fetch_row(lscan.result))) { /** lscan.row[0] */
+ log_write(0, LOG_MAIN, "mysql_fetch_row [%s]", mysql_error(lscan.mysql));
+ return(1);
+ }
+ } else {
+ return(1);
+ }
+
+ /** add user to results & rules... */
+ if ((int)atoi(lscan.row[1]) != 0) {
+ log_write(0, LOG_MAIN, "adding user ...");
+ 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);
+ }
+
+ return(0);
+ } /** load_realuser */
+
+ int load_aliases(char * emailaddr) {
+
+ lscan.lpart_domain = getlocalp_domain(emailaddr, lscan.lpart_domain); /** lscan.lpart_domain.localpart && lscan.lpart_domain.domain */
+ memset(lscan.querystr, 0x0, BUFFER_SIZE);
+ sprintf(lscan.querystr, "SELECT alias FROM mail_aliases WHERE local_part = '%s' AND domain = '%s'", lscan.lpart_domain.localpart, lscan.lpart_domain.domain);
+
+ if (get_mysqlres()) { /** sets lscan.result to results returned from db */
+ if (!(lscan.row = mysql_fetch_row(lscan.result))) { /** lscan.row[0] */
+ log_write(0, LOG_MAIN, "mysql_fetch_row [%s]", mysql_error(lscan.mysql));
+ return(1);
+ }
+ if (load_realuser((char *)lscan.row[0])) { /** failed to load alias as real user */
+ return(1);
+ }
+ } else {
+ /** check for wildcard localpart */
+ memset(lscan.querystr, 0x0, BUFFER_SIZE);
+ sprintf(lscan.querystr, "SELECT alias FROM mail_aliases WHERE local_part = '*' AND domain = '%s'", lscan.lpart_domain.domain);
+
+ if (get_mysqlres()) { /** sets lscan.result to results returned from db */
+ if (!(lscan.row = mysql_fetch_row(lscan.result))) { /** lscan.row[0] */
+ log_write(0, LOG_MAIN, "mysql_fetch_row [%s]", mysql_error(lscan.mysql));
+ return(1);
+ }
+ if (load_realuser((char *)lscan.row[0])) { /** failed to load alias as real user */
+ return(1);
+ }
+ }
+ }
+
+ return(0);
+ } /** load_aliases */
+
+ /** this loads all users settings into memory */
+ int init_users() {
+
+ if (mysql_setup()) {
+ return(1);
+ }
+
+ for (lscan.i = 0; lscan.i != recipients_count; lscan.i++) {
<<Diff was trimmed, longer than 597 lines>>
More information about the pld-cvs-commit
mailing list