Copyright updates:
[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
10 #include "exim.h"
11 #ifdef EXPERIMENTAL_BRIGHTMAIL
12
13 #include "bmi_spam.h"
14
15 uschar *bmi_current_optin = NULL;
16
17 uschar *bmi_process_message(header_line *header_list, int data_fd) {
18   BmiSystem *system = NULL;
19   BmiMessage *message = NULL;
20   BmiError err;
21   BmiErrorLocation err_loc;
22   BmiErrorType err_type;
23   const BmiVerdict *verdict = NULL;
24   FILE *data_file;
25   uschar data_buffer[4096];
26   uschar localhost[] = "127.0.0.1";
27   uschar *host_address;
28   uschar *verdicts = NULL;
29   int i,j;
30
31   err = bmiInitSystem(BMI_VERSION, CS bmi_config_file, &system);
32   if (bmiErrorIsFatal(err) == BMI_TRUE) {
33     err_loc = bmiErrorGetLocation(err);
34     err_type = bmiErrorGetType(err);
35     log_write(0, LOG_PANIC,
36                "bmi error [loc %d type %d]: could not initialize Brightmail system.", (int)err_loc, (int)err_type);
37     return NULL;
38   }
39
40   err = bmiInitMessage(system, &message);
41   if (bmiErrorIsFatal(err) == BMI_TRUE) {
42     err_loc = bmiErrorGetLocation(err);
43     err_type = bmiErrorGetType(err);
44     log_write(0, LOG_PANIC,
45                "bmi error [loc %d type %d]: could not initialize Brightmail message.", (int)err_loc, (int)err_type);
46     bmiFreeSystem(system);
47     return NULL;
48   }
49
50   /* Send IP address of sending host */
51   if (sender_host_address == NULL)
52     host_address = localhost;
53   else
54     host_address = sender_host_address;
55   err = bmiProcessConnection(CS host_address, message);
56   if (bmiErrorIsFatal(err) == BMI_TRUE) {
57     err_loc = bmiErrorGetLocation(err);
58     err_type = bmiErrorGetType(err);
59     log_write(0, LOG_PANIC,
60                "bmi error [loc %d type %d]: bmiProcessConnection() failed (IP %s).", (int)err_loc, (int)err_type, CS host_address);
61     bmiFreeMessage(message);
62     bmiFreeSystem(system);
63     return NULL;
64   };
65
66   /* Send envelope sender address */
67   err = bmiProcessFROM(CS sender_address, message);
68   if (bmiErrorIsFatal(err) == BMI_TRUE) {
69     err_loc = bmiErrorGetLocation(err);
70     err_type = bmiErrorGetType(err);
71     log_write(0, LOG_PANIC,
72                "bmi error [loc %d type %d]: bmiProcessFROM() failed (address %s).", (int)err_loc, (int)err_type, CS sender_address);
73     bmiFreeMessage(message);
74     bmiFreeSystem(system);
75     return NULL;
76   };
77
78   /* Send envelope recipients */
79   for(i=0;i<recipients_count;i++) {
80     recipient_item *r = recipients_list + i;
81     BmiOptin *optin = NULL;
82
83     /* create optin object if optin string is given */
84     if ((r->bmi_optin != NULL) && (Ustrlen(r->bmi_optin) > 1)) {
85       debug_printf("passing bmiOptin string: %s\n", r->bmi_optin);
86       bmiOptinInit(&optin);
87       err = bmiOptinMset(optin, r->bmi_optin, ':');
88       if (bmiErrorIsFatal(err) == BMI_TRUE) {
89         log_write(0, LOG_PANIC|LOG_MAIN,
90                    "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);
91         if (optin != NULL)
92           bmiOptinFree(optin);
93         optin = NULL;
94       };
95     };
96
97     err = bmiAccumulateTO(CS r->address, optin, message);
98
99     if (optin != NULL)
100       bmiOptinFree(optin);
101
102     if (bmiErrorIsFatal(err) == BMI_TRUE) {
103       err_loc = bmiErrorGetLocation(err);
104       err_type = bmiErrorGetType(err);
105       log_write(0, LOG_PANIC,
106                  "bmi error [loc %d type %d]: bmiAccumulateTO() failed (address %s).", (int)err_loc, (int)err_type, CS r->address);
107       bmiFreeMessage(message);
108       bmiFreeSystem(system);
109       return NULL;
110     };
111   };
112   err = bmiEndTO(message);
113   if (bmiErrorIsFatal(err) == BMI_TRUE) {
114     err_loc = bmiErrorGetLocation(err);
115     err_type = bmiErrorGetType(err);
116     log_write(0, LOG_PANIC,
117                "bmi error [loc %d type %d]: bmiEndTO() failed.", (int)err_loc, (int)err_type);
118     bmiFreeMessage(message);
119     bmiFreeSystem(system);
120     return NULL;
121   };
122
123   /* Send message headers */
124   while (header_list != NULL) {
125     /* skip deleted headers */
126     if (header_list->type == '*') {
127       header_list = header_list->next;
128       continue;
129     };
130     err = bmiAccumulateHeaders(CCS header_list->text, header_list->slen, message);
131     if (bmiErrorIsFatal(err) == BMI_TRUE) {
132       err_loc = bmiErrorGetLocation(err);
133       err_type = bmiErrorGetType(err);
134       log_write(0, LOG_PANIC,
135                  "bmi error [loc %d type %d]: bmiAccumulateHeaders() failed.", (int)err_loc, (int)err_type);
136       bmiFreeMessage(message);
137       bmiFreeSystem(system);
138       return NULL;
139     };
140     header_list = header_list->next;
141   };
142   err = bmiEndHeaders(message);
143   if (bmiErrorIsFatal(err) == BMI_TRUE) {
144     err_loc = bmiErrorGetLocation(err);
145     err_type = bmiErrorGetType(err);
146     log_write(0, LOG_PANIC,
147                "bmi error [loc %d type %d]: bmiEndHeaders() failed.", (int)err_loc, (int)err_type);
148     bmiFreeMessage(message);
149     bmiFreeSystem(system);
150     return NULL;
151   };
152
153   /* Send body */
154   data_file = fdopen(data_fd,"r");
155   do {
156     j = fread(data_buffer, 1, sizeof(data_buffer), data_file);
157     if (j > 0) {
158       err = bmiAccumulateBody(CCS data_buffer, j, message);
159       if (bmiErrorIsFatal(err) == BMI_TRUE) {
160         err_loc = bmiErrorGetLocation(err);
161         err_type = bmiErrorGetType(err);
162         log_write(0, LOG_PANIC,
163                    "bmi error [loc %d type %d]: bmiAccumulateBody() failed.", (int)err_loc, (int)err_type);
164         bmiFreeMessage(message);
165         bmiFreeSystem(system);
166         return NULL;
167       };
168     };
169   } while (j > 0);
170   err = bmiEndBody(message);
171   if (bmiErrorIsFatal(err) == BMI_TRUE) {
172     err_loc = bmiErrorGetLocation(err);
173     err_type = bmiErrorGetType(err);
174     log_write(0, LOG_PANIC,
175                "bmi error [loc %d type %d]: bmiEndBody() failed.", (int)err_loc, (int)err_type);
176     bmiFreeMessage(message);
177     bmiFreeSystem(system);
178     return NULL;
179   };
180
181
182   /* End message */
183   err = bmiEndMessage(message);
184   if (bmiErrorIsFatal(err) == BMI_TRUE) {
185     err_loc = bmiErrorGetLocation(err);
186     err_type = bmiErrorGetType(err);
187     log_write(0, LOG_PANIC,
188                "bmi error [loc %d type %d]: bmiEndMessage() failed.", (int)err_loc, (int)err_type);
189     bmiFreeMessage(message);
190     bmiFreeSystem(system);
191     return NULL;
192   };
193
194   /* Get store for the verdict string.  Since we are processing message data, assume that
195   the verdict is tainted.  XXX this should use a growable-string  */
196
197   verdicts = store_get(1, GET_TAINTED);
198   *verdicts = '\0';
199
200   for ( err = bmiAccessFirstVerdict(message, &verdict);
201         verdict;
202         err = bmiAccessNextVerdict(message, verdict, &verdict) ) {
203     char *verdict_str;
204
205     err = bmiCreateStrFromVerdict(verdict,&verdict_str);
206     if (!store_extend(verdicts,
207           Ustrlen(verdicts)+1, Ustrlen(verdicts)+1+strlen(verdict_str)+1)) {
208       /* can't allocate more store */
209       return NULL;
210     };
211     if (*verdicts != '\0')
212       Ustrcat(verdicts, US ":");
213     Ustrcat(verdicts, US verdict_str);
214     bmiFreeStr(verdict_str);
215   };
216
217   DEBUG(D_receive) debug_printf("bmi verdicts: %s\n", verdicts);
218
219   if (Ustrlen(verdicts) == 0)
220     return NULL;
221   else
222     return verdicts;
223 }
224
225
226 int bmi_get_delivery_status(uschar *base64_verdict) {
227   BmiError err;
228   BmiErrorLocation err_loc;
229   BmiErrorType err_type;
230   BmiVerdict *verdict = NULL;
231   int rc = 1;   /* deliver by default */
232
233   /* always deliver when there is no verdict */
234   if (base64_verdict == NULL)
235     return 1;
236
237   /* create verdict from base64 string */
238   err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict);
239   if (bmiErrorIsFatal(err) == BMI_TRUE) {
240     err_loc = bmiErrorGetLocation(err);
241     err_type = bmiErrorGetType(err);
242     log_write(0, LOG_PANIC,
243                "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict);
244     return 1;
245   };
246
247   err = bmiVerdictError(verdict);
248   if (bmiErrorIsFatal(err) == BMI_TRUE) {
249     /* deliver normally due to error */
250     rc = 1;
251   }
252   else if (bmiVerdictDestinationIsDefault(verdict) == BMI_TRUE) {
253     /* deliver normally */
254     rc = 1;
255   }
256   else if (bmiVerdictAccessDestination(verdict) == NULL) {
257     /* do not deliver */
258     rc = 0;
259   }
260   else {
261     /* deliver to alternate location */
262     rc = 1;
263   };
264
265   bmiFreeVerdict(verdict);
266   return rc;
267 }
268
269
270 uschar *bmi_get_alt_location(uschar *base64_verdict) {
271   BmiError err;
272   BmiErrorLocation err_loc;
273   BmiErrorType err_type;
274   BmiVerdict *verdict = NULL;
275   uschar *rc = NULL;
276
277   /* always deliver when there is no verdict */
278   if (base64_verdict == NULL)
279     return NULL;
280
281   /* create verdict from base64 string */
282   err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict);
283   if (bmiErrorIsFatal(err) == BMI_TRUE) {
284     err_loc = bmiErrorGetLocation(err);
285     err_type = bmiErrorGetType(err);
286     log_write(0, LOG_PANIC,
287                "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict);
288     return NULL;
289   };
290
291   err = bmiVerdictError(verdict);
292   if (bmiErrorIsFatal(err) == BMI_TRUE) {
293     /* deliver normally due to error */
294     rc = NULL;
295   }
296   else if (bmiVerdictDestinationIsDefault(verdict) == BMI_TRUE) {
297     /* deliver normally */
298     rc = NULL;
299   }
300   else if (bmiVerdictAccessDestination(verdict) == NULL) {
301     /* do not deliver */
302     rc = NULL;
303   }
304   else {
305     /* deliver to alternate location */
306     rc = store_get(strlen(bmiVerdictAccessDestination(verdict))+1, GET_TAINTED);
307     Ustrcpy(rc, bmiVerdictAccessDestination(verdict));
308     rc[strlen(bmiVerdictAccessDestination(verdict))] = '\0';
309   };
310
311   bmiFreeVerdict(verdict);
312   return rc;
313 }
314
315 uschar *bmi_get_base64_verdict(uschar *bmi_local_part, uschar *bmi_domain) {
316   BmiError err;
317   BmiErrorLocation err_loc;
318   BmiErrorType err_type;
319   BmiVerdict *verdict = NULL;
320   const BmiRecipient *recipient = NULL;
321   const char *verdict_str = NULL;
322   uschar *verdict_ptr;
323   uschar *verdict_buffer = NULL;
324   int sep = 0;
325
326   /* return nothing if there are no verdicts available */
327   if (bmi_verdicts == NULL)
328     return NULL;
329
330   /* allocate room for the b64 verdict string */
331   verdict_buffer = store_get(Ustrlen(bmi_verdicts)+1, GET_TAINTED);
332
333   /* loop through verdicts */
334   verdict_ptr = bmi_verdicts;
335   while ((verdict_str = CCS string_nextinlist(&verdict_ptr, &sep,
336                                           verdict_buffer,
337                                           Ustrlen(bmi_verdicts)+1)) != NULL) {
338
339     /* create verdict from base64 string */
340     err = bmiCreateVerdictFromStr(verdict_str, &verdict);
341     if (bmiErrorIsFatal(err) == BMI_TRUE) {
342       err_loc = bmiErrorGetLocation(err);
343       err_type = bmiErrorGetType(err);
344       log_write(0, LOG_PANIC,
345                  "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, verdict_str);
346       return NULL;
347     };
348
349     /* loop through rcpts for this verdict */
350     for ( recipient = bmiVerdictAccessFirstRecipient(verdict);
351           recipient != NULL;
352           recipient = bmiVerdictAccessNextRecipient(verdict, recipient)) {
353       uschar *rcpt_local_part;
354       uschar *rcpt_domain;
355
356       /* compare address against our subject */
357       rcpt_local_part = US bmiRecipientAccessAddress(recipient);
358       rcpt_domain = Ustrchr(rcpt_local_part,'@');
359       if (rcpt_domain == NULL) {
360         rcpt_domain = US"";
361       }
362       else {
363         *rcpt_domain = '\0';
364         rcpt_domain++;
365       };
366
367       if ( (strcmpic(rcpt_local_part, bmi_local_part) == 0) &&
368            (strcmpic(rcpt_domain, bmi_domain) == 0) ) {
369         /* found verdict */
370         bmiFreeVerdict(verdict);
371         return US verdict_str;
372       };
373     };
374
375     bmiFreeVerdict(verdict);
376   };
377
378   return NULL;
379 }
380
381
382 uschar *bmi_get_base64_tracker_verdict(uschar *base64_verdict) {
383   BmiError err;
384   BmiErrorLocation err_loc;
385   BmiErrorType err_type;
386   BmiVerdict *verdict = NULL;
387   uschar *rc = NULL;
388
389   /* always deliver when there is no verdict */
390   if (base64_verdict == NULL)
391     return NULL;
392
393   /* create verdict from base64 string */
394   err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict);
395   if (bmiErrorIsFatal(err) == BMI_TRUE) {
396     err_loc = bmiErrorGetLocation(err);
397     err_type = bmiErrorGetType(err);
398     log_write(0, LOG_PANIC,
399                "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict);
400     return NULL;
401   };
402
403   /* create old tracker string from verdict */
404   err = bmiCreateOldStrFromVerdict(verdict, &rc);
405   if (bmiErrorIsFatal(err) == BMI_TRUE) {
406     err_loc = bmiErrorGetLocation(err);
407     err_type = bmiErrorGetType(err);
408     log_write(0, LOG_PANIC,
409                "bmi error [loc %d type %d]: bmiCreateOldStrFromVerdict() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict);
410     return NULL;
411   };
412
413   bmiFreeVerdict(verdict);
414   return rc;
415 }
416
417
418 int bmi_check_rule(uschar *base64_verdict, uschar *option_list) {
419   BmiError err;
420   BmiErrorLocation err_loc;
421   BmiErrorType err_type;
422   BmiVerdict *verdict = NULL;
423   int rc = 0;
424   uschar *rule_num;
425   uschar *rule_ptr;
426   uschar rule_buffer[32];
427   int sep = 0;
428
429
430   /* no verdict -> no rule fired */
431   if (base64_verdict == NULL)
432     return 0;
433
434   /* create verdict from base64 string */
435   err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict);
436   if (bmiErrorIsFatal(err) == BMI_TRUE) {
437     err_loc = bmiErrorGetLocation(err);
438     err_type = bmiErrorGetType(err);
439     log_write(0, LOG_PANIC,
440                "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict);
441     return 0;
442   };
443
444   err = bmiVerdictError(verdict);
445   if (bmiErrorIsFatal(err) == BMI_TRUE) {
446     /* error -> no rule fired */
447     bmiFreeVerdict(verdict);
448     return 0;
449   }
450
451   /* loop through numbers */
452   /* option_list doesn't seem to be expanded so cannot be tainted.  If it ever is we
453   will trap here */
454   rule_ptr = option_list;
455   while ((rule_num = string_nextinlist(&rule_ptr, &sep,
456                                        rule_buffer, sizeof(rule_buffer)))) {
457     int rule_int = -1;
458
459     /* try to translate to int */
460     (void)sscanf(rule_num, "%d", &rule_int);
461     if (rule_int > 0) {
462       debug_printf("checking rule #%d\n", rule_int);
463       /* check if rule fired on the message */
464       if (bmiVerdictRuleFired(verdict, rule_int) == BMI_TRUE) {
465         debug_printf("rule #%d fired\n", rule_int);
466         rc = 1;
467         break;
468       };
469     };
470   };
471
472   bmiFreeVerdict(verdict);
473   return rc;
474 };
475
476 #endif