/* * This is a very simple (and very quick) cpu friendly mail virus frontend * to clamav daemon virus scanner. If you don't know what clamd is, please * go to read immediately the clamav website and documentation * http://www.clamav.net/. * You may link this wrapper to your MTA or MUA (ex. with maildrop) as a * standard mailfilter that read the mail feeded in the standard input * and output the mail in the standard output. The output mail will have * the header X-Virus-Ret: setted to a number different from 0 if a virus * was found in the mail, and the header Scan-Error: Y if there was a problem * with the virus scanner. * * There is a limit on the number of characters in each line of the header * or body of the mail that is setted at compile time to 6*1024 bytes (#define BUFLEN). * We strongly advice to do not remove this limit, and may be your mailserver software * already have a limit like that. For example Courier MTA set the maximum characters * for each line to 5000. Lines longer than that will be truncated without warning. * * Clamdscan executable absolute path is defined at compile time (#define CLAMDSCAN) * and defaults to /usr/bin/clamdscan. If you don't want to modify the define and you * have clamdscan to another path, you can do a symlinks like that: * ln -s /your_clamdscan_path/clamdscan /usr/bin/clamdscan * * * Copyright by (dAm2K) Dino Ciuffetti , TuxWeb S.r.l. - dino@tuxweb.it * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU General Public License along * with this program (COPYING); if not, go to http://www.fsf.org/ or write * to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, * MA 02111-1307, USA. * */ #include #include #include #include #include #include #include #include #include #include #define BUFLEN (6*1024) #define CLAMDSCAN "/usr/bin/clamdscan" // If the define below is not setted to 0, scandalo will exit with TEMPFAIL status, // getting mails deferred on errors (for ex when clamd daemon is down) #define TEMPFAIL_ON_ERROR 0 char *itoa(int n, char *s, int b) { static char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; int i=0, sign; if ((sign = n) < 0) n = -n; do { s[i++] = digits[n % b]; } while ((n /= b) > 0); if (sign < 0) s[i++] = '-'; s[i] = '\0'; return s; } int main() { FILE * input; short header = 1; // If it's == 0 the header is over char slice[BUFLEN]; int status = 0; char * read_data = 0; pid_t pid; int err; int newfd; char *const arguments[] = {CLAMDSCAN, "--no-summary", "-", NULL}; int father2child_pipefd[2]; int child2father_pipefd[2]; FILE * mailbodyfile; FILE * retclam; signal(SIGPIPE, SIG_IGN); newfd = dup(0); input = fdopen(newfd, "r"); pipe(father2child_pipefd); // Father speaking to child pipe(child2father_pipefd); // Child speaking to father pid = fork(); if (pid > 0) { // Father close(father2child_pipefd[0]); close(child2father_pipefd[1]); // Write to the pipe reading from stdin mailbodyfile = tmpfile(); retclam = fdopen(child2father_pipefd[0], "r"); while (feof(input) == 0) { // Not at the end of FD bzero(slice, BUFLEN); read_data = fgets(slice, BUFLEN, input); // read a line from stdin (max BUFLEN bytes) if (read_data == NULL) { if (TEMPFAIL_ON_ERROR != 0) { exit (99); } else { exit (0); } } if (header == 1) { // We are scanning header if (strcmp(slice, "\r\n") == 0) { // Empty line separate the eader from the body header = 0; } if (strcmp(slice, "\n\r") == 0) { // Empty line separate the eader from the body header = 0; } if (strcmp(slice, "\n") == 0) { // Empty line separate the eader from the body header = 0; } if (strcmp(slice, "\r") == 0) { // Empty line separate the eader from the body header = 0; } err = 0; if (header == 1) { // Print this header line write (1, slice, strlen(slice)); // Also write the header line to child pipe write(father2child_pipefd[1], slice, strlen(slice)); } } if (header == 0) { // End of the header... Scanning mail // Write header to child write(father2child_pipefd[1], "\n", strlen("\n")); header = 2; } if (header == 2) { // Mail BODY section //fprintf(stderr, "DEBUG: End of the header\n"); while (feof(input) == 0) { // Not at the end of FD bzero(slice, BUFLEN); read_data = fgets(slice, BUFLEN, input); // read a line from stdin (max BUFLEN bytes) if (read_data == NULL) { /* if (TEMPFAIL_ON_ERROR != 0) { //exit (99); } else { //exit (0); } */ } // Write body data to the pipe write(father2child_pipefd[1], slice, strlen(slice)); // Also write to a temporary file fwrite(slice, strlen(slice), 1, mailbodyfile); } close(father2child_pipefd[1]); fsync(1); //fprintf (stderr, "Going to sleep\n"); waitpid(pid, &status, 0); // READ FROM PIPE child2father_pipefd[0] IF IT'S A VIRUS bzero(slice, BUFLEN); fread (slice, BUFLEN, 1, retclam); //write (1, slice, strlen(slice)); fclose (retclam); if (WEXITSTATUS(status) > 1) { // Strange things happened to the child proces... write (1, "Scan-Error: Y\n", strlen("Scan-Error: Y\n")); } // Parse output data from child // It must be a clear valid header char *b; char *buf2 = malloc(BUFLEN); int i; write (1, "X-VirScanBy: scandalo 1.0 Stable - http://www.tuxweb.it/?section=progetti/scandalo\n", strlen("X-VirScanBy: scandalo 1.0 Stable - http://www.tuxweb.it/?section=progetti/scandalo\n")); //printf ("X-Virus-Ret: %i\n", WEXITSTATUS(status)); write (1, "X-Virus-Ret: ", strlen("X-Virus-Ret: ")); itoa(WEXITSTATUS(status), buf2, 10); write (1, buf2, strlen(buf2)); write (1, "\n", 1); fsync(1); b = (char *)strtok(slice, "\n"); while (b != NULL) { strcpy(buf2, b); i = strlen(buf2); if (buf2[i-1] == '\r') { buf2[i-1] = '\0'; } write (1, buf2, strlen(buf2)); write (1, "\n", 1); b = (char *)strtok(NULL, "\n"); } write (1, "\n", 1); fsync(1); // Print mail body //fprintf(stderr, "MAIL BODY\n"); rewind(mailbodyfile); while (feof(mailbodyfile) == 0) { // Not at the end of FD bzero(slice, BUFLEN); read_data = fgets(slice, BUFLEN, mailbodyfile); if (read_data == NULL) { } write(1, slice, strlen(slice)); } fsync(1); if (WEXITSTATUS(status) == 0) { exit (0); // No virus found } else if (WEXITSTATUS(status) == 1) { exit (0); // Virus found } else { // Unknown error. if (TEMPFAIL_ON_ERROR != 0) { exit (99); } else { exit (0); } } fclose(mailbodyfile); } } } if (pid == 0) { // Child close (0); close (1); // Close the write side of this pipe close(father2child_pipefd[1]); // Close the read side of this pipe close(child2father_pipefd[0]); // Let clamdscan read on stdin, but this stdin is associated to the read pipe dup2(father2child_pipefd[0], 0); // Let clamdscan speak on stdout, but this stdout is associated to the write pipe dup2(child2father_pipefd[1], 1); //write (child2father_pipefd[1], "X-Scanning: Y\n", strlen("X-Scanning: Y\n")); write (child2father_pipefd[1], "X-Virus-", strlen("X-Virus-")); fsync(child2father_pipefd[1]); err = execve(CLAMDSCAN, arguments, NULL); fprintf(stderr, "Virus Scanner problem: %i: %s\n", err, strerror(errno)); write (child2father_pipefd[1], "NOK: ", strlen("NOK: ")); write (child2father_pipefd[1], strerror(errno), strlen(strerror(errno))); fsync(child2father_pipefd[1]); if (TEMPFAIL_ON_ERROR != 0) { exit (3); } else { exit (0); } } if (pid < 0) { // Error if (TEMPFAIL_ON_ERROR != 0) { exit (99); } else { exit (0); } } fclose(input); return 0; }