Added a "consolidating" wish to connect several ideas about header
[users/jgh/exim.git] / configs / config.samples / L001
1 /*
2  * uvscan local_scan() function for Exim (requires at least Exim v4.14)
3  * known to work with VirusScan for Linux v4.16.0 (Scan engine v4.2.40)
4  * but should be OK with other platforms
5  *
6  * this file is free software (license=GNU GPLv2) and comes with no
7  * guarantees--if it breaks, you get to keep the pieces (maybe not the mail)!
8  *
9  * by (ie patches / flames to): mb/local_scan@dcs.qmul.ac.uk, 2003-05-02
10  * (original version on 2002-05-25)
11  */
12
13 #include <unistd.h>
14 #include <stdlib.h>
15 #include <sys/types.h>
16 #include <sys/wait.h>
17 #include <fcntl.h>
18 #include <string.h>
19 #include "local_scan.h"
20
21 /*
22  * remember to set LOCAL_SCAN_HAS_OPTIONS=yes in Local/Makefile
23  * otherwise you get stuck with the compile-time defaults
24  */
25
26 static uschar *uvscan_binary = US"/usr/local/uvscan/uvscan";
27 static uschar *data_directory = US"/usr/local/uvscan";
28
29 optionlist local_scan_options[] = { /* alphabetical order */
30         { "data_directory", opt_stringptr, &data_directory },
31         { "uvscan_binary", opt_stringptr, &uvscan_binary }
32 };
33
34 int local_scan_options_count = sizeof(local_scan_options)/sizeof(optionlist);
35
36 /* log headers in rejectlog or not? */
37
38 //#define VIRUS_IN_MAIL LOCAL_SCAN_REJECT
39 #define VIRUS_IN_MAIL LOCAL_SCAN_REJECT_NOLOGHDR
40
41 /*
42  * buffer is used both for file copying and catching uvscan's output
43  * BUFSIZE = 1024 should always be fine
44  */
45
46 #define BUFSIZE 1024
47
48 /* some number which uvscan doesn't return */
49 #define MAGIC 123
50
51 /*
52  * some macros to make the main function more obvious
53  * NB bailing out might leave tempfiles hanging around
54  * (and open fds, but no need to be worried about that)
55  */
56
57 #define BAIL(btext) { log_write(0, LOG_MAIN, "UVSCAN ERROR: "btext); \
58         *return_text = "local scanning problem: please try again later"; \
59         return LOCAL_SCAN_TEMPREJECT; }
60
61 #define DEBUG(dtext) if ((debug_selector & D_local_scan) != 0) \
62                 { debug_printf(dtext); sleep(1); }
63         /* sleep useful for running exim -d */
64
65 #define RESULT(rtext) header_add(32, \
66         "X-uvscan-result: "rtext" (%s)\n", message_id);
67
68 #define FREEZE(ftext) { header_add(32, \
69         "X-uvscan-warning: frozen for manual attention (%s)\n", message_id); \
70         RESULT(ftext); *return_text = ftext; return LOCAL_SCAN_ACCEPT_FREEZE; }
71
72 /* OK, enough waffle. On with the show! */
73
74 int local_scan(int fd, uschar **return_text)
75 {
76         char tf[] = "/tmp/local_scan.XXXXXX"; /* should this be tunable? */
77         int tmpfd, bytesin, bytesout, pid, status, pipe_fd[2];
78         fd_set fds; 
79         static uschar buffer[BUFSIZE];
80
81         DEBUG("entered uvscan local_scan() function");
82
83         /*
84          * I set majordomo to resend using -oMr lsmtp
85          * (and yes, I know majordomo isn't actually using SMTP..)
86          * no point in scanning these beasties twice
87          */
88
89         if(!strcmp(received_protocol, "lsmtp"))
90                 return LOCAL_SCAN_ACCEPT;
91
92         /* create a file to copy the data into */
93
94         if ((tmpfd = mkstemp(tf)) == -1)
95                 BAIL("mkstemp failed");
96
97         DEBUG("made tmp file");
98
99         /* copy said file BUFSIZE at a time */
100
101         while ((bytesin = read(fd, buffer, BUFSIZE)) > 0) {
102                 bytesout = write(tmpfd, buffer, bytesin);
103                 if (bytesout < 1)
104                         BAIL("writing to tmp file");
105         }
106         if (bytesin < 0)
107                 BAIL("reading from spool file");
108
109         close(tmpfd);
110
111         if(pipe(pipe_fd) == -1)
112                 BAIL("making pipe");
113
114         /* fork and scan */     
115
116         if((pid = fork()) == -1)
117                 BAIL("couldn't fork");
118
119         if(pid == 0) {
120                 close(1); /* close stdout */
121                 if(dup2(pipe_fd[1],1) == -1) /* duplicate write as stdout */
122                         BAIL("dup2 (stdout) failed");
123                 if(fcntl(1,F_SETFD,0) == -1) /* fd to NOT close on exec() */
124                         BAIL("fcntl (stdout) failed");
125
126                 execl(uvscan_binary, uvscan_binary, "--mime", "--secure",
127                         "-d", data_directory, tf, NULL);
128                 DEBUG("execl failed");
129                 _exit(MAGIC);
130         }
131
132         if(waitpid(pid, &status, 0) < 1)
133                 BAIL("couldn't wait for child");
134         
135         DEBUG("about to unlink");
136
137         if(unlink(tf) == -1)
138                 FREEZE("couldn't unlink tmp file");
139
140         DEBUG("unlinked :)");
141
142         /*
143          * choose what to do based on the return code of uvscan
144          * RESULT() or FREEZE() according to personal taste
145          */
146
147         if(WIFEXITED(status) != 0)
148                 switch(WEXITSTATUS(status)) {
149                 case 0: RESULT("clean"); break;
150                 case 2: RESULT("driver integrity check failed"); break;
151                 case 6: FREEZE("general problem occurred"); break;
152                 case 8: RESULT("could not find a driver"); break;
153                 case 12: FREEZE("failed to clean file"); break;
154                 case 13:
155                         // RESULT("virus detected"); /* were we to accept */
156                         DEBUG("about to read from uvscan process");
157                         FD_ZERO(&fds);
158                         FD_SET(pipe_fd[0], &fds);
159                         if(select(pipe_fd[0]+1, &fds, NULL, NULL, NULL)) {
160                                 /* last NULL above means wait forever! */
161                                 DEBUG("select returned non-zero");
162                                 if((bytesin = read(pipe_fd[0], buffer,
163                                                 BUFSIZE - 1)) > 0) {
164                                         buffer[bytesin] = (uschar)0;
165                                         *return_text = buffer + 22;
166                                         /* 22 was empirically found ;) */
167                                         return VIRUS_IN_MAIL;
168                                 } else
169                                         BAIL("reading from uvscan process");
170                         }
171                         break;
172                 case 15: FREEZE("self-check failed"); break;
173                 case 19: FREEZE("virus detected and cleaned"); break;
174                 case MAGIC: RESULT("couldn't run uvscan"); break;
175                 default:
176                         RESULT("unknown error code");
177                         header_add(32, "X-uvscan-status: %d\n",
178                                 WEXITSTATUS(status));
179                         break;
180                 }
181         else
182                 BAIL("child exited abnormally");
183
184         return LOCAL_SCAN_ACCEPT;
185 }