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