(1) Last-minute sieve patch (updates to latest spec).
[exim.git] / src / src / bmi_spam.c
1 /* $Cambridge: exim/src/src/bmi_spam.c,v 1.3 2005/02/17 11:58:25 ph10 Exp $ */
2
3 /*************************************************
4 *     Exim - an Internet mail transport agent    *
5 *************************************************/
6
7 /* Code for calling Brightmail AntiSpam.
8    Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004
9    License: GPL */
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, (char *)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((char *)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, (char *)host_address);
62     bmiFreeMessage(message);
63     bmiFreeSystem(system);
64     return NULL;
65   };
66
67   /* Send envelope sender address */
68   err = bmiProcessFROM((char *)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, (char *)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, (char *)r->address, (char *)r->bmi_optin);
92         if (optin != NULL)
93           bmiOptinFree(optin);
94         optin = NULL;
95       };
96     };
97
98     err = bmiAccumulateTO((char *)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, (char *)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((const char *)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((const char *)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 */
196   verdicts = store_get(1);
197   *verdicts = '\0';
198
199   for ( err = bmiAccessFirstVerdict(message, &verdict);
200         verdict != NULL;
201         err = bmiAccessNextVerdict(message, verdict, &verdict) ) {
202     char *verdict_str;
203
204     err = bmiCreateStrFromVerdict(verdict,&verdict_str);
205     if (!store_extend(verdicts, Ustrlen(verdicts)+1, Ustrlen(verdicts)+1+strlen(verdict_str)+1)) {
206       /* can't allocate more store */
207       return NULL;
208     };
209     if (*verdicts != '\0')
210       Ustrcat(verdicts, US ":");
211     Ustrcat(verdicts, US verdict_str);
212     bmiFreeStr(verdict_str);
213   };
214
215   DEBUG(D_receive) debug_printf("bmi verdicts: %s\n", verdicts);
216
217   if (Ustrlen(verdicts) == 0)
218     return NULL;
219   else
220     return verdicts;
221 }
222
223
224 int bmi_get_delivery_status(uschar *base64_verdict) {
225   BmiError err;
226   BmiErrorLocation err_loc;
227   BmiErrorType err_type;
228   BmiVerdict *verdict = NULL;
229   int rc = 1;   /* deliver by default */
230
231   /* always deliver when there is no verdict */
232   if (base64_verdict == NULL)
233     return 1;
234
235   /* create verdict from base64 string */
236   err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict);
237   if (bmiErrorIsFatal(err) == BMI_TRUE) {
238     err_loc = bmiErrorGetLocation(err);
239     err_type = bmiErrorGetType(err);
240     log_write(0, LOG_PANIC,
241                "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict);
242     return 1;
243   };
244
245   err = bmiVerdictError(verdict);
246   if (bmiErrorIsFatal(err) == BMI_TRUE) {
247     /* deliver normally due to error */
248     rc = 1;
249   }
250   else if (bmiVerdictDestinationIsDefault(verdict) == BMI_TRUE) {
251     /* deliver normally */
252     rc = 1;
253   }
254   else if (bmiVerdictAccessDestination(verdict) == NULL) {
255     /* do not deliver */
256     rc = 0;
257   }
258   else {
259     /* deliver to alternate location */
260     rc = 1;
261   };
262
263   bmiFreeVerdict(verdict);
264   return rc;
265 }
266
267
268 uschar *bmi_get_alt_location(uschar *base64_verdict) {
269   BmiError err;
270   BmiErrorLocation err_loc;
271   BmiErrorType err_type;
272   BmiVerdict *verdict = NULL;
273   uschar *rc = NULL;
274
275   /* always deliver when there is no verdict */
276   if (base64_verdict == NULL)
277     return NULL;
278
279   /* create verdict from base64 string */
280   err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict);
281   if (bmiErrorIsFatal(err) == BMI_TRUE) {
282     err_loc = bmiErrorGetLocation(err);
283     err_type = bmiErrorGetType(err);
284     log_write(0, LOG_PANIC,
285                "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict);
286     return NULL;
287   };
288
289   err = bmiVerdictError(verdict);
290   if (bmiErrorIsFatal(err) == BMI_TRUE) {
291     /* deliver normally due to error */
292     rc = NULL;
293   }
294   else if (bmiVerdictDestinationIsDefault(verdict) == BMI_TRUE) {
295     /* deliver normally */
296     rc = NULL;
297   }
298   else if (bmiVerdictAccessDestination(verdict) == NULL) {
299     /* do not deliver */
300     rc = NULL;
301   }
302   else {
303     /* deliver to alternate location */
304     rc = store_get(strlen(bmiVerdictAccessDestination(verdict))+1);
305     Ustrcpy(rc, bmiVerdictAccessDestination(verdict));
306     rc[strlen(bmiVerdictAccessDestination(verdict))] = '\0';
307   };
308
309   bmiFreeVerdict(verdict);
310   return rc;
311 }
312
313 uschar *bmi_get_base64_verdict(uschar *bmi_local_part, uschar *bmi_domain) {
314   BmiError err;
315   BmiErrorLocation err_loc;
316   BmiErrorType err_type;
317   BmiVerdict *verdict = NULL;
318   const BmiRecipient *recipient = NULL;
319   const char *verdict_str = NULL;
320   uschar *verdict_ptr;
321   uschar *verdict_buffer = NULL;
322   int sep = 0;
323
324   /* return nothing if there are no verdicts available */
325   if (bmi_verdicts == NULL)
326     return NULL;
327
328   /* allocate room for the b64 verdict string */
329   verdict_buffer = store_get(Ustrlen(bmi_verdicts)+1);
330
331   /* loop through verdicts */
332   verdict_ptr = bmi_verdicts;
333   while ((verdict_str = (const char *)string_nextinlist(&verdict_ptr, &sep,
334                                           verdict_buffer,
335                                           Ustrlen(bmi_verdicts)+1)) != NULL) {
336
337     /* create verdict from base64 string */
338     err = bmiCreateVerdictFromStr(verdict_str, &verdict);
339     if (bmiErrorIsFatal(err) == BMI_TRUE) {
340       err_loc = bmiErrorGetLocation(err);
341       err_type = bmiErrorGetType(err);
342       log_write(0, LOG_PANIC,
343                  "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, verdict_str);
344       return NULL;
345     };
346
347     /* loop through rcpts for this verdict */
348     for ( recipient = bmiVerdictAccessFirstRecipient(verdict);
349           recipient != NULL;
350           recipient = bmiVerdictAccessNextRecipient(verdict, recipient)) {
351       uschar *rcpt_local_part;
352       uschar *rcpt_domain;
353
354       /* compare address against our subject */
355       rcpt_local_part = (unsigned char *)bmiRecipientAccessAddress(recipient);
356       rcpt_domain = Ustrchr(rcpt_local_part,'@');
357       if (rcpt_domain == NULL) {
358         rcpt_domain = US"";
359       }
360       else {
361         *rcpt_domain = '\0';
362         rcpt_domain++;
363       };
364
365       if ( (strcmpic(rcpt_local_part, bmi_local_part) == 0) &&
366            (strcmpic(rcpt_domain, bmi_domain) == 0) ) {
367         /* found verdict */
368         bmiFreeVerdict(verdict);
369         return (uschar *)verdict_str;
370       };
371     };
372
373     bmiFreeVerdict(verdict);
374   };
375
376   return NULL;
377 }
378
379
380 uschar *bmi_get_base64_tracker_verdict(uschar *base64_verdict) {
381   BmiError err;
382   BmiErrorLocation err_loc;
383   BmiErrorType err_type;
384   BmiVerdict *verdict = NULL;
385   uschar *rc = NULL;
386
387   /* always deliver when there is no verdict */
388   if (base64_verdict == NULL)
389     return NULL;
390
391   /* create verdict from base64 string */
392   err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict);
393   if (bmiErrorIsFatal(err) == BMI_TRUE) {
394     err_loc = bmiErrorGetLocation(err);
395     err_type = bmiErrorGetType(err);
396     log_write(0, LOG_PANIC,
397                "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict);
398     return NULL;
399   };
400
401   /* create old tracker string from verdict */
402   err = bmiCreateOldStrFromVerdict(verdict, &rc);
403   if (bmiErrorIsFatal(err) == BMI_TRUE) {
404     err_loc = bmiErrorGetLocation(err);
405     err_type = bmiErrorGetType(err);
406     log_write(0, LOG_PANIC,
407                "bmi error [loc %d type %d]: bmiCreateOldStrFromVerdict() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict);
408     return NULL;
409   };
410
411   bmiFreeVerdict(verdict);
412   return rc;
413 }
414
415
416 int bmi_check_rule(uschar *base64_verdict, uschar *option_list) {
417   BmiError err;
418   BmiErrorLocation err_loc;
419   BmiErrorType err_type;
420   BmiVerdict *verdict = NULL;
421   int rc = 0;
422   uschar *rule_num;
423   uschar *rule_ptr;
424   uschar rule_buffer[32];
425   int sep = 0;
426
427
428   /* no verdict -> no rule fired */
429   if (base64_verdict == NULL)
430     return 0;
431
432   /* create verdict from base64 string */
433   err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict);
434   if (bmiErrorIsFatal(err) == BMI_TRUE) {
435     err_loc = bmiErrorGetLocation(err);
436     err_type = bmiErrorGetType(err);
437     log_write(0, LOG_PANIC,
438                "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict);
439     return 0;
440   };
441
442   err = bmiVerdictError(verdict);
443   if (bmiErrorIsFatal(err) == BMI_TRUE) {
444     /* error -> no rule fired */
445     bmiFreeVerdict(verdict);
446     return 0;
447   }
448
449   /* loop through numbers */
450   rule_ptr = option_list;
451   while ((rule_num = string_nextinlist(&rule_ptr, &sep,
452                                        rule_buffer, 32)) != NULL) {
453     int rule_int = -1;
454
455     /* try to translate to int */
456     sscanf(rule_num, "%d", &rule_int);
457     if (rule_int > 0) {
458       debug_printf("checking rule #%d\n", rule_int);
459       /* check if rule fired on the message */
460       if (bmiVerdictRuleFired(verdict, rule_int) == BMI_TRUE) {
461         debug_printf("rule #%d fired\n", rule_int);
462         rc = 1;
463         break;
464       };
465     };
466   };
467
468   bmiFreeVerdict(verdict);
469   return rc;
470 };
471
472 #endif