Convert more cases of list-walking to use self-assigned memory for the list-item
[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.  Since we are processing message data, assume that
194   the verdict is tainted.  XXX this should use a growable-string  */
195
196   verdicts = store_get(1, TRUE);
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, TRUE,
206           Ustrlen(verdicts)+1, Ustrlen(verdicts)+1+strlen(verdict_str)+1)) {
207       /* can't allocate more store */
208       return NULL;
209     };
210     if (*verdicts != '\0')
211       Ustrcat(verdicts, US ":");
212     Ustrcat(verdicts, US verdict_str);
213     bmiFreeStr(verdict_str);
214   };
215
216   DEBUG(D_receive) debug_printf("bmi verdicts: %s\n", verdicts);
217
218   if (Ustrlen(verdicts) == 0)
219     return NULL;
220   else
221     return verdicts;
222 }
223
224
225 int bmi_get_delivery_status(uschar *base64_verdict) {
226   BmiError err;
227   BmiErrorLocation err_loc;
228   BmiErrorType err_type;
229   BmiVerdict *verdict = NULL;
230   int rc = 1;   /* deliver by default */
231
232   /* always deliver when there is no verdict */
233   if (base64_verdict == NULL)
234     return 1;
235
236   /* create verdict from base64 string */
237   err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict);
238   if (bmiErrorIsFatal(err) == BMI_TRUE) {
239     err_loc = bmiErrorGetLocation(err);
240     err_type = bmiErrorGetType(err);
241     log_write(0, LOG_PANIC,
242                "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict);
243     return 1;
244   };
245
246   err = bmiVerdictError(verdict);
247   if (bmiErrorIsFatal(err) == BMI_TRUE) {
248     /* deliver normally due to error */
249     rc = 1;
250   }
251   else if (bmiVerdictDestinationIsDefault(verdict) == BMI_TRUE) {
252     /* deliver normally */
253     rc = 1;
254   }
255   else if (bmiVerdictAccessDestination(verdict) == NULL) {
256     /* do not deliver */
257     rc = 0;
258   }
259   else {
260     /* deliver to alternate location */
261     rc = 1;
262   };
263
264   bmiFreeVerdict(verdict);
265   return rc;
266 }
267
268
269 uschar *bmi_get_alt_location(uschar *base64_verdict) {
270   BmiError err;
271   BmiErrorLocation err_loc;
272   BmiErrorType err_type;
273   BmiVerdict *verdict = NULL;
274   uschar *rc = NULL;
275
276   /* always deliver when there is no verdict */
277   if (base64_verdict == NULL)
278     return NULL;
279
280   /* create verdict from base64 string */
281   err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict);
282   if (bmiErrorIsFatal(err) == BMI_TRUE) {
283     err_loc = bmiErrorGetLocation(err);
284     err_type = bmiErrorGetType(err);
285     log_write(0, LOG_PANIC,
286                "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict);
287     return NULL;
288   };
289
290   err = bmiVerdictError(verdict);
291   if (bmiErrorIsFatal(err) == BMI_TRUE) {
292     /* deliver normally due to error */
293     rc = NULL;
294   }
295   else if (bmiVerdictDestinationIsDefault(verdict) == BMI_TRUE) {
296     /* deliver normally */
297     rc = NULL;
298   }
299   else if (bmiVerdictAccessDestination(verdict) == NULL) {
300     /* do not deliver */
301     rc = NULL;
302   }
303   else {
304     /* deliver to alternate location */
305     rc = store_get(strlen(bmiVerdictAccessDestination(verdict))+1, TRUE);
306     Ustrcpy(rc, bmiVerdictAccessDestination(verdict));
307     rc[strlen(bmiVerdictAccessDestination(verdict))] = '\0';
308   };
309
310   bmiFreeVerdict(verdict);
311   return rc;
312 }
313
314 uschar *bmi_get_base64_verdict(uschar *bmi_local_part, uschar *bmi_domain) {
315   BmiError err;
316   BmiErrorLocation err_loc;
317   BmiErrorType err_type;
318   BmiVerdict *verdict = NULL;
319   const BmiRecipient *recipient = NULL;
320   const char *verdict_str = NULL;
321   uschar *verdict_ptr;
322   uschar *verdict_buffer = NULL;
323   int sep = 0;
324
325   /* return nothing if there are no verdicts available */
326   if (bmi_verdicts == NULL)
327     return NULL;
328
329   /* allocate room for the b64 verdict string */
330   verdict_buffer = store_get(Ustrlen(bmi_verdicts)+1, TRUE);
331
332   /* loop through verdicts */
333   verdict_ptr = bmi_verdicts;
334   while ((verdict_str = CCS string_nextinlist(&verdict_ptr, &sep,
335                                           verdict_buffer,
336                                           Ustrlen(bmi_verdicts)+1)) != NULL) {
337
338     /* create verdict from base64 string */
339     err = bmiCreateVerdictFromStr(verdict_str, &verdict);
340     if (bmiErrorIsFatal(err) == BMI_TRUE) {
341       err_loc = bmiErrorGetLocation(err);
342       err_type = bmiErrorGetType(err);
343       log_write(0, LOG_PANIC,
344                  "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, verdict_str);
345       return NULL;
346     };
347
348     /* loop through rcpts for this verdict */
349     for ( recipient = bmiVerdictAccessFirstRecipient(verdict);
350           recipient != NULL;
351           recipient = bmiVerdictAccessNextRecipient(verdict, recipient)) {
352       uschar *rcpt_local_part;
353       uschar *rcpt_domain;
354
355       /* compare address against our subject */
356       rcpt_local_part = US bmiRecipientAccessAddress(recipient);
357       rcpt_domain = Ustrchr(rcpt_local_part,'@');
358       if (rcpt_domain == NULL) {
359         rcpt_domain = US"";
360       }
361       else {
362         *rcpt_domain = '\0';
363         rcpt_domain++;
364       };
365
366       if ( (strcmpic(rcpt_local_part, bmi_local_part) == 0) &&
367            (strcmpic(rcpt_domain, bmi_domain) == 0) ) {
368         /* found verdict */
369         bmiFreeVerdict(verdict);
370         return US verdict_str;
371       };
372     };
373
374     bmiFreeVerdict(verdict);
375   };
376
377   return NULL;
378 }
379
380
381 uschar *bmi_get_base64_tracker_verdict(uschar *base64_verdict) {
382   BmiError err;
383   BmiErrorLocation err_loc;
384   BmiErrorType err_type;
385   BmiVerdict *verdict = NULL;
386   uschar *rc = NULL;
387
388   /* always deliver when there is no verdict */
389   if (base64_verdict == NULL)
390     return NULL;
391
392   /* create verdict from base64 string */
393   err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict);
394   if (bmiErrorIsFatal(err) == BMI_TRUE) {
395     err_loc = bmiErrorGetLocation(err);
396     err_type = bmiErrorGetType(err);
397     log_write(0, LOG_PANIC,
398                "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict);
399     return NULL;
400   };
401
402   /* create old tracker string from verdict */
403   err = bmiCreateOldStrFromVerdict(verdict, &rc);
404   if (bmiErrorIsFatal(err) == BMI_TRUE) {
405     err_loc = bmiErrorGetLocation(err);
406     err_type = bmiErrorGetType(err);
407     log_write(0, LOG_PANIC,
408                "bmi error [loc %d type %d]: bmiCreateOldStrFromVerdict() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict);
409     return NULL;
410   };
411
412   bmiFreeVerdict(verdict);
413   return rc;
414 }
415
416
417 int bmi_check_rule(uschar *base64_verdict, uschar *option_list) {
418   BmiError err;
419   BmiErrorLocation err_loc;
420   BmiErrorType err_type;
421   BmiVerdict *verdict = NULL;
422   int rc = 0;
423   uschar *rule_num;
424   uschar *rule_ptr;
425   uschar rule_buffer[32];
426   int sep = 0;
427
428
429   /* no verdict -> no rule fired */
430   if (base64_verdict == NULL)
431     return 0;
432
433   /* create verdict from base64 string */
434   err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict);
435   if (bmiErrorIsFatal(err) == BMI_TRUE) {
436     err_loc = bmiErrorGetLocation(err);
437     err_type = bmiErrorGetType(err);
438     log_write(0, LOG_PANIC,
439                "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict);
440     return 0;
441   };
442
443   err = bmiVerdictError(verdict);
444   if (bmiErrorIsFatal(err) == BMI_TRUE) {
445     /* error -> no rule fired */
446     bmiFreeVerdict(verdict);
447     return 0;
448   }
449
450   /* loop through numbers */
451   /* option_list doesn't seem to be expanded so cannot be tainted.  If it ever is we
452   will trap here */
453   rule_ptr = option_list;
454   while ((rule_num = string_nextinlist(&rule_ptr, &sep,
455                                        rule_buffer, sizeof(rule_buffer)))) {
456     int rule_int = -1;
457
458     /* try to translate to int */
459     (void)sscanf(rule_num, "%d", &rule_int);
460     if (rule_int > 0) {
461       debug_printf("checking rule #%d\n", rule_int);
462       /* check if rule fired on the message */
463       if (bmiVerdictRuleFired(verdict, rule_int) == BMI_TRUE) {
464         debug_printf("rule #%d fired\n", rule_int);
465         rc = 1;
466         break;
467       };
468     };
469   };
470
471   bmiFreeVerdict(verdict);
472   return rc;
473 };
474
475 #endif