Refactor authenticators API to take an (opaque) smtp connection context
[users/heiko/exim.git] / src / src / dcc.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) Wolfgang Breyha 2005 - 2015
6  * Vienna University Computer Center
7  * wbreyha@gmx.net
8  * See the file NOTICE for conditions of use and distribution.
9  *
10  * Copyright (c) The Exim Maintainers 2015 - 2018
11  */
12
13 /* This patch is based on code from Tom Kistners exiscan (ACL integration) and
14  * the DCC local_scan patch from Christopher Bodenstein */
15
16 /* Code for calling dccifd. Called from acl.c. */
17
18 #include "exim.h"
19 #ifdef EXPERIMENTAL_DCC
20 #include "dcc.h"
21 #include "unistd.h"
22
23 uschar dcc_header_str[256];
24 int dcc_ok = 0;
25 int dcc_rc = 0;
26
27 /* This function takes a file descriptor and a buffer as input and
28  * returns either 0 for success or errno in case of error. */
29
30 int flushbuffer (int socket, uschar *buffer)
31  {
32   int retval, rsp;
33   rsp = write(socket, buffer, Ustrlen(buffer));
34   DEBUG(D_acl)
35     debug_printf("DCC: Result of the write() = %d\n", rsp);
36   if(rsp < 0)
37   {
38     DEBUG(D_acl)
39       debug_printf("DCC: Error writing buffer to socket: %s\n", strerror(errno));
40     retval = errno;
41   }
42   else {
43     DEBUG(D_acl)
44       debug_printf("DCC: Wrote buffer to socket:\n%s\n", buffer);
45     retval = 0;
46   }
47   return retval;
48 }
49
50 int
51 dcc_process(uschar **listptr)
52 {
53   int sep = 0;
54   const uschar *list = *listptr;
55   FILE *data_file;
56   uschar *dcc_default_ip_option = US"127.0.0.1";
57   uschar *dcc_helo_option = US"localhost";
58   uschar *dcc_reject_message = US"Rejected by DCC";
59   uschar *xtra_hdrs = NULL;
60   uschar *override_client_ip  = NULL;
61
62   /* from local_scan */
63   int i, j, k, c, retval, sockfd, resp, line;
64   unsigned int portnr;
65   struct sockaddr_un  serv_addr;
66   struct sockaddr_in  serv_addr_in;
67   struct hostent *ipaddress;
68   uschar sockpath[128];
69   uschar sockip[40], client_ip[40];
70   uschar opts[128];
71   uschar rcpt[128], from[128];
72   uschar sendbuf[4096];
73   uschar recvbuf[4096];
74   uschar dcc_return_text[1024];
75   uschar message_subdir[2];
76   struct header_line *dcchdr;
77   uschar *dcc_acl_options;
78   uschar dcc_acl_options_buffer[10];
79   uschar dcc_xtra_hdrs[1024];
80
81   /* grep 1st option */
82   if ((dcc_acl_options = string_nextinlist(&list, &sep,
83                    dcc_acl_options_buffer, sizeof(dcc_acl_options_buffer))))
84     {
85     /* parse 1st option */
86     if (  strcmpic(dcc_acl_options, US"false") == 0
87        || Ustrcmp(dcc_acl_options, "0") == 0
88        )
89       return FAIL;      /* explicitly no matching */
90     }
91   else
92     return FAIL;        /* empty means "don't match anything" */
93
94   sep = 0;
95
96   /* if we scanned this message last time, just return */
97   if (dcc_ok)
98     return dcc_rc;
99
100   /* open the spooled body */
101   message_subdir[1] = '\0';
102   for (i = 0; i < 2; i++)
103     {
104     message_subdir[0] = split_spool_directory == (i == 0) ? message_id[5] : 0;
105
106     if ((data_file = Ufopen(
107             spool_fname(US"input", message_subdir, message_id, US"-D"),
108             "rb")))
109       break;
110     }
111
112   if (!data_file)
113     {
114     /* error while spooling */
115     log_write(0, LOG_MAIN|LOG_PANIC,
116            "dcc acl condition: error while opening spool file");
117     return DEFER;
118     }
119
120   /* Initialize the variables */
121
122   bzero(sockip,sizeof(sockip));
123   if (dccifd_address) {
124     if (dccifd_address[0] == '/')
125       Ustrncpy(sockpath, dccifd_address, sizeof(sockpath));
126     else
127       if( sscanf(CS dccifd_address, "%s %u", sockip, &portnr) != 2) {
128         log_write(0, LOG_MAIN,
129           "dcc acl condition: warning - invalid dccifd address: '%s'", dccifd_address);
130         (void)fclose(data_file);
131         return DEFER;
132       }
133   }
134
135   /* opts is what we send as dccifd options - see man dccifd */
136   /* We don't support any other option than 'header' so just copy that */
137   bzero(opts,sizeof(opts));
138   Ustrncpy(opts, dccifd_options, sizeof(opts)-1);
139   /* if $acl_m_dcc_override_client_ip is set use it */
140   if (((override_client_ip = expand_string(US"$acl_m_dcc_override_client_ip")) != NULL) &&
141        (override_client_ip[0] != '\0')) {
142     Ustrncpy(client_ip, override_client_ip, sizeof(client_ip)-1);
143     DEBUG(D_acl)
144       debug_printf("DCC: Client IP (overridden): %s\n", client_ip);
145   }
146   else if(sender_host_address) {
147   /* else if $sender_host_address is available use that? */
148     Ustrncpy(client_ip, sender_host_address, sizeof(client_ip)-1);
149     DEBUG(D_acl)
150       debug_printf("DCC: Client IP (sender_host_address): %s\n", client_ip);
151   }
152   else {
153     /* sender_host_address is NULL which means it comes from localhost */
154     Ustrncpy(client_ip, dcc_default_ip_option, sizeof(client_ip)-1);
155     DEBUG(D_acl)
156       debug_printf("DCC: Client IP (default): %s\n", client_ip);
157   }
158   /* strncat(opts, my_request, strlen(my_request)); */
159   Ustrcat(opts, "\n");
160   Ustrncat(opts, client_ip, sizeof(opts)-Ustrlen(opts)-1);
161   Ustrncat(opts, "\nHELO ", sizeof(opts)-Ustrlen(opts)-1);
162   Ustrncat(opts, dcc_helo_option, sizeof(opts)-Ustrlen(opts)-2);
163   Ustrcat(opts, "\n");
164
165   /* initialize the other variables */
166   dcchdr = header_list;
167   /* we set the default return value to DEFER */
168   retval = DEFER;
169
170   bzero(sendbuf,sizeof(sendbuf));
171   bzero(dcc_header_str,sizeof(dcc_header_str));
172   bzero(rcpt,sizeof(rcpt));
173   bzero(from,sizeof(from));
174
175   /* send a null return path as "<>". */
176   if (Ustrlen(sender_address) > 0)
177     Ustrncpy(from, sender_address, sizeof(from));
178   else
179     Ustrncpy(from, "<>", sizeof(from));
180   Ustrncat(from, "\n", sizeof(from)-Ustrlen(from)-1);
181
182   /**************************************
183    * Now creating the socket connection *
184    **************************************/
185
186   /* If sockip contains an ip, we use a tcp socket, otherwise a UNIX socket */
187   if(Ustrcmp(sockip, "")){
188     ipaddress = gethostbyname(CS sockip);
189     bzero(CS  &serv_addr_in, sizeof(serv_addr_in));
190     serv_addr_in.sin_family = AF_INET;
191     bcopy(CS ipaddress->h_addr, CS &serv_addr_in.sin_addr.s_addr, ipaddress->h_length);
192     serv_addr_in.sin_port = htons(portnr);
193     if ((sockfd = socket(AF_INET, SOCK_STREAM,0)) < 0){
194       DEBUG(D_acl)
195         debug_printf("DCC: Creating TCP socket connection failed: %s\n", strerror(errno));
196       log_write(0,LOG_PANIC,"DCC: Creating TCP socket connection failed: %s\n", strerror(errno));
197       /* if we cannot create the socket, defer the mail */
198       (void)fclose(data_file);
199       return retval;
200     }
201     /* Now connecting the socket (INET) */
202     if (connect(sockfd, (struct sockaddr *)&serv_addr_in, sizeof(serv_addr_in)) < 0){
203       DEBUG(D_acl)
204         debug_printf("DCC: Connecting to TCP socket failed: %s\n", strerror(errno));
205       log_write(0,LOG_PANIC,"DCC: Connecting to TCP socket failed: %s\n", strerror(errno));
206       /* if we cannot contact the socket, defer the mail */
207       (void)fclose(data_file);
208       return retval;
209     }
210   } else {
211     /* connecting to the dccifd UNIX socket */
212     bzero(&serv_addr, sizeof(serv_addr));
213     serv_addr.sun_family = AF_UNIX;
214     Ustrncpy(serv_addr.sun_path, sockpath, sizeof(serv_addr.sun_path));
215     if ((sockfd = socket(AF_UNIX, SOCK_STREAM,0)) < 0){
216       DEBUG(D_acl)
217         debug_printf("DCC: Creating UNIX socket connection failed: %s\n", strerror(errno));
218       log_write(0,LOG_PANIC,"DCC: Creating UNIX socket connection failed: %s\n", strerror(errno));
219       /* if we cannot create the socket, defer the mail */
220       (void)fclose(data_file);
221       return retval;
222     }
223     /* Now connecting the socket (UNIX) */
224     if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0){
225       DEBUG(D_acl)
226                             debug_printf("DCC: Connecting to UNIX socket failed: %s\n", strerror(errno));
227       log_write(0,LOG_PANIC,"DCC: Connecting to UNIX socket failed: %s\n", strerror(errno));
228       /* if we cannot contact the socket, defer the mail */
229       (void)fclose(data_file);
230       return retval;
231     }
232   }
233   /* the socket is open, now send the options to dccifd*/
234   DEBUG(D_acl)
235     debug_printf("\nDCC: ---------------------------\nDCC: Socket opened; now sending input\nDCC: -----------------\n");
236   /* First, fill in the input buffer */
237   Ustrncpy(sendbuf, opts, sizeof(sendbuf));
238   Ustrncat(sendbuf, from, sizeof(sendbuf)-Ustrlen(sendbuf)-1);
239
240   DEBUG(D_acl)
241   {
242     debug_printf("DCC: opts = %s\nDCC: sender = %s\nDCC: rcpt count = %d\n", opts, from, recipients_count);
243     debug_printf("DCC: Sending options:\nDCC: ****************************\n");
244   }
245
246   /* let's send each of the recipients to dccifd */
247   for (i = 0; i < recipients_count; i++){
248     DEBUG(D_acl)
249       debug_printf("DCC: recipient = %s\n",recipients_list[i].address);
250     if(Ustrlen(sendbuf) + Ustrlen(recipients_list[i].address) > sizeof(sendbuf))
251     {
252       DEBUG(D_acl)
253         debug_printf("DCC: Writing buffer: %s\n", sendbuf);
254       flushbuffer(sockfd, sendbuf);
255       bzero(sendbuf, sizeof(sendbuf));
256     }
257     Ustrncat(sendbuf, recipients_list[i].address, sizeof(sendbuf)-Ustrlen(sendbuf)-1);
258     Ustrncat(sendbuf, "\r\n", sizeof(sendbuf)-Ustrlen(sendbuf)-1);
259   }
260   /* send a blank line between options and message */
261   Ustrncat(sendbuf, "\n", sizeof(sendbuf)-Ustrlen(sendbuf)-1);
262   /* Now we send the input buffer */
263   DEBUG(D_acl)
264     debug_printf("DCC: %s\nDCC: ****************************\n", sendbuf);
265   flushbuffer(sockfd, sendbuf);
266
267   /* now send the message */
268   /* Clear the input buffer */
269   bzero(sendbuf, sizeof(sendbuf));
270   /* First send the headers */
271   /* Now send the headers */
272   DEBUG(D_acl)
273     debug_printf("DCC: Sending headers:\nDCC: ****************************\n");
274   Ustrncpy(sendbuf, dcchdr->text, sizeof(sendbuf)-2);
275   while((dcchdr=dcchdr->next)) {
276     if(dcchdr->slen > sizeof(sendbuf)-2) {
277       /* The size of the header is bigger than the size of
278        * the input buffer, so split it up in smaller parts. */
279        flushbuffer(sockfd, sendbuf);
280        bzero(sendbuf, sizeof(sendbuf));
281        j = 0;
282        while(j < dcchdr->slen)
283        {
284         for(i = 0; i < sizeof(sendbuf)-2; i++) {
285           sendbuf[i] = dcchdr->text[j];
286           j++;
287         }
288         flushbuffer(sockfd, sendbuf);
289         bzero(sendbuf, sizeof(sendbuf));
290        }
291     } else if(Ustrlen(sendbuf) + dcchdr->slen > sizeof(sendbuf)-2) {
292       flushbuffer(sockfd, sendbuf);
293       bzero(sendbuf, sizeof(sendbuf));
294       Ustrncpy(sendbuf, dcchdr->text, sizeof(sendbuf)-2);
295     } else {
296       Ustrncat(sendbuf, dcchdr->text, sizeof(sendbuf)-Ustrlen(sendbuf)-2);
297     }
298   }
299
300   /* a blank line separates header from body */
301   Ustrncat(sendbuf, "\n", sizeof(sendbuf)-Ustrlen(sendbuf)-1);
302   flushbuffer(sockfd, sendbuf);
303   DEBUG(D_acl)
304     debug_printf("\nDCC: ****************************\n%s", sendbuf);
305
306   /* Clear the input buffer */
307   bzero(sendbuf, sizeof(sendbuf));
308
309   /* now send the body */
310   DEBUG(D_acl)
311     debug_printf("DCC: Writing body:\nDCC: ****************************\n");
312   (void)fseek(data_file, SPOOL_DATA_START_OFFSET, SEEK_SET);
313   while((fread(sendbuf, 1, sizeof(sendbuf)-1, data_file)) > 0) {
314     flushbuffer(sockfd, sendbuf);
315     bzero(sendbuf, sizeof(sendbuf));
316   }
317   DEBUG(D_acl)
318     debug_printf("\nDCC: ****************************\n");
319
320   /* shutdown() the socket */
321   if(shutdown(sockfd, 1) < 0){
322     DEBUG(D_acl)
323       debug_printf("DCC: Couldn't shutdown socket: %s\n", strerror(errno));
324     log_write(0,LOG_MAIN,"DCC: Couldn't shutdown socket: %s\n", strerror(errno));
325     /* If there is a problem with the shutdown()
326      * defer the mail. */
327     (void)fclose(data_file);
328     return retval;
329   }
330   DEBUG(D_acl)
331     debug_printf("\nDCC: -------------------------\nDCC: Input sent.\nDCC: -------------------------\n");
332
333     /********************************
334    * receiving output from dccifd *
335    ********************************/
336   DEBUG(D_acl)
337     debug_printf("\nDCC: -------------------------------------\nDCC: Now receiving output from server\nDCC: -----------------------------------\n");
338
339   /******************************************************************
340    * We should get 3 lines:                                         *
341    * 1/ First line is overall result: either 'A' for Accept,        *
342    *    'R' for Reject, 'S' for accept Some recipients or           *
343    *    'T' for a Temporary error.                                  *
344    * 2/ Second line contains the list of Accepted/Rejected          *
345    *    recipients in the form AARRA (A = accepted, R = rejected).  *
346    * 3/ Third line contains the X-DCC header.                       *
347    ******************************************************************/
348
349   line = 1;    /* we start at the first line of the output */
350   j = 0;       /* will be used as index for the recipients list */
351   k = 0;       /* initializing the index of the X-DCC header: dcc_header_str[k] */
352
353   /* Let's read from the socket until there's nothing left to read */
354   bzero(recvbuf, sizeof(recvbuf));
355   while((resp = read(sockfd, recvbuf, sizeof(recvbuf)-1)) > 0) {
356     /* How much did we get from the socket */
357     c = Ustrlen(recvbuf) + 1;
358     DEBUG(D_acl)
359       debug_printf("DCC: Length of the output buffer is: %d\nDCC: Output buffer is:\nDCC: ------------\nDCC: %s\nDCC: -----------\n", c, recvbuf);
360
361     /* Now let's read each character and see what we've got */
362     for(i = 0; i < c; i++) {
363       /* First check if we reached the end of the line and
364        * then increment the line counter */
365       if(recvbuf[i] == '\n') {
366         line++;
367       }
368       else {
369         /* The first character of the first line is the
370          * overall response. If there's another character
371          * on that line it is not correct. */
372         if(line == 1) {
373           if(i == 0) {
374             /* Now get the value and set the
375              * return value accordingly */
376             if(recvbuf[i] == 'A') {
377               DEBUG(D_acl)
378                 debug_printf("DCC: Overall result = A\treturning OK\n");
379               Ustrcpy(dcc_return_text, "Mail accepted by DCC");
380               dcc_result = US"A";
381               retval = OK;
382             }
383             else if(recvbuf[i] == 'R') {
384               DEBUG(D_acl)
385                 debug_printf("DCC: Overall result = R\treturning FAIL\n");
386               dcc_result = US"R";
387               retval = FAIL;
388               if(sender_host_name) {
389                 log_write(0, LOG_MAIN, "H=%s [%s] F=<%s>: rejected by DCC", sender_host_name, sender_host_address, sender_address);
390               }
391               else {
392                 log_write(0, LOG_MAIN, "H=[%s] F=<%s>: rejected by DCC", sender_host_address, sender_address);
393               }
394               Ustrncpy(dcc_return_text, dcc_reject_message, Ustrlen(dcc_reject_message) + 1);
395             }
396             else if(recvbuf[i] == 'S') {
397               DEBUG(D_acl)
398                 debug_printf("DCC: Overall result  = S\treturning OK\n");
399               Ustrcpy(dcc_return_text, "Not all recipients accepted by DCC");
400               /* Since we're in an ACL we want a global result
401                * so we accept for all */
402               dcc_result = US"A";
403               retval = OK;
404             }
405             else if(recvbuf[i] == 'G') {
406               DEBUG(D_acl)
407                 debug_printf("DCC: Overall result  = G\treturning FAIL\n");
408               Ustrcpy(dcc_return_text, "Greylisted by DCC");
409               dcc_result = US"G";
410               retval = FAIL;
411             }
412             else if(recvbuf[i] == 'T') {
413               DEBUG(D_acl)
414                 debug_printf("DCC: Overall result = T\treturning DEFER\n");
415               retval = DEFER;
416               log_write(0,LOG_MAIN,"Temporary error with DCC: %s\n", recvbuf);
417               Ustrcpy(dcc_return_text, "Temporary error with DCC");
418               dcc_result = US"T";
419             }
420             else {
421               DEBUG(D_acl)
422                 debug_printf("DCC: Overall result = something else\treturning DEFER\n");
423               retval = DEFER;
424               log_write(0,LOG_MAIN,"Unknown DCC response: %s\n", recvbuf);
425               Ustrcpy(dcc_return_text, "Unknown DCC response");
426               dcc_result = US"T";
427             }
428           }
429           else {
430             /* We're on the first line but not on the first character,
431              * there must be something wrong. */
432             DEBUG(D_acl) debug_printf("DCC: Line = %d but i = %d != 0"
433                 "  character is %c - This is wrong!\n", line, i, recvbuf[i]);
434             log_write(0,LOG_MAIN,"Wrong header from DCC, output is %s\n", recvbuf);
435           }
436         }
437         else if(line == 2) {
438           /* On the second line we get a list of
439            * answers for each recipient. We don't care about
440            * it because we're in an acl and take the
441            * global result. */
442         }
443         else if(line > 2) {
444           /* The third and following lines are the X-DCC header,
445            * so we store it in dcc_header_str. */
446           /* check if we don't get more than we can handle */
447           if(k < sizeof(dcc_header_str)) {
448             dcc_header_str[k] = recvbuf[i];
449             k++;
450           }
451           else {
452             DEBUG(D_acl) debug_printf("DCC: We got more output than we can store"
453                 " in the X-DCC header. Truncating at 120 characters.\n");
454           }
455         }
456         else {
457           /* Wrong line number. There must be a problem with the output. */
458           DEBUG(D_acl)
459             debug_printf("DCC: Wrong line number in output. Line number is %d\n", line);
460         }
461       }
462     }
463     /* we reinitialize the output buffer before we read again */
464     bzero(recvbuf,sizeof(recvbuf));
465   }
466   /* We have read everything from the socket */
467
468   /* We need to terminate the X-DCC header with a '\n' character. This needs to be k-1
469    * since dcc_header_str[k] contains '\0'. */
470   dcc_header_str[k-1] = '\n';
471
472   /* Now let's sum up what we've got. */
473   DEBUG(D_acl)
474     debug_printf("\nDCC: --------------------------\nDCC: Overall result = %d\nDCC: X-DCC header: %sReturn message: %s\nDCC: dcc_result: %s\n", retval, dcc_header_str, dcc_return_text, dcc_result);
475
476   /* We only add the X-DCC header if it starts with X-DCC */
477   if(!(Ustrncmp(dcc_header_str, "X-DCC", 5))){
478     dcc_header = dcc_header_str;
479     if(dcc_direct_add_header) {
480       header_add(' ' , "%s", dcc_header_str);
481   /* since the MIME ACL already writes the .eml file to disk without DCC Header we've to erase it */
482       unspool_mbox();
483     }
484   }
485   else {
486     DEBUG(D_acl)
487       debug_printf("DCC: Wrong format of the X-DCC header: %s\n", dcc_header_str);
488   }
489
490   /* check if we should add additional headers passed in acl_m_dcc_add_header */
491   if(dcc_direct_add_header) {
492     if (((xtra_hdrs = expand_string(US"$acl_m_dcc_add_header")) != NULL) && (xtra_hdrs[0] != '\0')) {
493       Ustrncpy(dcc_xtra_hdrs, xtra_hdrs, sizeof(dcc_xtra_hdrs) - 2);
494       if (dcc_xtra_hdrs[Ustrlen(dcc_xtra_hdrs)-1] != '\n')
495         Ustrcat(dcc_xtra_hdrs, "\n");
496       header_add(' ', "%s", dcc_xtra_hdrs);
497       DEBUG(D_acl)
498         debug_printf("DCC: adding additional headers in $acl_m_dcc_add_header: %s", dcc_xtra_hdrs);
499     }
500   }
501
502   dcc_ok = 1;
503   /* Now return to exim main process */
504   DEBUG(D_acl)
505     debug_printf("DCC: Before returning to exim main process:\nDCC: return_text = %s - retval = %d\nDCC: dcc_result = %s\n", dcc_return_text, retval, dcc_result);
506
507   (void)fclose(data_file);
508   dcc_rc = retval;
509   return dcc_rc;
510 }
511
512 #endif