| 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 | } |