SPDX: Mass-update to GPL-2.0-or-later
[exim.git] / src / src / bmi_spam.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Code for calling Brightmail AntiSpam.
6    Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004
7    License: GPL */
8 /* Copyright (c) The Exim Maintainers 2021 - 2022 */
9 /* SPDX-License-Identifier: GPL-2.0-or-later */
10
11 #include "exim.h"
12 #ifdef EXPERIMENTAL_BRIGHTMAIL
13
14 #include "bmi_spam.h"
15
16 uschar *bmi_current_optin = NULL;
17
18 uschar *bmi_process_message(header_line *header_list, int data_fd) {
19   BmiSystem *system = NULL;
20   BmiMessage *message = NULL;
21   BmiError err;
22   BmiErrorLocation err_loc;
23   BmiErrorType err_type;
24   const BmiVerdict *verdict = NULL;
25   FILE *data_file;
26   uschar data_buffer[4096];
27   uschar localhost[] = "127.0.0.1";
28   uschar *host_address;
29   uschar *verdicts = NULL;
30   int i,j;
31
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);
38     return NULL;
39   }
40
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);
48     return NULL;
49   }
50
51   /* Send IP address of sending host */
52   if (sender_host_address == NULL)
53     host_address = localhost;
54   else
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);
64     return NULL;
65   };
66
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);
76     return NULL;
77   };
78
79   /* Send envelope recipients */
80   for(i=0;i<recipients_count;i++) {
81     recipient_item *r = recipients_list + i;
82     BmiOptin *optin = NULL;
83
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);
87       bmiOptinInit(&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);
92         if (optin != NULL)
93           bmiOptinFree(optin);
94         optin = NULL;
95       };
96     };
97
98     err = bmiAccumulateTO(CS r->address, optin, message);
99
100     if (optin != NULL)
101       bmiOptinFree(optin);
102
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);
110       return NULL;
111     };
112   };
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);
121     return NULL;
122   };
123
124   /* Send message headers */
125   while (header_list != NULL) {
126     /* skip deleted headers */
127     if (header_list->type == '*') {
128       header_list = header_list->next;
129       continue;
130     };
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);
139       return NULL;
140     };
141     header_list = header_list->next;
142   };
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);
151     return NULL;
152   };
153
154   /* Send body */
155   data_file = fdopen(data_fd,"r");
156   do {
157     j = fread(data_buffer, 1, sizeof(data_buffer), data_file);
158     if (j > 0) {
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);
167         return NULL;
168       };
169     };
170   } while (j > 0);
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);
179     return NULL;
180   };
181
182
183   /* End message */
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);
192     return NULL;
193   };
194
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  */
197
198   verdicts = store_get(1, GET_TAINTED);
199   *verdicts = '\0';
200
201   for ( err = bmiAccessFirstVerdict(message, &verdict);
202         verdict;
203         err = bmiAccessNextVerdict(message, verdict, &verdict) ) {
204     char *verdict_str;
205
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 */
210       return NULL;
211     };
212     if (*verdicts != '\0')
213       Ustrcat(verdicts, US ":");
214     Ustrcat(verdicts, US verdict_str);
215     bmiFreeStr(verdict_str);
216   };
217
218   DEBUG(D_receive) debug_printf("bmi verdicts: %s\n", verdicts);
219
220   if (Ustrlen(verdicts) == 0)
221     return NULL;
222   else
223     return verdicts;
224 }
225
226
227 int bmi_get_delivery_status(uschar *base64_verdict) {
228   BmiError err;
229   BmiErrorLocation err_loc;
230   BmiErrorType err_type;
231   BmiVerdict *verdict = NULL;
232   int rc = 1;   /* deliver by default */
233
234   /* always deliver when there is no verdict */
235   if (base64_verdict == NULL)
236     return 1;
237
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);
245     return 1;
246   };
247
248   err = bmiVerdictError(verdict);
249   if (bmiErrorIsFatal(err) == BMI_TRUE) {
250     /* deliver normally due to error */
251     rc = 1;
252   }
253   else if (bmiVerdictDestinationIsDefault(verdict) == BMI_TRUE) {
254     /* deliver normally */
255     rc = 1;
256   }
257   else if (bmiVerdictAccessDestination(verdict) == NULL) {
258     /* do not deliver */
259     rc = 0;
260   }
261   else {
262     /* deliver to alternate location */
263     rc = 1;
264   };
265
266   bmiFreeVerdict(verdict);
267   return rc;
268 }
269
270
271 uschar *bmi_get_alt_location(uschar *base64_verdict) {
272   BmiError err;
273   BmiErrorLocation err_loc;
274   BmiErrorType err_type;
275   BmiVerdict *verdict = NULL;
276   uschar *rc = NULL;
277
278   /* always deliver when there is no verdict */
279   if (base64_verdict == NULL)
280     return NULL;
281
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);
289     return NULL;
290   };
291
292   err = bmiVerdictError(verdict);
293   if (bmiErrorIsFatal(err) == BMI_TRUE) {
294     /* deliver normally due to error */
295     rc = NULL;
296   }
297   else if (bmiVerdictDestinationIsDefault(verdict) == BMI_TRUE) {
298     /* deliver normally */
299     rc = NULL;
300   }
301   else if (bmiVerdictAccessDestination(verdict) == NULL) {
302     /* do not deliver */
303     rc = NULL;
304   }
305   else {
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';
310   };
311
312   bmiFreeVerdict(verdict);
313   return rc;
314 }
315
316 uschar *bmi_get_base64_verdict(uschar *bmi_local_part, uschar *bmi_domain) {
317   BmiError err;
318   BmiErrorLocation err_loc;
319   BmiErrorType err_type;
320   BmiVerdict *verdict = NULL;
321   const BmiRecipient *recipient = NULL;
322   const char *verdict_str = NULL;
323   uschar *verdict_ptr;
324   uschar *verdict_buffer = NULL;
325   int sep = 0;
326
327   /* return nothing if there are no verdicts available */
328   if (bmi_verdicts == NULL)
329     return NULL;
330
331   /* allocate room for the b64 verdict string */
332   verdict_buffer = store_get(Ustrlen(bmi_verdicts)+1, GET_TAINTED);
333
334   /* loop through verdicts */
335   verdict_ptr = bmi_verdicts;
336   while ((verdict_str = CCS string_nextinlist(&verdict_ptr, &sep,
337                                           verdict_buffer,
338                                           Ustrlen(bmi_verdicts)+1)) != NULL) {
339
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);
347       return NULL;
348     };
349
350     /* loop through rcpts for this verdict */
351     for ( recipient = bmiVerdictAccessFirstRecipient(verdict);
352           recipient != NULL;
353           recipient = bmiVerdictAccessNextRecipient(verdict, recipient)) {
354       uschar *rcpt_local_part;
355       uschar *rcpt_domain;
356
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) {
361         rcpt_domain = US"";
362       }
363       else {
364         *rcpt_domain = '\0';
365         rcpt_domain++;
366       };
367
368       if ( (strcmpic(rcpt_local_part, bmi_local_part) == 0) &&
369            (strcmpic(rcpt_domain, bmi_domain) == 0) ) {
370         /* found verdict */
371         bmiFreeVerdict(verdict);
372         return US verdict_str;
373       };
374     };
375
376     bmiFreeVerdict(verdict);
377   };
378
379   return NULL;
380 }
381
382
383 uschar *bmi_get_base64_tracker_verdict(uschar *base64_verdict) {
384   BmiError err;
385   BmiErrorLocation err_loc;
386   BmiErrorType err_type;
387   BmiVerdict *verdict = NULL;
388   uschar *rc = NULL;
389
390   /* always deliver when there is no verdict */
391   if (base64_verdict == NULL)
392     return NULL;
393
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);
401     return NULL;
402   };
403
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);
411     return NULL;
412   };
413
414   bmiFreeVerdict(verdict);
415   return rc;
416 }
417
418
419 int bmi_check_rule(uschar *base64_verdict, uschar *option_list) {
420   BmiError err;
421   BmiErrorLocation err_loc;
422   BmiErrorType err_type;
423   BmiVerdict *verdict = NULL;
424   int rc = 0;
425   uschar *rule_num;
426   uschar *rule_ptr;
427   uschar rule_buffer[32];
428   int sep = 0;
429
430
431   /* no verdict -> no rule fired */
432   if (base64_verdict == NULL)
433     return 0;
434
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);
442     return 0;
443   };
444
445   err = bmiVerdictError(verdict);
446   if (bmiErrorIsFatal(err) == BMI_TRUE) {
447     /* error -> no rule fired */
448     bmiFreeVerdict(verdict);
449     return 0;
450   }
451
452   /* loop through numbers */
453   /* option_list doesn't seem to be expanded so cannot be tainted.  If it ever is we
454   will trap here */
455   rule_ptr = option_list;
456   while ((rule_num = string_nextinlist(&rule_ptr, &sep,
457                                        rule_buffer, sizeof(rule_buffer)))) {
458     int rule_int = -1;
459
460     /* try to translate to int */
461     (void)sscanf(rule_num, "%d", &rule_int);
462     if (rule_int > 0) {
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);
467         rc = 1;
468         break;
469       };
470     };
471   };
472
473   bmiFreeVerdict(verdict);
474   return rc;
475 };
476
477 #endif