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 <[EMAIL PROTECTED]> + * + * 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: [EMAIL PROTECTED] '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 ([EMAIL PROTECTED]) */ + 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 ([EMAIL PROTECTED]) */ + 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, "[EMAIL PROTECTED]", (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, "[EMAIL PROTECTED]", (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>> _______________________________________________ pld-cvs-commit mailing list [email protected] http://lists.pld-linux.org/mailman/listinfo/pld-cvs-commit
