/* This program is Yandex spamooborona (c) 2005 agent for use with exim (http://www.exim.org) MTA by its local_scan feature. To enable exim local scan please copy this file to exim source tree Local/local_scan.c, edit Local/Makefile to add LOCAL_SCAN_SOURCE=Local/local_scan.c and compile exim. For exim compilation with local scan feature details please visit http://www.exim.org/exim-html-4.50/doc/html/spec_toc.html#TOC333 For Yandex spamooborona details please visit http://so.yandex.ru */ #include #include #include #include #include #include #include "local_scan.h" #define READ_FAIL(x) ((x) < 0) #define MAX_SIZE_SO 64 * 1024 #define SO_FAILURE_HDR "X-Spam-Flag" static int _OK = 0; static int ERR_WRITE = 53; static int ERR_READ = 54; static int MAX_FAILS_C = 256; static int MAX_PATH = 256; const char temp_dir[] = "/var/spool/spamooborona"; const char socket_name[] = "/var/run/sp-daemon.sock"; typedef int socket_t; static socket_t sock = -1; int iFdInp; static int mOpenTmp (char *pszDir, char *pszPrefix, char *pszPath) { /* Creates a temp file in pszDir (/tmp if NULL) opened for exclusive access. Will be deleted on close. Avoids using tempnam(). Returns fd or -1 if error and sets errno */ int iLen; static int miRand = 1; int iFd = -1; int iI; char *pszSep = ""; if (!pszDir) pszDir = "/var/spool/spamooborona"; iLen = strlen(pszDir); if (iLen > MAX_PATH) { return -1; } if (pszDir[iLen -1] != '/') { pszSep = "/"; }; /* mode 0600 conforms with glibc */ for (iI = 0; iI < 10; iI++) { /* Create path */ sprintf (pszPath, "%s%s%.8s%8.8d%3.3d%6.6d", pszDir, pszSep, pszPrefix, getpid(), miRand++, time(NULL)); umask ((mode_t) 0); if ((iFd = open (pszPath, O_RDWR | O_EXCL | O_CREAT, 0666)) > 0) break; if (errno != EEXIST) return -1; /* Unexpected error */ /* Else, for some reason this file exists. Try again after a short sleep */ sleep(1); } if (iFd == -1) return -1; /* Tried 10 times */ /* Return open file descriptor */ return iFd; } static int ReadFd (int iFdMsg, int fd) { char psMsg [MAX_SIZE_SO]; // max size SO can swallow int iLen, result = _OK; if ((iLen = read (fd, psMsg, sizeof (psMsg))) > 0) { if (write (iFdMsg, psMsg, (unsigned int) iLen) != iLen) result = ERR_WRITE; } else result = ERR_READ; close (iFdMsg); return result; } void CleanupInp (char *sName) { if (sName) unlink (sName); close (iFdInp); return; } int FakeSMTPCommand (socket_t sock, char *command, char *value, char *sName, int Cleanup) { char sCommand[1024]; char answ [3]; int Len; sprintf (sCommand, "%s %s", command, value); if (send (sock, sCommand, strlen (sCommand), 0) != (int) strlen (sCommand)) { log_write (0, LOG_MAIN, "socket sending '%s' error %d", sCommand, errno); if (Cleanup) CleanupInp (sName); return ERR_WRITE; } memset (answ, '\0', sizeof (answ)); Len = read (sock, answ, sizeof (answ)); if (READ_FAIL (Len)) { log_write (0, LOG_MAIN, "read() error %d, len=%d", errno, Len); if (Cleanup) CleanupInp (sName); return ERR_WRITE; } if (strncmp (answ, "OK", 2) != 0) { log_write (0, LOG_MAIN, "server did not confirm, answ=%s", answ); if (Cleanup) CleanupInp (sName); return ERR_WRITE; // Cannot read message error code } return OK; } char *RemoteHost (char *c_IP, char *c_host) { char *sRemoteHost; sRemoteHost = (char *)store_get (c_IP ? strlen (c_IP) : 0 + c_host ? strlen (c_host) : 0 + 4); sprintf (sRemoteHost, "%s [%s]", c_host ? c_host : "", c_IP ? c_IP : ""); return sRemoteHost; } static int SendEnvelope (char *sFile) { int i; char str [256]; char *rh = NULL; // sender IP and hostname rh = RemoteHost (sender_host_address, sender_host_name); if (FakeSMTPCommand (sock, "CONNECT", rh, sFile, 1) != _OK) return ERR_WRITE; // envelope from if (FakeSMTPCommand (sock, "MAILFROM", strlen (sender_address) == 0 ? "MAILER-DAEMON" : (char*) sender_address, sFile, 1) != _OK) return ERR_WRITE; // envelope rcpto for (i = 0; i < recipients_count; i ++) { if (FakeSMTPCommand (sock, "RCPTTO", recipients_list[i].address, sFile, 1) != _OK) return ERR_WRITE; } if (FakeSMTPCommand (sock, "DATA", sFile, sFile, 1) != _OK) return ERR_WRITE; if (FakeSMTPCommand (sock, ".", "", sFile, 1) != _OK) return ERR_WRITE; return _OK; } int GetFiles (char *pInpFile, int local_scan_fd) { /* Returns OK if no errors, else error code. On succesful return, pEnvFile points to Envelope file name and pInpFile points to Message filename */ int iStatus; struct header_line *h_line; iFdInp = mOpenTmp ((char *)temp_dir, "sp-inp", pInpFile); if (iFdInp == -1) { return ERR_WRITE; } /* Emit headers */ h_line = header_list; while (h_line != NULL) { if (h_line->type == '*') // internal header { h_line = h_line->next; continue; } if (write (iFdInp, h_line->text, strlen (h_line->text)) != strlen (h_line->text)) { CleanupInp (""); return ERR_WRITE; } h_line = h_line->next; } if (write (iFdInp, "\n", 1) != 1) { CleanupInp (""); return ERR_WRITE; } /* Read msg */ if ((iStatus = ReadFd (iFdInp, local_scan_fd))) { return iStatus; } /* Return success */ return _OK; } int GetAndTransferMessage (int fd, char *sFile) { char answ [4]; int iStatus; int Len; iStatus = GetFiles ((char *)sFile, fd); if (iStatus != _OK) { log_write (0, LOG_MAIN, "sp-exim: Error %d getting message", iStatus); close (sock); return iStatus; } iStatus = SendEnvelope (sFile); if (iStatus != _OK) { log_write (0, LOG_MAIN, "sp-exim: error %d sending envelope data", iStatus); close (sock); return iStatus; } // fprintf (stderr, "Transmit OK\n"); return _OK; } void header_del (uschar *hdr) { struct header_line *h_line; h_line = header_list; while (h_line != NULL) { if (h_line->type == '*') // internal header { h_line = h_line->next; continue; } if (strncmp (h_line->text, hdr, strlen(hdr)) == 0) { h_line->type = '*'; while (h_line->next && (*h_line->next->text == ' ' || *h_line->next->text == '\t')) { h_line->next->type = '*'; h_line = h_line->next; } } h_line = h_line->next; } } void AlterSubject (char *label) { struct header_line *h_line; char subject [1024]; char *strP; h_line = header_list; memset (subject, '\0', sizeof (subject)); while (h_line != NULL) { if (h_line->type == '*') // internal header { h_line = h_line->next; continue; } if (strncmp (h_line->text, "Subject", strlen("Subject")) == 0) { strP = strchr (h_line->text, ':'); strncpy (subject, ++strP, sizeof (subject) - strlen (strP) - 1); while (h_line->next && (*h_line->next->text == ' ' || *h_line->next->text == '\t')) { strcat (subject, h_line->next->text); h_line = h_line->next; } header_del (US "Subject"); break; } h_line = h_line->next; } header_add (' ', "Subject: [%s] %s", label, subject ? subject : ""); } int WaitForScanResult (uschar **resStr) { int Len; int rej, result = LOCAL_SCAN_ACCEPT, answer_size, spm = 0; char answ [4096]; char *strP, *tok, *tmp, *spmStr = NULL; char hdr [256]; char hdrv [4096]; memset (hdr, '\0', sizeof (hdr)); memset (hdrv, '\0', sizeof (hdrv)); memset (answ, '\0', sizeof (answ)); Len = read (sock, answ, sizeof (answ) - 1); if (strncmp (answ, "SODAEMON ", 9) == 0) { strP = (char *)answ; for (tok = (char *)strtok (strP, "\n"); tok; tok = (char *)strtok (NULL, "\n")) { // signature always goes first if (strncmp (tok, "SODAEMON ", 9) == 0) { if (sscanf (tok, "%*s %d", &answer_size) == 1) { if (answer_size == 0) // empty reply { strcpy (hdr, SO_FAILURE_HDR); strcpy (hdrv, "SKIP"); result = LOCAL_SCAN_ACCEPT; break; } else if (answer_size > sizeof (answ) - 1) log_write(0, LOG_MAIN, "sp-exim: daemon reports %d size answer", answer_size); } continue; } // reject or accept flag if (strncmp (tok, "REJECT ", 7) == 0) { if (sscanf (tok, "%*s %d", &rej) == 1) result = rej ? LOCAL_SCAN_REJECT : LOCAL_SCAN_ACCEPT; continue; } // reject string if (strncmp (tok, "REJECTSTR ", 10) == 0) { tmp = strchr (tok, ' '); if (result == LOCAL_SCAN_REJECT) *resStr = (uschar *)strdup (++tmp); continue; } // spam flag if (strncmp (tok, "SPAM ", 5) == 0) { if (sscanf (tok, "%*s %d", &spm) != 1) { spm = 0; log_write(0, LOG_MAIN, "sp-exim: Error receiving spam flag"); } continue; } // spam label if (strncmp (tok, "SPAMSTR ", 8) == 0) { tmp = strchr (tok, ' '); if (spm) spmStr = strdup (++tmp); continue; } if (! isspace (*tok)) // New header { if (strlen (hdr)) { header_del ((uschar *) hdr); header_add (' ', "%s: %s\n", hdr, hdrv); } memset (hdr, '\0', sizeof (hdr)); memset (hdrv, '\0', sizeof (hdrv)); tmp = strchr (tok, ':'); if (tmp) *tmp = '\0'; strncpy (hdr, tok, sizeof (hdr) - 1); ++tmp; while (isspace (*tmp)) ++tmp; strncpy (hdrv, tmp, sizeof (hdrv) - 1); } else // append multiline header value { hdrv [strlen (hdrv)] = '\n'; strncpy (&hdrv [strlen (hdrv)], tok, sizeof (hdrv) - strlen (hdrv) - 1); } } // do not forget the last header if (strlen (hdr)) { header_del ((uschar *) hdr); header_add (' ', "%s: %s\n", hdr, hdrv); } if (spm && spmStr) AlterSubject (spmStr); } else { result = LOCAL_SCAN_ACCEPT; log_write(0, LOG_MAIN, "sp-exim: wrong signature in answer: %s", answ); } return result; } int local_scan(int fd, uschar **return_text) { int retval = _OK; int ccnt; struct sockaddr_un sockaddr; char sFileInp [MAX_PATH + 81]; // Socket stuff if ((sock = socket (AF_UNIX, SOCK_STREAM, 0)) < 0) { log_write(0, LOG_MAIN, "sp-exim: socket() failed"); exit (1); } memset (&sockaddr, '\0', sizeof (struct sockaddr_un)); sockaddr.sun_family = AF_UNIX; if (sizeof (socket_name) > sizeof (sockaddr.sun_path)) { close (sock); log_write(0, LOG_MAIN, "sp-exim: UNIX socket name %s too long", socket_name); exit (1); } strcpy (sockaddr.sun_path, socket_name); for (ccnt = 0; ccnt <= MAX_FAILS_C; ccnt ++) { if (connect (sock, (struct sockaddr *) &sockaddr, sizeof (struct sockaddr_un)) < 0) { if (ccnt < MAX_FAILS_C) usleep (100); else { close (sock); log_write(0, LOG_MAIN, "sp-exim: socked connect to %s failed", (char *)socket_name); return LOCAL_SCAN_TEMPREJECT; } } else break; } if (GetAndTransferMessage (fd, (char *)sFileInp) != _OK) { close (sock); unlink (sFileInp); SPOOL_DATA_START_OFFSET; return LOCAL_SCAN_TEMPREJECT; } retval = WaitForScanResult (return_text); unlink (sFileInp); close (sock); SPOOL_DATA_START_OFFSET; return retval; } /* End of local_scan.c */