/************************************************* * Exim - an Internet mail transport agent * *************************************************/ /* Code for calling Brightmail AntiSpam. Copyright (c) Tom Kistner 2004 License: GPL */ /* Copyright (c) The Exim Maintainers 2021 - 2022 */ /* SPDX-License-Identifier: GPL-2.0-or-later */ #include "exim.h" #ifdef EXPERIMENTAL_BRIGHTMAIL #include "bmi_spam.h" uschar *bmi_current_optin = NULL; uschar *bmi_process_message(header_line *header_list, int data_fd) { BmiSystem *system = NULL; BmiMessage *message = NULL; BmiError err; BmiErrorLocation err_loc; BmiErrorType err_type; const BmiVerdict *verdict = NULL; FILE *data_file; uschar data_buffer[4096]; uschar localhost[] = "127.0.0.1"; uschar *host_address; uschar *verdicts = NULL; int i,j; err = bmiInitSystem(BMI_VERSION, CS bmi_config_file, &system); if (bmiErrorIsFatal(err) == BMI_TRUE) { err_loc = bmiErrorGetLocation(err); err_type = bmiErrorGetType(err); log_write(0, LOG_PANIC, "bmi error [loc %d type %d]: could not initialize Brightmail system.", (int)err_loc, (int)err_type); return NULL; } err = bmiInitMessage(system, &message); if (bmiErrorIsFatal(err) == BMI_TRUE) { err_loc = bmiErrorGetLocation(err); err_type = bmiErrorGetType(err); log_write(0, LOG_PANIC, "bmi error [loc %d type %d]: could not initialize Brightmail message.", (int)err_loc, (int)err_type); bmiFreeSystem(system); return NULL; } /* Send IP address of sending host */ if (sender_host_address == NULL) host_address = localhost; else host_address = sender_host_address; err = bmiProcessConnection(CS host_address, message); if (bmiErrorIsFatal(err) == BMI_TRUE) { err_loc = bmiErrorGetLocation(err); err_type = bmiErrorGetType(err); log_write(0, LOG_PANIC, "bmi error [loc %d type %d]: bmiProcessConnection() failed (IP %s).", (int)err_loc, (int)err_type, CS host_address); bmiFreeMessage(message); bmiFreeSystem(system); return NULL; }; /* Send envelope sender address */ err = bmiProcessFROM(CS sender_address, message); if (bmiErrorIsFatal(err) == BMI_TRUE) { err_loc = bmiErrorGetLocation(err); err_type = bmiErrorGetType(err); log_write(0, LOG_PANIC, "bmi error [loc %d type %d]: bmiProcessFROM() failed (address %s).", (int)err_loc, (int)err_type, CS sender_address); bmiFreeMessage(message); bmiFreeSystem(system); return NULL; }; /* Send envelope recipients */ for(i=0;ibmi_optin != NULL) && (Ustrlen(r->bmi_optin) > 1)) { debug_printf("passing bmiOptin string: %s\n", r->bmi_optin); bmiOptinInit(&optin); err = bmiOptinMset(optin, r->bmi_optin, ':'); if (bmiErrorIsFatal(err) == BMI_TRUE) { log_write(0, LOG_PANIC|LOG_MAIN, "bmi warning: [loc %d type %d]: bmiOptinMSet() failed (address '%s', string '%s').", (int)err_loc, (int)err_type, CS r->address, CS r->bmi_optin); if (optin != NULL) bmiOptinFree(optin); optin = NULL; }; }; err = bmiAccumulateTO(CS r->address, optin, message); if (optin != NULL) bmiOptinFree(optin); if (bmiErrorIsFatal(err) == BMI_TRUE) { err_loc = bmiErrorGetLocation(err); err_type = bmiErrorGetType(err); log_write(0, LOG_PANIC, "bmi error [loc %d type %d]: bmiAccumulateTO() failed (address %s).", (int)err_loc, (int)err_type, CS r->address); bmiFreeMessage(message); bmiFreeSystem(system); return NULL; }; }; err = bmiEndTO(message); if (bmiErrorIsFatal(err) == BMI_TRUE) { err_loc = bmiErrorGetLocation(err); err_type = bmiErrorGetType(err); log_write(0, LOG_PANIC, "bmi error [loc %d type %d]: bmiEndTO() failed.", (int)err_loc, (int)err_type); bmiFreeMessage(message); bmiFreeSystem(system); return NULL; }; /* Send message headers */ while (header_list != NULL) { /* skip deleted headers */ if (header_list->type == '*') { header_list = header_list->next; continue; }; err = bmiAccumulateHeaders(CCS header_list->text, header_list->slen, message); if (bmiErrorIsFatal(err) == BMI_TRUE) { err_loc = bmiErrorGetLocation(err); err_type = bmiErrorGetType(err); log_write(0, LOG_PANIC, "bmi error [loc %d type %d]: bmiAccumulateHeaders() failed.", (int)err_loc, (int)err_type); bmiFreeMessage(message); bmiFreeSystem(system); return NULL; }; header_list = header_list->next; }; err = bmiEndHeaders(message); if (bmiErrorIsFatal(err) == BMI_TRUE) { err_loc = bmiErrorGetLocation(err); err_type = bmiErrorGetType(err); log_write(0, LOG_PANIC, "bmi error [loc %d type %d]: bmiEndHeaders() failed.", (int)err_loc, (int)err_type); bmiFreeMessage(message); bmiFreeSystem(system); return NULL; }; /* Send body */ data_file = fdopen(data_fd,"r"); do { j = fread(data_buffer, 1, sizeof(data_buffer), data_file); if (j > 0) { err = bmiAccumulateBody(CCS data_buffer, j, message); if (bmiErrorIsFatal(err) == BMI_TRUE) { err_loc = bmiErrorGetLocation(err); err_type = bmiErrorGetType(err); log_write(0, LOG_PANIC, "bmi error [loc %d type %d]: bmiAccumulateBody() failed.", (int)err_loc, (int)err_type); bmiFreeMessage(message); bmiFreeSystem(system); return NULL; }; }; } while (j > 0); err = bmiEndBody(message); if (bmiErrorIsFatal(err) == BMI_TRUE) { err_loc = bmiErrorGetLocation(err); err_type = bmiErrorGetType(err); log_write(0, LOG_PANIC, "bmi error [loc %d type %d]: bmiEndBody() failed.", (int)err_loc, (int)err_type); bmiFreeMessage(message); bmiFreeSystem(system); return NULL; }; /* End message */ err = bmiEndMessage(message); if (bmiErrorIsFatal(err) == BMI_TRUE) { err_loc = bmiErrorGetLocation(err); err_type = bmiErrorGetType(err); log_write(0, LOG_PANIC, "bmi error [loc %d type %d]: bmiEndMessage() failed.", (int)err_loc, (int)err_type); bmiFreeMessage(message); bmiFreeSystem(system); return NULL; }; /* Get store for the verdict string. Since we are processing message data, assume that the verdict is tainted. XXX this should use a growable-string */ verdicts = store_get(1, GET_TAINTED); *verdicts = '\0'; for ( err = bmiAccessFirstVerdict(message, &verdict); verdict; err = bmiAccessNextVerdict(message, verdict, &verdict) ) { char *verdict_str; err = bmiCreateStrFromVerdict(verdict,&verdict_str); if (!store_extend(verdicts, Ustrlen(verdicts)+1, Ustrlen(verdicts)+1+strlen(verdict_str)+1)) { /* can't allocate more store */ return NULL; }; if (*verdicts != '\0') Ustrcat(verdicts, US ":"); Ustrcat(verdicts, US verdict_str); bmiFreeStr(verdict_str); }; DEBUG(D_receive) debug_printf("bmi verdicts: %s\n", verdicts); if (Ustrlen(verdicts) == 0) return NULL; else return verdicts; } int bmi_get_delivery_status(uschar *base64_verdict) { BmiError err; BmiErrorLocation err_loc; BmiErrorType err_type; BmiVerdict *verdict = NULL; int rc = 1; /* deliver by default */ /* always deliver when there is no verdict */ if (base64_verdict == NULL) return 1; /* create verdict from base64 string */ err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict); if (bmiErrorIsFatal(err) == BMI_TRUE) { err_loc = bmiErrorGetLocation(err); err_type = bmiErrorGetType(err); log_write(0, LOG_PANIC, "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict); return 1; }; err = bmiVerdictError(verdict); if (bmiErrorIsFatal(err) == BMI_TRUE) { /* deliver normally due to error */ rc = 1; } else if (bmiVerdictDestinationIsDefault(verdict) == BMI_TRUE) { /* deliver normally */ rc = 1; } else if (bmiVerdictAccessDestination(verdict) == NULL) { /* do not deliver */ rc = 0; } else { /* deliver to alternate location */ rc = 1; }; bmiFreeVerdict(verdict); return rc; } uschar *bmi_get_alt_location(uschar *base64_verdict) { BmiError err; BmiErrorLocation err_loc; BmiErrorType err_type; BmiVerdict *verdict = NULL; uschar *rc = NULL; /* always deliver when there is no verdict */ if (base64_verdict == NULL) return NULL; /* create verdict from base64 string */ err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict); if (bmiErrorIsFatal(err) == BMI_TRUE) { err_loc = bmiErrorGetLocation(err); err_type = bmiErrorGetType(err); log_write(0, LOG_PANIC, "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict); return NULL; }; err = bmiVerdictError(verdict); if (bmiErrorIsFatal(err) == BMI_TRUE) { /* deliver normally due to error */ rc = NULL; } else if (bmiVerdictDestinationIsDefault(verdict) == BMI_TRUE) { /* deliver normally */ rc = NULL; } else if (bmiVerdictAccessDestination(verdict) == NULL) { /* do not deliver */ rc = NULL; } else { /* deliver to alternate location */ rc = store_get(strlen(bmiVerdictAccessDestination(verdict))+1, GET_TAINTED); Ustrcpy(rc, bmiVerdictAccessDestination(verdict)); rc[strlen(bmiVerdictAccessDestination(verdict))] = '\0'; }; bmiFreeVerdict(verdict); return rc; } uschar *bmi_get_base64_verdict(uschar *bmi_local_part, uschar *bmi_domain) { BmiError err; BmiErrorLocation err_loc; BmiErrorType err_type; BmiVerdict *verdict = NULL; const BmiRecipient *recipient = NULL; const char *verdict_str = NULL; uschar *verdict_ptr; uschar *verdict_buffer = NULL; int sep = 0; /* return nothing if there are no verdicts available */ if (bmi_verdicts == NULL) return NULL; /* allocate room for the b64 verdict string */ verdict_buffer = store_get(Ustrlen(bmi_verdicts)+1, GET_TAINTED); /* loop through verdicts */ verdict_ptr = bmi_verdicts; while ((verdict_str = CCS string_nextinlist(&verdict_ptr, &sep, verdict_buffer, Ustrlen(bmi_verdicts)+1)) != NULL) { /* create verdict from base64 string */ err = bmiCreateVerdictFromStr(verdict_str, &verdict); if (bmiErrorIsFatal(err) == BMI_TRUE) { err_loc = bmiErrorGetLocation(err); err_type = bmiErrorGetType(err); log_write(0, LOG_PANIC, "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, verdict_str); return NULL; }; /* loop through rcpts for this verdict */ for ( recipient = bmiVerdictAccessFirstRecipient(verdict); recipient != NULL; recipient = bmiVerdictAccessNextRecipient(verdict, recipient)) { uschar *rcpt_local_part; uschar *rcpt_domain; /* compare address against our subject */ rcpt_local_part = US bmiRecipientAccessAddress(recipient); rcpt_domain = Ustrchr(rcpt_local_part,'@'); if (rcpt_domain == NULL) { rcpt_domain = US""; } else { *rcpt_domain = '\0'; rcpt_domain++; }; if ( (strcmpic(rcpt_local_part, bmi_local_part) == 0) && (strcmpic(rcpt_domain, bmi_domain) == 0) ) { /* found verdict */ bmiFreeVerdict(verdict); return US verdict_str; }; }; bmiFreeVerdict(verdict); }; return NULL; } uschar *bmi_get_base64_tracker_verdict(uschar *base64_verdict) { BmiError err; BmiErrorLocation err_loc; BmiErrorType err_type; BmiVerdict *verdict = NULL; uschar *rc = NULL; /* always deliver when there is no verdict */ if (base64_verdict == NULL) return NULL; /* create verdict from base64 string */ err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict); if (bmiErrorIsFatal(err) == BMI_TRUE) { err_loc = bmiErrorGetLocation(err); err_type = bmiErrorGetType(err); log_write(0, LOG_PANIC, "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict); return NULL; }; /* create old tracker string from verdict */ err = bmiCreateOldStrFromVerdict(verdict, &rc); if (bmiErrorIsFatal(err) == BMI_TRUE) { err_loc = bmiErrorGetLocation(err); err_type = bmiErrorGetType(err); log_write(0, LOG_PANIC, "bmi error [loc %d type %d]: bmiCreateOldStrFromVerdict() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict); return NULL; }; bmiFreeVerdict(verdict); return rc; } int bmi_check_rule(uschar *base64_verdict, uschar *option_list) { BmiError err; BmiErrorLocation err_loc; BmiErrorType err_type; BmiVerdict *verdict = NULL; int rc = 0; uschar *rule_num; uschar *rule_ptr; uschar rule_buffer[32]; int sep = 0; /* no verdict -> no rule fired */ if (base64_verdict == NULL) return 0; /* create verdict from base64 string */ err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict); if (bmiErrorIsFatal(err) == BMI_TRUE) { err_loc = bmiErrorGetLocation(err); err_type = bmiErrorGetType(err); log_write(0, LOG_PANIC, "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict); return 0; }; err = bmiVerdictError(verdict); if (bmiErrorIsFatal(err) == BMI_TRUE) { /* error -> no rule fired */ bmiFreeVerdict(verdict); return 0; } /* loop through numbers */ /* option_list doesn't seem to be expanded so cannot be tainted. If it ever is we will trap here */ rule_ptr = option_list; while ((rule_num = string_nextinlist(&rule_ptr, &sep, rule_buffer, sizeof(rule_buffer)))) { int rule_int = -1; /* try to translate to int */ (void)sscanf(rule_num, "%d", &rule_int); if (rule_int > 0) { debug_printf("checking rule #%d\n", rule_int); /* check if rule fired on the message */ if (bmiVerdictRuleFired(verdict, rule_int) == BMI_TRUE) { debug_printf("rule #%d fired\n", rule_int); rc = 1; break; }; }; }; bmiFreeVerdict(verdict); return rc; }; #endif