#include "local_scan.h" #include #include #include #include #include #include #include #include #include "spamtest.h" #include "msgstore.h" //#define SPAMTEST_USE_SYSLOG /* Default values for configuration options */ #define KAS_CONNECT_TIMEOUT 60000 #define KAS_DEFAULT_DOMAIN "" #define KAS_LOG_LEVEL 0 #define KAS_ON_ERROR "tempfail" #define KAS_DATA_TIMEOUT 60000 #define KAS_CONNECT_TO "tcp:127.0.0.1:2277" #define KAS_FILTERING_SIZE_LIMIT 512 /* Result variables for configuration options */ static uschar *result_default_domain; static uschar *result_connect_to; static uschar *result_on_error_str; static int result_on_error; /* Configuration options */ //old static int st_connect_timeout = 0; static int debug_level = 0; static int st_rw_timeout = 0; static int filtering_size_limit=0; static uschar *st_mail_domain; static uschar *st_on_error; static uschar *st_address; //new static int kas_connect_timeout = 0; static int kas_log_level = 0; static int kas_data_timeout = 0; static int kas_filtering_size_limit = 0; static uschar *kas_default_domain; static uschar *kas_on_error; static uschar *kas_connect_to; optionlist local_scan_options[] = {/* The entries must appear in alphabetical order */ { "connect_timeout", opt_int, &st_connect_timeout }, { "kas_connect_timeout", opt_int, &kas_connect_timeout }, { "kas_connect_to", opt_stringptr, &kas_connect_to }, { "kas_data_timeout", opt_int, &kas_data_timeout }, { "kas_default_domain", opt_stringptr, &kas_default_domain }, { "kas_filtering_size_limit", opt_int, &kas_filtering_size_limit }, { "kas_log_level",opt_int,&kas_log_level}, { "kas_on_error",opt_stringptr,&kas_on_error}, { "local_mail_domain", opt_stringptr, &st_mail_domain }, { "log_level",opt_int,&debug_level}, { "on_spamtest_error",opt_stringptr,&st_on_error}, { "rw_timeout", opt_int, &st_rw_timeout }, { "spamtest_address", opt_stringptr, &st_address }, { "spamtest_filtering_size_limit", opt_int, &filtering_size_limit }, }; int local_scan_options_count = sizeof(local_scan_options)/sizeof(optionlist); int check_results(spamtest_session_t *s,spamtest_status_t *status,int *res,int fd,uschar **return_text); int proceed_accept(spamtest_status_t *status,int fd); void logger(const int log_level,const char *fmt,...); void prepare_config_parameters(); void free_conf(); int local_scan(int fd, uschar **return_text) { int myfd; char buf[4096]; int cnt = 0; int totalcnt = 0; int i; header_line * tmp; spamtest_session_t s; int errorcode = STS_ERR_NO_ERR; spamtest_status_t *status = NULL; int rcpt_sent = 0; int res = 0; int rest = 0; char *ctmp; /* Initialize spamtest session */ prepare_config_parameters(); if(filtering_size_limit > 0) { struct stat sb; fstat(fd,&sb); if(sb.st_size > filtering_size_limit * 1024) { logger(LOG_DEBUG," Message larger than filtering_size_limit, accepting"); free_conf(); return LOCAL_SCAN_ACCEPT; } } bzero(&s,sizeof(s)); s.access_address = result_connect_to; s.rw_timeout = st_rw_timeout; s.connect_timeout = st_connect_timeout; s.mail_domain = result_default_domain; if ( s.access_address == NULL || s.access_address[0] == '\0' ) { logger(LOG_ERR," Spamtest access address not defined, cannot process mail by spam filtering"); free_conf(); return LOCAL_SCAN_ACCEPT; } logger(LOG_DEBUG," Spam test session: %s %s %d %d",s.access_address,s.mail_domain,s.connect_timeout,s.rw_timeout); if ( (errorcode = sts_init(&s)) < 0 ) { logger(LOG_ERR," Unable to connect to %s (%s)",s.access_address,sts_strerror(errorcode)); free_conf(); return result_on_error; }; if ( ! s.should_store_message ) { /* SMTP mode, blackholing message because filter will send message */ logger(LOG_ERR," Filter in SMTP mode, Incorrect situation."); //recipients_count = 0; sts_close(&s); free_conf(); return result_on_error; } if ( sender_address != NULL && sender_address[0] != '\0' ) { if ( (errorcode = sts_set_sender(&s,sender_address)) != STS_ERR_NO_ERR ) { logger(LOG_ERR," Unable to pass sender address %s(%d)",sender_address,errorcode); free_conf(); return result_on_error; } } if ( sender_host_address != NULL && sender_host_address[0] != '\0' ) { if ( ( errorcode = sts_set_relay(&s,sender_host_address) ) != STS_ERR_NO_ERR ) { logger(LOG_ERR," Unable to pass relay IP"); if(errorcode == STS_ERR_OUT_OF_MEMORY){ free_conf(); return result_on_error; } } } rcpt_sent = 0; for ( i = 0;itype != '*') { logger(LOG_DEBUG,"Iterating a , %c %d %s|%d",tmp->type,tmp->slen,tmp->text,tmp->text[tmp->slen-1]); if ( ( status = sts_send_body(&s,tmp->text,tmp->slen,STS_REINIT_TIMEOUT) ) == NULL ) { /* failure. finish proceed */ logger(LOG_ERR," NULL status after sending header %s",tmp->text); sts_close(&s); } else { /* check status and finish if need */ logger(LOG_DEBUG," header %s sent to filter",tmp->text); if ( check_results(&s,status,&res,fd,return_text) > 0 ){ free_conf(); return res; } } } if(tmp==header_last) break; tmp = tmp->next; } logger(LOG_DEBUG," Headers sent"); if ( ( status = sts_send_body(&s,"\n",1,STS_REINIT_TIMEOUT) ) == NULL ) { /* failure. finish proceed */ logger(LOG_ERR," NULL status after sending empty string(end of headers)"); sts_close(&s); } else { /* check status and finish if need */ logger(LOG_DEBUG," empty string (end of headers) sent to filter"); if ( check_results(&s,status,&res,fd,return_text) > 0 ){ free_conf(); return res; } } logger(LOG_DEBUG," Enter sent"); i = 0; ctmp = buf; while ( (cnt = read(fd,ctmp,buf + sizeof(buf) - ctmp)) > 0 ) { logger(LOG_DEBUG,"sending %d bytes to filter",cnt); status = sts_send_body(&s,buf,cnt,STS_REINIT_TIMEOUT); logger(LOG_DEBUG,"send"); if ( status == NULL ) { // failure. finish proceed logger(LOG_ERR," NULL status after sending part of body"); sts_close(&s); } else { // check status and finish if need logger(LOG_DEBUG," part of body sent to filter"); if ( check_results(&s,status,&res,fd,return_text) > 0 ){ free_conf(); return res; } } }; logger(LOG_DEBUG," Total sent %d bytes of message",totalcnt); status = sts_body_end(&s,STS_FLUSH_DATA); logger(LOG_DEBUG," Body sent"); if ( check_results(&s,status,&res,fd,return_text) > 0 ){ free_conf(); return res; } else sts_close(&s); logger(LOG_DEBUG," EOF"); free_conf(); return LOCAL_SCAN_ACCEPT; } int check_results(spamtest_session_t *s,spamtest_status_t *status,int *res,int fd,uschar **return_text) { logger(LOG_DEBUG," Check results started."); switch ( status->status ) { case STS_SMTP_ACCEPT: case STS_BLACKHOLE: logger(LOG_DEBUG," SMTP_ACCEPT or BLACK_HOLE."); recipients_count = 0; sts_close(s); *res = LOCAL_SCAN_ACCEPT; return 1; case STS_REJECT: logger(LOG_DEBUG," REJECT"); *return_text=(uschar *)status->reason; sts_close(s); *res = LOCAL_SCAN_REJECT; return 1; case STS_FILTER_ERROR: logger(LOG_DEBUG," FILTER_ERROR"); sts_close(s); *res = result_on_error; return 1; case STS_ACCEPT: logger(LOG_DEBUG," ACCEPT"); if ( proceed_accept(status,fd) != 0 ) *res = result_on_error; else *res = LOCAL_SCAN_ACCEPT; sts_close(s); return 1; case STS_CONTINUE: logger(LOG_DEBUG," CONTINUE"); return 0; } } extern int recipients_list_max; int proceed_accept(spamtest_status_t *status,int fd) { spamtest_status_t *st; message_status_t *mst; int i; int rcptn; int good_rcpt_cnt = 0,k; int found = 0; logger(LOG_DEBUG," accept_message started."); st = glue_actions(status); if (st==NULL) { logger(LOG_ERR," glue_actions return NULL" ); return -1; } mst = &st->ms_array[0] ; rcptn = rcptlist_count(mst->rcpts); for( k=0; krcpts, k))>0) good_rcpt_cnt++; if(good_rcpt_cnt <1) { logger(LOG_INFO," Empty recipient list"); recipients_count = 0; return 0; } if ( (mst != NULL) && (mst->action == STS_ACTION_CHANGE) ) { logger(LOG_DEBUG," spamtest status returned STS_ACTION_CHANGE for message."); // cleanup old rcpts list recipients_count = 0; recipients_list = NULL; recipients_list_max = 0; for (i = 0; ircpts);i++) { char *rcpt = rcptlist_getn(mst->rcpts,i); if(rcpt) { logger(LOG_DEBUG,"Adding recipient: %s",rcpt); receive_add_recipient(string_copy(US rcpt),-1); } } logger(LOG_DEBUG," new recipients_count=%d",recipients_count); if ( sender_address[0] != '\0' ) { if (strcasecmp(mst->mailfrom,sender_address) != 0 ) { /* changing sender address */ logger(LOG_DEBUG," senders different"); sender_address = string_copy( US mst->mailfrom ); } else { logger(LOG_DEBUG," senders equivalented"); } } logger(LOG_DEBUG," Actions count = %d",mst->action_count); for (i=0;iaction_count;i++) { header_line * tmp; header_line * matched; header_line * last; int deleted_count = 0; int matched_count = 0; int hdrlen = strlen(mst->act_array[i].header); switch ( mst->act_array[i].type ) { case STS_HEADER_DEL: logger(LOG_DEBUG," Delete header %s",mst->act_array[i].header); tmp = header_list; while ( tmp != NULL ) { if ( (tmp->type != '*') && (tmp->slen > 0) && (strncasecmp(tmp->text, mst->act_array[i].header, hdrlen < tmp->slen ? hdrlen:tmp->slen) == 0 ) ) { deleted_count++; tmp->type = '*'; } tmp = tmp->next; } logger(LOG_DEBUG," deleted %d headers",deleted_count); break; case STS_HEADER_ADD: logger(LOG_DEBUG," Add value to header %s %s",mst->act_array[i].header,mst->act_array[i].value); tmp = header_list; matched = NULL; while ( tmp != NULL ) { logger(LOG_DEBUG,"Iterating, %c %d %s",tmp->type,tmp->slen,tmp->text); if ( (tmp->slen > 0) && (strncasecmp(tmp->text, mst->act_array[i].header, hdrlen < tmp->slen ? hdrlen:tmp->slen) == 0 ) ) { if ( matched != NULL ) { if ( (tmp->type != '*') || ( matched->type == '*' ) ) matched = tmp; } else { matched = tmp; } } tmp = tmp->next; if(matched) break; } if ( matched != NULL ) { /* adding value to exits header */ logger(LOG_DEBUG,"Found matched header, %c %d %s",matched->type,matched->slen,matched->text); if ( matched->type == '*' ) { matched->text = string_sprintf("%s: %s\n",mst->act_array[i].header,mst->act_array[i].value); matched->slen = strlen(matched->text); matched->type = ' '; matched->type = header_checkname(matched,0); } else { logger(LOG_DEBUG,"Appending value to existing header value"); matched->text = string_sprintf("%s %s\n",matched->text,mst->act_array[i].value); matched->slen += (strlen(mst->act_array[i].value) + 2); } } else { /* add new header in case when this header not found */ logger(LOG_DEBUG," We must add value to header %s but this header not found.",mst->act_array[i].header); header_add(' ',"%s: %s\n",mst->act_array[i].header,mst->act_array[i].value); } logger(LOG_DEBUG," Add header %s %s",mst->act_array[i].header,mst->act_array[i].value); break; case STS_HEADER_PREPEND: logger(LOG_DEBUG," Prepend value to header %s %s",mst->act_array[i].header,mst->act_array[i].value); tmp = header_list; matched = NULL; while ( tmp != NULL ) { logger(LOG_DEBUG,"Iterating, %c %d %s",tmp->type,tmp->slen,tmp->text); if ( (tmp->slen > 0) && (strncasecmp(tmp->text, mst->act_array[i].header, hdrlen < tmp->slen ? hdrlen:tmp->slen) == 0 ) ) { if ( matched != NULL ) { if ( (tmp->type != '*') || ( matched->type == '*' ) ) matched = tmp; } else { matched = tmp; } } tmp = tmp->next; if(matched) break; } if ( matched != NULL ) { /* adding value to exits header */ logger(LOG_DEBUG,"Found matched header, %c %d %s",matched->type,matched->slen,matched->text); if ( matched->type == '*' ) { matched->text = string_sprintf("%s: %s\n",mst->act_array[i].header,mst->act_array[i].value); matched->slen = strlen(matched->text); matched->type = ' '; matched->type = header_checkname(matched,0); } else { int l = strlen(mst->act_array[i].header); char *p = matched->text+l+2; matched->text[l]=0; matched->text = string_sprintf("%s: %s %s",matched->text, mst->act_array[i].value,p); matched->slen += (strlen(mst->act_array[i].value) + 1); logger(LOG_DEBUG,"Prepending value %s to existing header value %s|", mst->act_array[i].value, matched->text); } } else { /* add new header in case when this header not found */ logger(LOG_DEBUG," We must add value to header %s but this header not found.",mst->act_array[i].header); header_add(' ',"%s: %s\n",mst->act_array[i].header,mst->act_array[i].value); } logger(LOG_DEBUG," Prepend header %s %s",mst->act_array[i].header,mst->act_array[i].value); break; case STS_HEADER_CHG: logger(LOG_DEBUG," Change header %s %s",mst->act_array[i].header,mst->act_array[i].value); tmp = header_list; matched = NULL; matched_count = 0; while ( tmp != NULL ) { int hdrlen = strlen(mst->act_array[i].header); if ( (tmp->type != '*') && (tmp->slen > 0) && (strncasecmp(tmp->text, mst->act_array[i].header, hdrlen < tmp->slen ? hdrlen:tmp->slen) == 0 ) ) { matched = tmp; matched_count++; } tmp = tmp->next; } if ( matched != NULL ) { matched->text = string_sprintf("%s: %s\n",mst->act_array[i].header,mst->act_array[i].value); matched->slen += (strlen(mst->act_array[i].value) + strlen(mst->act_array[i].header) + 3); if ( matched_count > 1 ) { tmp = header_list; while ( (tmp != NULL) && matched_count > 1 ) { if ( (tmp->type != '*') && (tmp->slen > 0) && (strncasecmp(tmp->text, mst->act_array[i].header, hdrlen < tmp->slen ? hdrlen:tmp->slen) == 0 ) && (tmp != matched) ) { tmp->type = '*'; matched_count--; } tmp = tmp->next; } } } else { logger(LOG_DEBUG," We must change value for header %s but this header not found.Adding new",mst->act_array[i].header); header_add(' ',"%s: %s\n",mst->act_array[i].header,mst->act_array[i].value); } break; case STS_HEADER_NEW: logger(LOG_DEBUG," New header %s: %s",mst->act_array[i].header,mst->act_array[i].value); tmp = header_list; matched = NULL; while ( tmp != NULL ) { if ( (tmp->type != '*') && (tmp->slen > 0) && (strncasecmp(tmp->text, mst->act_array[i].header, hdrlen < tmp->slen ? hdrlen:tmp->slen) == 0 ) ) { matched = tmp; } tmp = tmp->next; } if ( matched != NULL && matched != header_last ) { if(matched == header_last) { logger(LOG_DEBUG,"Header %s is last in message, adding below", mst->act_array[i].header); header_add(' ',"%s: %s\n",mst->act_array[i].header,mst->act_array[i].value); } else { /* adding value after last header with this name */ logger(LOG_DEBUG,"Some headers %s found",mst->act_array[i].header); tmp = header_last; header_add(' ',"%s: %s\n",mst->act_array[i].header,mst->act_array[i].value); tmp->next->next = matched->next; matched->next = tmp->next; tmp->next = NULL; header_last = tmp; } } else { /* add new header at the end of headers list */ logger(LOG_DEBUG,"Headers %s not found",mst->act_array[i].header); header_add(' ',"%s: %s\n",mst->act_array[i].header,mst->act_array[i].value); } break; default: logger(LOG_INFO," Invalid action type %d",mst->act_array[i].type); } } } else if ( (mst != NULL) && (mst->action == STS_ACTION_BOUNCE) ) { /* bounce message */ header_line *tmp; message_t *bounced_mess; logger(LOG_DEBUG," bouncing message."); /* preparing bounced message */ bounced_mess = message_init(); if ( bounced_mess == NULL ) { logger(LOG_ERR," Unable to allocate memory for bounced message"); return -1; } logger(LOG_DEBUG," new message allocated."); /* copying data from filter to prepared message (for automatically creating headers list ) */ for (i=0;ibouncemsg);i++ ) { if ( message_put_body(bounced_mess,msgtext_get_chunk(mst->bouncemsg,i),msgtext_chunk_len(mst->bouncemsg,i)) == -1 ) { logger(LOG_ERR," unable to copy data from filter to bounced message"); message_free(bounced_mess); return -1; } else { logger(LOG_DEBUG," message_put_body - ok."); } } /* deleting exim old headers */ tmp = header_list; while ( tmp != NULL ) { tmp->type = '*'; tmp = tmp->next; }; logger(LOG_DEBUG," headers deleted."); /* adding new headers */ for ( i = 0;i < bounced_mess->headers->used; i++ ) { header_add(' ',"%s: %s",bounced_mess->headers->array[i].header.hptr,bounced_mess->headers->array[i].value.hptr); logger(LOG_DEBUG," added header: %s: %s",bounced_mess->headers->array[i].header.hptr,bounced_mess->headers->array[i].value.hptr); } /* replacing rcpts */ recipients_count = 0; recipients_list = NULL; recipients_list_max = 0; if ( rcptlist_count(bounced_mess->recipients) > 0 ) { for (i = 0; irecipients);i++) { char *rcpt = rcptlist_getn(bounced_mess->recipients,i); if ( rcpt == NULL ) { logger(LOG_ERR," unable to get recipient %d",i); message_free(bounced_mess); return -1; } logger(LOG_DEBUG," rcpt = %s",rcpt); receive_add_recipient(string_copy(US rcpt),-1); } } else { if ( sender_address[0] != '\0' ) receive_add_recipient(string_copy(US sender_address),-1); else { logger(LOG_ERR," Unable to get sender address to bounce, failed"); return -1; } } /* cleaning sender ( must be empty string ) */ logger(LOG_DEBUG," sender address emptied"); sender_address = ""; /* writing new message body */ lseek(fd,SPOOL_DATA_START_OFFSET,SEEK_SET); if ( get_message_bodychunks_count(bounced_mess) > 0 ) { for (i=0;iaction); else logger(LOG_ERR," mst is NULL"); } logger(LOG_DEBUG," Message accepted."); return 0; } void logger(const int log_level,const char *fmt,...) { va_list params; char buf[512]; /* LOG_ERR == 3 is minimum level for us */ if (log_level <= (debug_level+3)) { va_start(params,fmt); vsnprintf(buf,(size_t)512,fmt,params); debug_printf("-ls- %s\n",buf); #ifdef SPAMTEST_USE_SYSLOG va_start(params,fmt); vsyslog(log_level>LOG_DEBUG?LOG_DEBUG:log_level,fmt,params); #else log_write(0,LOG_MAIN,"spamtest: %s",buf); #endif va_end(params); } } void prepare_config_parameters() { int len; /* * Firstly we are looking for new parameter, * then - for old parameter, else - we use * default value. */ if (kas_log_level != 0) debug_level = kas_log_level; if (debug_level == 0) debug_level = KAS_LOG_LEVEL; if (kas_connect_timeout != 0) st_connect_timeout = kas_connect_timeout*1000; if (st_connect_timeout == 0) st_connect_timeout = KAS_CONNECT_TIMEOUT; if (kas_data_timeout != 0) st_rw_timeout = kas_data_timeout*1000; if (st_rw_timeout == 0) st_rw_timeout = KAS_DATA_TIMEOUT; if (kas_filtering_size_limit != 0) filtering_size_limit = kas_filtering_size_limit; if (filtering_size_limit == 0) filtering_size_limit = KAS_FILTERING_SIZE_LIMIT; if (kas_default_domain != NULL){ len = strlen(kas_default_domain); result_default_domain = malloc(len+1); strncpy(result_default_domain,kas_default_domain,len); result_default_domain[len]='\0'; } else if (st_mail_domain != NULL){ len = strlen(st_mail_domain); result_default_domain = malloc(len+1); strncpy(result_default_domain,st_mail_domain,len); result_default_domain[len]='\0'; } else{ len = strlen(KAS_DEFAULT_DOMAIN); result_default_domain = malloc(len+1); strncpy(result_default_domain,KAS_DEFAULT_DOMAIN,len); result_default_domain[len]='\0'; } if (kas_connect_to != NULL){ len = strlen(kas_connect_to); result_connect_to = malloc(len+1); strncpy(result_connect_to,kas_connect_to,len); result_connect_to[len]='\0'; } else if (st_address != NULL){ len = strlen(st_address); result_connect_to = malloc(len+1); strncpy(result_connect_to,st_address,len); result_connect_to[len]='\0'; }/* one parameter have no default, * because else we can't disable kas-exim in config file*/ if (kas_on_error != NULL){ len = strlen(kas_on_error); result_on_error_str = malloc(len+1); strncpy(result_on_error_str,kas_on_error,len); result_on_error_str[len]='\0'; } else if (st_on_error != NULL){ len = strlen(st_on_error); result_on_error_str = malloc(len+1); strncpy(result_on_error_str,st_on_error,len); result_on_error_str[len]='\0'; } else{ len = strlen(KAS_ON_ERROR); result_on_error_str = malloc(len+1); strncpy(result_on_error_str,KAS_ON_ERROR,len); result_on_error_str[len]='\0'; } if ( strcasecmp(result_on_error_str,"accept") == 0 ){ result_on_error = LOCAL_SCAN_ACCEPT; } else if ( strcasecmp(result_on_error_str,"reject") == 0 ){ result_on_error = LOCAL_SCAN_REJECT; } else if ( strcasecmp(result_on_error_str,"tempfail") == 0 ){ result_on_error = LOCAL_SCAN_TEMPREJECT; } else{ logger(LOG_ERR,"Invalid value for parameter on_spamtest_error. Will use 'accept'"); result_on_error = LOCAL_SCAN_ACCEPT; } } void free_conf(){ free(result_default_domain); free(result_connect_to); free(result_on_error_str); }