#include #include #include #undef DEBUG // avoiding warnings: exim defines it too #include #include "../command-gpl/kavcommand.h" #include "../command-gpl/kavsockets.h" #include "../command-gpl/kaverrors.h" #include "misc.h" int mboxOpen(const char *filename) { int fd, ret; struct flock flk; // open file while ((fd = open(filename, O_RDWR)) == -1 && errno == EINTR) { } if (fd == -1) { log_write(0, LOG_MAIN | LOG_PANIC, "kav4lms: Cannot open mbox"); return (-1); } /* set a write-lock on file NOTE: according the exim's sources only the first SPOOL_DATA_START_OFFSET bytes are locked, so we lock the rest of the file for possible changes; trying to lock the entire file may fail */ flk.l_start = SPOOL_DATA_START_OFFSET; flk.l_len = 0; flk.l_type = F_WRLCK; flk.l_whence = SEEK_SET; while ((ret = fcntl(fd, F_SETLK, &flk) == -1) && errno == EINTR) { } if (ret == -1) { log_write(0, LOG_MAIN | LOG_PANIC, "kav4lms: cannot lock mailbox"); return (-1); } debug_printf(0, LOG_MAIN, "kav4lms: Locked mbox"); return (fd); } int mboxClose(const int fd) { int ret = 0; struct flock flk; // unlock the mbox flk.l_start = SPOOL_DATA_START_OFFSET; flk.l_len = 0; flk.l_type = F_UNLCK; flk.l_whence = SEEK_SET; while ((ret = fcntl(fd, F_SETLK, &flk) == -1) && errno == EINTR) { } if (ret == -1) log_write(0, LOG_MAIN | LOG_PANIC, "kav4lms: Cannot unlock mbox"); else debug_printf(0, LOG_MAIN, "kav4lms: Unlocked mbox"); // set the position to SPOOL_DATA_START_OFFSET, // because local_scan() expects it; Exim doesn't complain about it // but a filter using local_scan() API may not function properly lseek(fd, SPOOL_DATA_START_OFFSET, SEEK_SET); // close mbox while ((close(fd)) == -1 && errno == EINTR) { } return (ret); } int mboxProcess(int mbox, const char *dest, char **rejectMsg, char *tmpPath) { struct kavHeaderList *headers = 0; int tfd, ret; char *tmpl = malloc(strlen(tmpPath) + TMPFILE_TMPL_SIZE + 1); if (tmpl == 0) { log_write(0, LOG_MAIN | LOG_PANIC, "kav4lms: Cannot allocate memory"); return (-1); } // build temporary file template strcpy(tmpl, tmpPath); strcat(tmpl, TMPFILE_TMPL); // create temporary file if ((tfd = mkstemp(tmpl)) == -1) { free(tmpl); log_write(0, LOG_MAIN | LOG_PANIC, "kav4lms: Cannot create temporary file"); return (-1); } // write the headers into the file if (writeHeadersToTemp(tfd) == -1) { free(tmpl); close(tfd); return (-1); } // write message body if (writeBodyToTemp(tfd, mbox) == -1) { free(tmpl); close(tfd); return (-1); } debug_printf(0, LOG_MAIN, "kav4lms: Requesting scanning from kavmd"); // request for scan ret = kavProcess(tmpl, dest, (char *) expand_string(US"${sender_address}"), (char *) expand_string(US"${recipients}"), (char *) expand_string(US"${message_exim_id}"), (char *) expand_string(US"${sender_host_address}"), (char *) expand_string(US"${received_ip_address}"), &headers, rejectMsg); debug_printf(0, LOG_MAIN, "kav4lms: Scan returned %d", ret); if (updateHeaders(ret, headers) == -1) { free(tmpl); log_write(0, LOG_MAIN | LOG_PANIC, "kav4lms: Cannot update headers"); close(tfd); return (-1); } // write the file back into exim's queue if (writeBodyToMbox(mbox, tfd) == -1) { free(tmpl); log_write(0, LOG_MAIN | LOG_PANIC, "kav4lms: Cannot update body"); close(tfd); return (-1); } // free resources unlink(tmpl); free(tmpl); close(tfd); return (ret); } int writeHeadersToTemp(int fd) { int ret = 0; struct header_line *p = header_list; while (p != 0) { if (p->type != '*') while ((ret = write(fd, p->text, p->slen)) == -1 && errno == EINTR) { } if (ret == -1) { log_write(0, LOG_MAIN | LOG_PANIC, "kav4lms: Cannot write to temporary file"); return (-1); } p = p->next; } // separate main headers from body while ((ret = write(fd, "\n", 1)) == -1 && errno == EINTR) { } if (ret == -1) { log_write(0, LOG_MAIN | LOG_PANIC, "kav4lms: Cannot write to temporary file"); return (-1); } return (0); } int writeBodyToTemp(int fd, int mbox) { int ret; char buf[10240]; lseek(mbox, SPOOL_DATA_START_OFFSET, SEEK_SET); do { while ((ret = read(mbox, &buf, sizeof(buf))) == -1 && errno == EINTR) { } if (ret == -1) { log_write(0, LOG_MAIN | LOG_PANIC, "kav4lms: Cannot read from mbox file"); return (-1); } while ((ret = write(fd, &buf, ret)) == -1 && errno == EINTR) { } } while ((ret == -1 && errno == EINTR) || ret > 0); if (ret == -1) { log_write(0, LOG_MAIN | LOG_PANIC, "kav4lms: Cannot write to temporary file"); return (-1); } return (0); } int writeBodyToMbox(int mbox, int fd) { int ret = 0, count = 0; char buf[10240]; // locate body start lseek(fd, 0, SEEK_SET); do { while ((ret = read(fd, &buf, 1)) == -1 && errno == EINTR) { } if (buf[0] == 0x0d && count % 2 == 0) ++count; else if (buf[0] == 0x0a && count % 2 == 0) count += 2; else if (buf[0] == 0x0a && count % 2 == 1) ++count; else count = 0; } while (count < 4 && ret == 1); ftruncate(mbox, SPOOL_DATA_START_OFFSET); lseek(mbox, 0, SEEK_END); do { while ((ret = read(fd, &buf, sizeof(buf))) == -1 && errno == EINTR) { } if (ret > 0) { ReplaceCRLFWithLF(buf, &ret); while ((ret = write(mbox, &buf, ret)) == -1 && errno == EINTR) { } } } while (ret > 0); return (ret == -1 ? -1 : 0); } int processResult(int res, uschar **yield, const char *rejectMsg) { // check result if (res == -1) { *yield = string_copy(US"kav4lms: temporary failure (cannot contact kavmd)"); return (OK); } else if (res & CMD_MTA_ACTION_DROP || res & CMD_MTA_ACTION_REJECT || res & CMD_MTA_ACTION_TEMPFAIL) { // check individual values if (res & CMD_MTA_ACTION_TEMPFAIL) { *yield = string_copy(US"kav4lms: temporary failure"); return (FAIL); } if (res & CMD_MTA_ACTION_DROP) { *yield = string_copy(US"kav4lms: drop"); return (OK); } if (res & CMD_MTA_ACTION_REJECT) { if (*rejectMsg == '\0') *yield = string_copy(US"kav4lms: reject"); else *yield = string_sprintf("kav4lms: reject " "(Reason: %s)", rejectMsg); return (OK); } } else // if none of the above is true, // the MTA should continue *yield = string_copy(US"kav4lms: continue"); return (OK); } int updateHeaders(int res, struct kavHeaderList *headers) { struct kavHeaderList *it; if (res & CMD_MTA_ACTION_MAKETEXT) { header_add_at_position(1, US"MIME-Version", 0, ' ', "MIME-Version: 1.0\n"); header_remove(1, US"MIME-Version"); header_add_at_position(1, US"Content-Type", 0, ' ', "Content-Type: text/plain\n"); header_remove(1, US"Content-Type"); header_remove(-1, US"Content-Disposition"); header_remove(-1, US"Content-Transfer-Encoding"); } for (it = headers; it != 0; it = it->next) { char *name = 0; char *header; int size = 0; int headerPresence = 0; // cleanup header line debug_printf(0, LOG_MAIN, "slen before cleanup: %d", it->data.slen); ReplaceCRLFWithLF(it->data.data, &it->data.slen); debug_printf(0, LOG_MAIN, "slen after cleanup: %d", it->data.slen); // check if last char is '\n' if (it->data.data[it->data.slen - 1] == '\n') { // make a copy of the data, and add a NUL terminator if ((header = malloc(it->data.slen + 1)) == 0) return (-1); strncpy(header, it->data.data, it->data.slen); header[it->data.slen] = 0; } else { debug_printf(0, LOG_MAIN, "Added LF to end of header line"); // make a copy of the data, and add '\n' + NUL terminator if ((header = malloc(it->data.slen + 2)) == 0) return (-1); strncpy(header, it->data.data, it->data.slen); header[it->data.slen] = '\n'; header[it->data.slen + 1] = 0; } if (header == 0) { log_write(0, LOG_MAIN | LOG_PANIC, "kav4lms: Cannot allocate memory"); return (-1); } // compute the size of header name and make a copy name = strchr(header, ':'); if (name == NULL) return (-1); size = name - header; if ((name = malloc(size + 1)) == 0) { free(header); return (-1); } strncpy(name, header, size); name[size] = 0; log_write(0, LOG_MAIN, "header line : [%d] \"%s\"", strlen(header), header); debug_printf(0, LOG_MAIN, "header name : [%d] \"%s\"", strlen(name), name); // check if the header already exists headerPresence = headerExists((uschar *) name, size); debug_printf(0, LOG_MAIN, "headerPresence = %d", headerPresence); // insert the new header after the first matched name header_add_at_position(1, (uschar *) name, 0, ' ', header); // check if the header isn't X-Anti-Virus, and if it // existed before it has been added in the above code... if (strncasecmp(name, HEADER_XANTIVIRUS, HEADER_XANTIVIRUS_SIZE) != 0 && headerPresence == 1) { // ... if so, remove the old header header_remove(1, (uschar *) name); debug_printf(0, LOG_MAIN, "removed header \"%s\"", name); } free(header); free(name); } return (0); } void ReplaceCRLFWithLF(char *header, int *size) { int i = 1; int replaced = 0; int orig_size = *size; for ( ; i < orig_size; ++i) { if (header[i - 1] == '\r' && header[i] == '\n') { ++replaced; --(*size); } if (replaced) header[i - replaced] = header[i]; } if (replaced) header[*size] = 0; } int headerExists(uschar *name, int size) { struct header_line *it = header_list; for ( ; it != 0; it = it->next) { if (header_testname(it, name, size, 1) != 0) return (1); } return (0); } int checkDirAccess(const char *path) { struct stat sb; // check access if (stat(path, &sb) == -1) { if (errno == ENOENT) { // if it does not exist, try to make it if (mkdir(path, S_IRWXU) == -1) { // cannot make dir log_write(0, LOG_MAIN | LOG_PANIC, "kav4lms: could not create directory (%s)", strerror(errno)); return (-1); } else { debug_printf(0, LOG_MAIN, "kav4lms: created temporary directory %s", path); return (0); } } else { // stat() error log_write(0, LOG_MAIN | LOG_PANIC, "kav4lms: stat() error: %s", strerror(errno)); return (-1); } } // check if it's a dir if (!S_ISDIR(sb.st_mode)) { log_write(0, LOG_MAIN | LOG_PANIC, "kav4lms: not a directory: %s", path); return (-1); } // check access if (!((sb.st_uid == geteuid() && sb.st_mode & S_IRWXU) || (sb.st_gid == getegid() && sb.st_mode & S_IRWXG)) || sb.st_mode & S_IRWXO) { log_write(0, LOG_MAIN | LOG_PANIC, "kav4lms: need access to directory %s", path); return (-1); } return (0); }