/*
* QScan virus filter for Qmail/Sophos antivirus
* Jedi/Sector One <j@4u.net>
*/
#include <config.h>
#include "qscan.h"
#include "qscan_p.h"
#ifdef WITH_DMALLOC
# include <dmalloc.h>
#endif
static void write_log(const int priority, const char * const format, ...)
{
va_list args;
va_start(args, format);
openlog("qscan", LOG_NDELAY | LOG_PID, LOG_MAIL);
vsyslog(priority, format, args);
closelog();
va_end(args);
}
static void perror_log(const char * const msg)
{
write_log(LOG_ERR, "%s : %s", msg, strerror(errno));
}
static int spawn_ripmime(void)
{
if (pipe(ripmime_pipe) != 0) {
perror_log("pipe");
return -1;
}
if ((ripmime_pid = fork()) == (pid_t) -1) {
perror_log("fork");
(void) close(ripmime_pipe[0]);
(void) close(ripmime_pipe[1]);
return -1;
}
if (ripmime_pid == (pid_t) 0) {
if (close(ripmime_pipe[1]) != 0) {
perror_log("close");
exit(EXIT_FAILURE);
}
if (ripmime_pipe[0] != 0) {
if (dup2(ripmime_pipe[0], 0) == -1) {
perror_log("dup2");
exit(EXIT_FAILURE);
}
if (close(ripmime_pipe[0]) != 0) {
perror_log("close");
exit(EXIT_FAILURE);
}
}
(void) execlp(RIPMIME, RIPMIME, RIPMIME_ARGS, ripmime_destdir,
(char *) NULL);
perror_log("execlp");
exit(EXIT_FAILURE);
}
if (close(ripmime_pipe[0]) != 0) {
perror_log("close");
return -1;
}
return 0;
}
static int spawn_qq(void)
{
if (ripmime_pid <= (pid_t) 0) {
perror_log("need ripmime before qq");
return -1;
}
if (pipe(qq_pipe) != 0) {
perror_log("pipe");
return -1;
}
if (pipe(qq_pipe_envelope) != 0) {
perror_log("pipe");
return -1;
}
if ((qq_pid = fork()) == (pid_t) -1) {
perror_log("fork");
(void) close(qq_pipe[0]);
(void) close(qq_pipe[1]);
(void) close(qq_pipe_envelope[0]);
(void) close(qq_pipe_envelope[1]);
return -1;
}
if (qq_pid == (pid_t) 0) {
if (close(ripmime_pipe[1]) != 0) {
perror_log("close");
exit(EXIT_FAILURE);
}
if (close(qq_pipe[1]) != 0) {
perror_log("close");
exit(EXIT_FAILURE);
}
if (qq_pipe[0] != 0) {
if (dup2(qq_pipe[0], 0) == -1) {
perror_log("dup2");
exit(EXIT_FAILURE);
}
if (close(qq_pipe[0]) != 0) {
perror_log("close");
exit(EXIT_FAILURE);
}
}
if (close(qq_pipe_envelope[1]) != 0) {
perror_log("close");
exit(EXIT_FAILURE);
}
if (qq_pipe_envelope[0] != 1) {
if (dup2(qq_pipe_envelope[0], 1) == -1) {
perror_log("dup2");
exit(EXIT_FAILURE);
}
(void) close(qq_pipe_envelope[1]);
}
(void) execlp(QMAIL_QUEUE, QMAIL_QUEUE, (char *) NULL);
perror_log("execlp");
exit(EXIT_FAILURE);
}
if (close(qq_pipe[0]) != 0) {
perror_log("close");
return -1;
}
return 0;
}
static int find_concurrency(void)
{
DIR *dir;
const struct dirent *entry;
int concurrency = 0;
if ((dir = opendir(RIPMIME_DESTDIR_BASE)) == NULL) {
perror_log("unable to opendir() the ripmime base directory");
return -1;
}
while ((entry = readdir(dir)) != NULL) {
if (entry->d_name == NULL || *(entry->d_name) == '.') {
continue;
}
concurrency++;
if (concurrency >= INT_MAX) {
break;
}
}
if (closedir(dir) != 0) {
perror_log("unable to closedir() the ripmime base directory");
return -1;
}
return concurrency;
}
static int cleanup_destdir(void)
{
DIR *dir;
const struct dirent *entry;
const char *found;
int ret = 0;
if (chdir(ripmime_destdir) != 0) {
perror_log("unable to chdir to ripmime_destdir");
return -1;
}
if ((dir = opendir(".")) == NULL) {
perror_log("unable to opendir() ripmime_destdir");
return -1;
}
while ((entry = readdir(dir)) != NULL) {
if ((found = entry->d_name) == NULL ||
(found[0] == '.' &&
(found[1] == 0 ||
(found[1] == '.' && found[2] == 0)))) {
continue;
}
if (unlink(found) != 0) {
ret = -1;
}
}
if (closedir(dir) != 0) {
perror_log("unable to closedir() ripmime_destdir");
return -1;
}
return ret;
}
static int has_attachments(void)
{
DIR *dir;
const struct dirent *entry;
const char *found;
struct stat st;
int ret = 0;
if (chdir(ripmime_destdir) != 0) {
perror_log("unable to chdir() to ripmime_destdir");
return -1;
}
if ((dir = opendir(".")) == NULL) {
perror_log("unable to opendir() ripmime_destdir");
return -1;
}
while ((entry = readdir(dir)) != NULL) {
if ((found = entry->d_name) == NULL ||
(found[0] == '.' &&
(found[1] == 0 ||
(found[1] == '.' && found[2] == 0)))) {
continue;
}
if (lstat(found, &st) != 0) {
write_log(LOG_ERR, "unable to lstat [%s]", found);
ret = -1;
} else if (!S_ISREG(st.st_mode) || (st.st_mode & S_IRUSR) ==
(mode_t) 0) {
write_log(LOG_ERR, "bad file [%s] to scan", found);
if (unlink(found) != 0) {
perror_log("unable to unlink a bad file");
}
ret = -1;
} else if (ret >= 0 && ret < INT_MAX) {
ret++;
}
}
if (closedir(dir) != 0) {
perror_log("unable to closedir() ripmime_destdir");
return -1;
}
return ret;
}
static void wait_everybody_and_die(const int ret)
{
int status;
pid_t pid;
if (*ripmime_destdir != 0) {
if (cleanup_destdir() != 0) {
perror_log("error while cleaning up destdir before dying");
}
if (rmdir(ripmime_destdir) != 0) {
perror_log("error while removing destdir before dying");
}
}
if (ripmime_pipe[1] > 1) {
(void) close(ripmime_pipe[1]);
}
if (qq_pipe[1] > 1) {
(void) close(qq_pipe[1]);
}
if (qq_pipe_envelope[1] > 1) {
(void) close(qq_pipe_envelope[1]);
}
if (av_pipe[0] > 1) {
(void) close(av_pipe[0]);
}
(void) close(0);
(void) close(1);
for(;;) {
if ((pid = wait(&status)) == (pid_t) -1 && errno != EINTR) {
exit(ret);
}
}
}
static int spawn_antivirus(char ** const foundvirus)
{
char line[AV_LINE_MAX];
FILE *avfp;
int status;
pid_t pid;
int ret = 0;
if (foundvirus == NULL) {
perror_log("foundvirus is a NULL pointer");
return -1;
}
*foundvirus = NULL;
if (pipe(av_pipe) != 0) {
perror_log("pipe");
return -1;
}
if ((av_pid = fork()) == (pid_t) -1) {
perror_log("fork");
(void) close(av_pipe[0]);
(void) close(av_pipe[1]);
return -1;
}
if (av_pid == (pid_t) 0) {
if (av_pipe[1] != 1) {
if (dup2(av_pipe[1], 1) == -1) {
perror_log("dup2");
exit(EXIT_FAILURE);
}
if (close(av_pipe[1]) != 0) {
perror_log("close");
exit(EXIT_FAILURE);
}
}
if (close(av_pipe[0]) != 0 ||
close(qq_pipe[1]) != 0 ||
close(qq_pipe_envelope[1]) != 0) {
perror_log("close");
exit(EXIT_FAILURE);
}
if (chdir(ripmime_destdir) != 0) {
perror_log("ripmime_destdir vanished, won't scan");
exit(EXIT_FAILURE);
}
(void) nice(SWEEP_NICE);
(void) alarm(SWEEP_TIMEOUT);
(void) execlp(SWEEP, SWEEP, SWEEP_ARGS, ripmime_destdir,
(char *) NULL);
perror_log("execlp");
exit(EXIT_FAILURE);
}
if (close(av_pipe[1]) != 0) {
perror_log("close");
wait_everybody_and_die(99);
}
if ((avfp = fdopen(av_pipe[0], "r")) == NULL) {
perror_log("fdopen");
wait_everybody_and_die(99);
}
while (fgets(line, (int) sizeof line, avfp) != NULL) {
register char *virus;
if ((virus = strstr(line, SWEEP_VIRUS_PREFIX)) != NULL) {
register char *crlf;
if (ret < INT_MAX) {
ret++;
}
if (*foundvirus != NULL) {
continue;
}
while ((crlf = strrchr(line, '\n')) != NULL ||
(crlf = strrchr(line, '\r')) != NULL) {
*crlf = 0;
}
*virus = 0;
virus = strrchr(line, ' ');
*foundvirus = strdup(++virus);
}
}
if (fclose(avfp) != 0) {
perror_log("close");
}
(void) close(av_pipe[0]);
while ((pid = wait(&status)) == (pid_t) -1 && errno == EINTR);
if (pid != av_pid) {
if (close(qq_pipe[1]) != 0) {
perror_log("close qq pipe");
}
if (close(qq_pipe_envelope[1]) != 0) {
perror_log("close envelope pipe");
}
if (pid == qq_pid) {
perror_log("we were waiting for the scanner to die, qq died first");
} else {
perror_log("unexpected pid died");
}
while ((pid = wait(&status)) == (pid_t) -1 && errno == EINTR);
if (pid == av_pid) {
perror_log("we were waiting for qq to die, but scanner died");
} else {
perror_log("unexpected pid died");
}
wait_everybody_and_die(99);
}
return ret;
}
static void delay_seconds(const time_t seconds)
{
#ifdef HAVE_NANOSLEEP
struct timespec ts;
ts.tv_sec = seconds;
ts.tv_nsec = 0L;
(void) nanosleep(&ts, NULL);
#elif defined(HAVE_USLEEP)
# ifdef USLEEP_RETURNS_INT
if (usleep((unsigned long) seconds * 1000000UL) != 0) {
if (errno == EINVAL)
# endif
{
unsigned long wr = (unsigned long) seconds;
do {
(void) usleep(999999UL);
wr--;
} while (wr > 0UL);
}
# ifdef USLEEP_RETURNS_INT
}
# endif
#else
(void) sleep((unsigned int) seconds);
#endif
}
static int viruscan_destdir(void)
{
char *foundvirus;
int nb_attachments;
int avret;
int ret = 0;
int tries = SCAN_MAX_RETRIES;
if ((nb_attachments = has_attachments()) < 0) {
perror_log("error while checking for attachments");
return -1;
}
if (nb_attachments == 0) {
if (rmdir(ripmime_destdir) != 0) {
perror_log("unable to remove ripmime_destdir, supposed to be empty");
if (cleanup_destdir() != 0) {
perror_log("tried to cleanup destdir, but still got troubles");
}
if (rmdir(ripmime_destdir) != 0) {
perror_log("last chance to unlink ripmime_destdir failed");
}
}
*ripmime_destdir = 0;
return 0;
}
while (find_concurrency() >= SCAN_CONCURRENCY) {
if (tries <= 0) {
write_log(LOG_INFO, "max scanning concurrency reached, "
"intentionnaly delaying");
ret = -1;
goto clean;
}
tries--;
delay_seconds((time_t) SCAN_DELAY_BETWEEN_RETRIES);
}
if ((avret = spawn_antivirus(&foundvirus)) < 0) {
perror_log("antivirus failure");
ret = -1;
goto clean;
} else if (avret > 0) {
write_log(LOG_INFO, "%s in a mail from [%s]", foundvirus,
getenv("TCPREMOTEIP"));
if (avret > 1) {
write_log(LOG_INFO,
"%d other virus %s found in the same mail from [%s]",
avret - 1, avret > 2 ? "were" : "was",
getenv("TCPREMOTEIP"));
}
ret = 1;
}
clean:
if (cleanup_destdir() != 0) {
perror_log("destdir cleanup after virus checking failed");
}
if (rmdir(ripmime_destdir) != 0) {
perror_log("unlinking ripmime_destdir failed after virus checking");
}
*ripmime_destdir = 0;
return ret;
}
static int create_destdir_name(void)
{
(void) snprintf(ripmime_destdir, sizeof ripmime_destdir,
RIPMIME_DESTDIR_BASE "/%lu_%lu",
(unsigned long) now, (unsigned long) getpid());
return 0;
}
static int create_destdir(void)
{
if (create_destdir_name() != 0) {
perror_log("unable to create the destdir name");
return -1;
}
if (mkdir(ripmime_destdir, (mode_t) 0750) != 0) {
if (chdir(ripmime_destdir) != 0) {
perror_log("unable to create/access ripmime_destdir");
return -1;
}
if (cleanup_destdir() != 0) {
perror_log("unable to clean up destdir");
if (rmdir(ripmime_destdir) != 0) {
perror_log("unable to remote destdir");
}
*ripmime_destdir = 0;
return -1;
}
}
return 0;
}
static int wait_ripmime(void)
{
int status;
pid_t pid;
while ((pid = wait(&status)) == (pid_t) -1 && errno == EINTR);
if (pid != ripmime_pid) {
if (close(qq_pipe[1]) != 0) {
perror_log("close qq pipe");
}
if (close(qq_pipe_envelope[1]) != 0) {
perror_log("close envelope pipe");
}
if (pid == qq_pid) {
perror_log("we were waiting for ripmime to die, but qq died first");
} else {
perror_log("unexpected pid died");
}
while ((pid = wait(&status)) == (pid_t) -1 && errno == EINTR);
if (pid == ripmime_pid) {
perror_log("we were waiting for qq to die, but ripmime died");
} else {
perror_log("unexpected pid died");
}
wait_everybody_and_die(99);
}
return 0;
}
static int safe_write(const int fd, const void *buf_, size_t count)
{
ssize_t written;
register const char *buf = (const char *) buf_;
while (count > (size_t) 0U) {
for (;;) {
if ((written = write(fd, buf, count)) <= (ssize_t) 0) {
if (errno == EAGAIN) {
sleep(1);
} else if (errno != EINTR) {
return -1;
}
continue;
}
break;
}
buf += written;
count -= written;
}
return 0;
}
static int add_received(void)
{
static const char *months[] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
char date[256];
struct tm *tm;
if ((tm = gmtime(&now)) == NULL ||
tm->tm_mon < 0 || tm->tm_mon > 11) {
return -1;
}
(void) snprintf(date, sizeof date,
"Received: from qmail by qscan (mail filter); "
"%d %s %d %02d:%02d:%02d +0000\n",
tm->tm_mday, months[tm->tm_mon],
tm->tm_year + 1900,
tm->tm_hour, tm->tm_min, tm->tm_sec);
return safe_write(qq_pipe[1], date, strlen(date));
}
static void timeout(int sig)
{
(void) sig;
wait_everybody_and_die(52);
}
static void check_bypass(void)
{
if (getenv("QS_BYPASS") == NULL && getenv("TCPREMOTEIP") != NULL) {
return;
}
(void) execlp(QMAIL_QUEUE, QMAIL_QUEUE, (char *) NULL);
exit(99);
}
static int inject_message(void)
{
char buf[MSG_BUFSIZE];
ssize_t len;
ssize_t ripmime_written;
ssize_t qq_written;
ssize_t written;
for (;;) {
if ((len = read(0, buf, sizeof buf)) < (ssize_t) 0) {
if (errno == EINTR) {
continue;
}
perror_log("read() troubles when reading the message feed");
return -1;
}
if (len == (ssize_t) 0) {
return 0;
}
ripmime_written = qq_written = (ssize_t) 0;
do {
if ((written = write(ripmime_pipe[1], buf + ripmime_written,
(size_t) (len - ripmime_written))) <
(ssize_t) 0) {
if (errno == EINTR) {
continue;
}
perror_log("write() troubles when feeding ripmime");
return -1;
}
ripmime_written += written;
if ((written = write(qq_pipe[1], buf + qq_written,
(size_t) (len - qq_written))) <
(ssize_t) 0) {
if (errno == EINTR) {
continue;
}
perror_log("write() troubles when feeding qq");
return -1;
}
qq_written += written;
} while (ripmime_written < len || qq_written < len);
}
}
static int inject_envelope(void)
{
char buf[ENVELOPE_BUFSIZE];
ssize_t len;
for (;;) {
if ((len = read(1, buf, sizeof buf)) < (ssize_t) 0) {
if (errno == EINTR) {
continue;
}
perror_log("read() troubles when reading the envelope");
return -1;
}
if (len == (ssize_t) 0) {
return 0;
}
if (safe_write(qq_pipe_envelope[1], buf, (size_t) len) != 0) {
perror_log("write() troubles when feeding the envelope");
return -1;
}
}
}
int main(void)
{
int nbviruses;
(void) signal(SIGALRM, timeout);
(void) alarm(QSCAN_TIMEOUT);
now = time(NULL);
check_bypass();
if (create_destdir() != 0) {
perror_log("unable to create the destination directory");
return 1;
}
if (spawn_ripmime() != 0) {
perror_log("unable to spawn ripmime");
return 1;
}
if (spawn_qq() != 0) {
perror_log("unable to spawn qq");
return 1;
}
if (add_received() != 0) {
perror_log("unable to add the received line");
wait_everybody_and_die(99);
}
if (inject_message() != 0) {
perror_log("unable to inject message");
wait_everybody_and_die(99);
}
if (close(ripmime_pipe[1]) != 0) {
perror_log("close ripmime pipe");
wait_everybody_and_die(99);
}
if (wait_ripmime() != 0) {
perror_log("troubles when waiting for ripmime");
wait_everybody_and_die(99);
}
if ((nbviruses = viruscan_destdir()) < 0) {
write_log(LOG_INFO, "virus scanning delayed");
wait_everybody_and_die(99);
}
if (nbviruses != 0) {
write_log(LOG_INFO, "delivery from [%s] refused",
getenv("TCPREMOTEIP"));
wait_everybody_and_die(39);
}
if (inject_envelope() != 0) {
perror_log("unable to inject the envelope to qq");
wait_everybody_and_die(99);
}
if (close(qq_pipe[1]) != 0) {
perror_log("close qq pipe");
wait_everybody_and_die(99);
}
if (close(qq_pipe_envelope[1]) != 0) {
perror_log("close envelope pipe");
wait_everybody_and_die(99);
}
wait_everybody_and_die(0);
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1