1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Code for calling Brightmail AntiSpam.
6 Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004
8 /* Copyright (c) The Exim Maintainers 2021 - 2022 */
9 /* SPDX-License-Identifier: GPL-2.0-or-later */
12 #ifdef EXPERIMENTAL_BRIGHTMAIL
16 uschar *bmi_current_optin = NULL;
18 uschar *bmi_process_message(header_line *header_list, int data_fd) {
19 BmiSystem *system = NULL;
20 BmiMessage *message = NULL;
22 BmiErrorLocation err_loc;
23 BmiErrorType err_type;
24 const BmiVerdict *verdict = NULL;
26 uschar data_buffer[4096];
27 uschar localhost[] = "127.0.0.1";
29 uschar *verdicts = NULL;
32 err = bmiInitSystem(BMI_VERSION, CS bmi_config_file, &system);
33 if (bmiErrorIsFatal(err) == BMI_TRUE) {
34 err_loc = bmiErrorGetLocation(err);
35 err_type = bmiErrorGetType(err);
36 log_write(0, LOG_PANIC,
37 "bmi error [loc %d type %d]: could not initialize Brightmail system.", (int)err_loc, (int)err_type);
41 err = bmiInitMessage(system, &message);
42 if (bmiErrorIsFatal(err) == BMI_TRUE) {
43 err_loc = bmiErrorGetLocation(err);
44 err_type = bmiErrorGetType(err);
45 log_write(0, LOG_PANIC,
46 "bmi error [loc %d type %d]: could not initialize Brightmail message.", (int)err_loc, (int)err_type);
47 bmiFreeSystem(system);
51 /* Send IP address of sending host */
52 if (sender_host_address == NULL)
53 host_address = localhost;
55 host_address = sender_host_address;
56 err = bmiProcessConnection(CS host_address, message);
57 if (bmiErrorIsFatal(err) == BMI_TRUE) {
58 err_loc = bmiErrorGetLocation(err);
59 err_type = bmiErrorGetType(err);
60 log_write(0, LOG_PANIC,
61 "bmi error [loc %d type %d]: bmiProcessConnection() failed (IP %s).", (int)err_loc, (int)err_type, CS host_address);
62 bmiFreeMessage(message);
63 bmiFreeSystem(system);
67 /* Send envelope sender address */
68 err = bmiProcessFROM(CS sender_address, message);
69 if (bmiErrorIsFatal(err) == BMI_TRUE) {
70 err_loc = bmiErrorGetLocation(err);
71 err_type = bmiErrorGetType(err);
72 log_write(0, LOG_PANIC,
73 "bmi error [loc %d type %d]: bmiProcessFROM() failed (address %s).", (int)err_loc, (int)err_type, CS sender_address);
74 bmiFreeMessage(message);
75 bmiFreeSystem(system);
79 /* Send envelope recipients */
80 for(i=0;i<recipients_count;i++) {
81 recipient_item *r = recipients_list + i;
82 BmiOptin *optin = NULL;
84 /* create optin object if optin string is given */
85 if ((r->bmi_optin != NULL) && (Ustrlen(r->bmi_optin) > 1)) {
86 debug_printf("passing bmiOptin string: %s\n", r->bmi_optin);
88 err = bmiOptinMset(optin, r->bmi_optin, ':');
89 if (bmiErrorIsFatal(err) == BMI_TRUE) {
90 log_write(0, LOG_PANIC|LOG_MAIN,
91 "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);
98 err = bmiAccumulateTO(CS r->address, optin, message);
103 if (bmiErrorIsFatal(err) == BMI_TRUE) {
104 err_loc = bmiErrorGetLocation(err);
105 err_type = bmiErrorGetType(err);
106 log_write(0, LOG_PANIC,
107 "bmi error [loc %d type %d]: bmiAccumulateTO() failed (address %s).", (int)err_loc, (int)err_type, CS r->address);
108 bmiFreeMessage(message);
109 bmiFreeSystem(system);
113 err = bmiEndTO(message);
114 if (bmiErrorIsFatal(err) == BMI_TRUE) {
115 err_loc = bmiErrorGetLocation(err);
116 err_type = bmiErrorGetType(err);
117 log_write(0, LOG_PANIC,
118 "bmi error [loc %d type %d]: bmiEndTO() failed.", (int)err_loc, (int)err_type);
119 bmiFreeMessage(message);
120 bmiFreeSystem(system);
124 /* Send message headers */
125 while (header_list != NULL) {
126 /* skip deleted headers */
127 if (header_list->type == '*') {
128 header_list = header_list->next;
131 err = bmiAccumulateHeaders(CCS header_list->text, header_list->slen, message);
132 if (bmiErrorIsFatal(err) == BMI_TRUE) {
133 err_loc = bmiErrorGetLocation(err);
134 err_type = bmiErrorGetType(err);
135 log_write(0, LOG_PANIC,
136 "bmi error [loc %d type %d]: bmiAccumulateHeaders() failed.", (int)err_loc, (int)err_type);
137 bmiFreeMessage(message);
138 bmiFreeSystem(system);
141 header_list = header_list->next;
143 err = bmiEndHeaders(message);
144 if (bmiErrorIsFatal(err) == BMI_TRUE) {
145 err_loc = bmiErrorGetLocation(err);
146 err_type = bmiErrorGetType(err);
147 log_write(0, LOG_PANIC,
148 "bmi error [loc %d type %d]: bmiEndHeaders() failed.", (int)err_loc, (int)err_type);
149 bmiFreeMessage(message);
150 bmiFreeSystem(system);
155 data_file = fdopen(data_fd,"r");
157 j = fread(data_buffer, 1, sizeof(data_buffer), data_file);
159 err = bmiAccumulateBody(CCS data_buffer, j, message);
160 if (bmiErrorIsFatal(err) == BMI_TRUE) {
161 err_loc = bmiErrorGetLocation(err);
162 err_type = bmiErrorGetType(err);
163 log_write(0, LOG_PANIC,
164 "bmi error [loc %d type %d]: bmiAccumulateBody() failed.", (int)err_loc, (int)err_type);
165 bmiFreeMessage(message);
166 bmiFreeSystem(system);
171 err = bmiEndBody(message);
172 if (bmiErrorIsFatal(err) == BMI_TRUE) {
173 err_loc = bmiErrorGetLocation(err);
174 err_type = bmiErrorGetType(err);
175 log_write(0, LOG_PANIC,
176 "bmi error [loc %d type %d]: bmiEndBody() failed.", (int)err_loc, (int)err_type);
177 bmiFreeMessage(message);
178 bmiFreeSystem(system);
184 err = bmiEndMessage(message);
185 if (bmiErrorIsFatal(err) == BMI_TRUE) {
186 err_loc = bmiErrorGetLocation(err);
187 err_type = bmiErrorGetType(err);
188 log_write(0, LOG_PANIC,
189 "bmi error [loc %d type %d]: bmiEndMessage() failed.", (int)err_loc, (int)err_type);
190 bmiFreeMessage(message);
191 bmiFreeSystem(system);
195 /* Get store for the verdict string. Since we are processing message data, assume that
196 the verdict is tainted. XXX this should use a growable-string */
198 verdicts = store_get(1, GET_TAINTED);
201 for ( err = bmiAccessFirstVerdict(message, &verdict);
203 err = bmiAccessNextVerdict(message, verdict, &verdict) ) {
206 err = bmiCreateStrFromVerdict(verdict,&verdict_str);
207 if (!store_extend(verdicts,
208 Ustrlen(verdicts)+1, Ustrlen(verdicts)+1+strlen(verdict_str)+1)) {
209 /* can't allocate more store */
212 if (*verdicts != '\0')
213 Ustrcat(verdicts, US ":");
214 Ustrcat(verdicts, US verdict_str);
215 bmiFreeStr(verdict_str);
218 DEBUG(D_receive) debug_printf("bmi verdicts: %s\n", verdicts);
220 if (Ustrlen(verdicts) == 0)
227 int bmi_get_delivery_status(uschar *base64_verdict) {
229 BmiErrorLocation err_loc;
230 BmiErrorType err_type;
231 BmiVerdict *verdict = NULL;
232 int rc = 1; /* deliver by default */
234 /* always deliver when there is no verdict */
235 if (base64_verdict == NULL)
238 /* create verdict from base64 string */
239 err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict);
240 if (bmiErrorIsFatal(err) == BMI_TRUE) {
241 err_loc = bmiErrorGetLocation(err);
242 err_type = bmiErrorGetType(err);
243 log_write(0, LOG_PANIC,
244 "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict);
248 err = bmiVerdictError(verdict);
249 if (bmiErrorIsFatal(err) == BMI_TRUE) {
250 /* deliver normally due to error */
253 else if (bmiVerdictDestinationIsDefault(verdict) == BMI_TRUE) {
254 /* deliver normally */
257 else if (bmiVerdictAccessDestination(verdict) == NULL) {
262 /* deliver to alternate location */
266 bmiFreeVerdict(verdict);
271 uschar *bmi_get_alt_location(uschar *base64_verdict) {
273 BmiErrorLocation err_loc;
274 BmiErrorType err_type;
275 BmiVerdict *verdict = NULL;
278 /* always deliver when there is no verdict */
279 if (base64_verdict == NULL)
282 /* create verdict from base64 string */
283 err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict);
284 if (bmiErrorIsFatal(err) == BMI_TRUE) {
285 err_loc = bmiErrorGetLocation(err);
286 err_type = bmiErrorGetType(err);
287 log_write(0, LOG_PANIC,
288 "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict);
292 err = bmiVerdictError(verdict);
293 if (bmiErrorIsFatal(err) == BMI_TRUE) {
294 /* deliver normally due to error */
297 else if (bmiVerdictDestinationIsDefault(verdict) == BMI_TRUE) {
298 /* deliver normally */
301 else if (bmiVerdictAccessDestination(verdict) == NULL) {
306 /* deliver to alternate location */
307 rc = store_get(strlen(bmiVerdictAccessDestination(verdict))+1, GET_TAINTED);
308 Ustrcpy(rc, bmiVerdictAccessDestination(verdict));
309 rc[strlen(bmiVerdictAccessDestination(verdict))] = '\0';
312 bmiFreeVerdict(verdict);
316 uschar *bmi_get_base64_verdict(uschar *bmi_local_part, uschar *bmi_domain) {
318 BmiErrorLocation err_loc;
319 BmiErrorType err_type;
320 BmiVerdict *verdict = NULL;
321 const BmiRecipient *recipient = NULL;
322 const char *verdict_str = NULL;
324 uschar *verdict_buffer = NULL;
327 /* return nothing if there are no verdicts available */
328 if (bmi_verdicts == NULL)
331 /* allocate room for the b64 verdict string */
332 verdict_buffer = store_get(Ustrlen(bmi_verdicts)+1, GET_TAINTED);
334 /* loop through verdicts */
335 verdict_ptr = bmi_verdicts;
336 while ((verdict_str = CCS string_nextinlist(&verdict_ptr, &sep,
338 Ustrlen(bmi_verdicts)+1)) != NULL) {
340 /* create verdict from base64 string */
341 err = bmiCreateVerdictFromStr(verdict_str, &verdict);
342 if (bmiErrorIsFatal(err) == BMI_TRUE) {
343 err_loc = bmiErrorGetLocation(err);
344 err_type = bmiErrorGetType(err);
345 log_write(0, LOG_PANIC,
346 "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, verdict_str);
350 /* loop through rcpts for this verdict */
351 for ( recipient = bmiVerdictAccessFirstRecipient(verdict);
353 recipient = bmiVerdictAccessNextRecipient(verdict, recipient)) {
354 uschar *rcpt_local_part;
357 /* compare address against our subject */
358 rcpt_local_part = US bmiRecipientAccessAddress(recipient);
359 rcpt_domain = Ustrchr(rcpt_local_part,'@');
360 if (rcpt_domain == NULL) {
368 if ( (strcmpic(rcpt_local_part, bmi_local_part) == 0) &&
369 (strcmpic(rcpt_domain, bmi_domain) == 0) ) {
371 bmiFreeVerdict(verdict);
372 return US verdict_str;
376 bmiFreeVerdict(verdict);
383 uschar *bmi_get_base64_tracker_verdict(uschar *base64_verdict) {
385 BmiErrorLocation err_loc;
386 BmiErrorType err_type;
387 BmiVerdict *verdict = NULL;
390 /* always deliver when there is no verdict */
391 if (base64_verdict == NULL)
394 /* create verdict from base64 string */
395 err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict);
396 if (bmiErrorIsFatal(err) == BMI_TRUE) {
397 err_loc = bmiErrorGetLocation(err);
398 err_type = bmiErrorGetType(err);
399 log_write(0, LOG_PANIC,
400 "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict);
404 /* create old tracker string from verdict */
405 err = bmiCreateOldStrFromVerdict(verdict, &rc);
406 if (bmiErrorIsFatal(err) == BMI_TRUE) {
407 err_loc = bmiErrorGetLocation(err);
408 err_type = bmiErrorGetType(err);
409 log_write(0, LOG_PANIC,
410 "bmi error [loc %d type %d]: bmiCreateOldStrFromVerdict() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict);
414 bmiFreeVerdict(verdict);
419 int bmi_check_rule(uschar *base64_verdict, uschar *option_list) {
421 BmiErrorLocation err_loc;
422 BmiErrorType err_type;
423 BmiVerdict *verdict = NULL;
427 uschar rule_buffer[32];
431 /* no verdict -> no rule fired */
432 if (base64_verdict == NULL)
435 /* create verdict from base64 string */
436 err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict);
437 if (bmiErrorIsFatal(err) == BMI_TRUE) {
438 err_loc = bmiErrorGetLocation(err);
439 err_type = bmiErrorGetType(err);
440 log_write(0, LOG_PANIC,
441 "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict);
445 err = bmiVerdictError(verdict);
446 if (bmiErrorIsFatal(err) == BMI_TRUE) {
447 /* error -> no rule fired */
448 bmiFreeVerdict(verdict);
452 /* loop through numbers */
453 /* option_list doesn't seem to be expanded so cannot be tainted. If it ever is we
455 rule_ptr = option_list;
456 while ((rule_num = string_nextinlist(&rule_ptr, &sep,
457 rule_buffer, sizeof(rule_buffer)))) {
460 /* try to translate to int */
461 (void)sscanf(rule_num, "%d", &rule_int);
463 debug_printf("checking rule #%d\n", rule_int);
464 /* check if rule fired on the message */
465 if (bmiVerdictRuleFired(verdict, rule_int) == BMI_TRUE) {
466 debug_printf("rule #%d fired\n", rule_int);
473 bmiFreeVerdict(verdict);