]> www.infradead.org Git - users/dwmw2/crm114-spamd.git/commitdiff
port dspam-spamd to crm as crm114-spamd with new features
authorJohannes Berg <johannes@sipsolutions.net>
Fri, 5 Oct 2007 11:21:18 +0000 (13:21 +0200)
committerJohannes Berg <johannes@sipsolutions.net>
Fri, 5 Oct 2007 11:21:18 +0000 (13:21 +0200)
.gitignore
Makefile
README
crm114-spamd.c [new file with mode: 0644]
dspam-spamd.c [deleted file]

index c265aa4a179a7043b4e4d78c655667fbb5bc8c33..52864a6384186551edbe00f8efa8fcb3af0e30b2 100644 (file)
@@ -1,4 +1,3 @@
 Makefile
-config.log
-config.status
-src/Makefile
+*~
+crm114-spamd
index e39656c97cdf7e51316c9f948dfc13218af2214b..cf35e01216040f73e12e3edf7d46c3389c4ab719 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
 CFLAGS = -Wall
 LDFLAGS = -lident
 
-dspam-spamd: dspam-spamd.c
+crm114-spamd: crm114-spamd.c
 
 clean:
-       rm -f *~ dspam-spamd
\ No newline at end of file
+       rm -f *~ crm114-spamd
diff --git a/README b/README
index 4b68bd4e6095cc211d89d998ddc52094b89d0f1e..add0422abcc4a9589dbae50551022ef9eb18bac0 100644 (file)
--- a/README
+++ b/README
@@ -5,22 +5,42 @@ specified on http://spamassassin.apache.org/full/3.0.x/dist/spamd/PROTOCOL.
 Currently, it only handles the REPORT query, but adding support for others
 should be straight-forward.
 
-It will
- - report 0 score if dspam determines the message to not be spam,
-   and report that it is not spam
- - report 10*probability if dspam determines the message to be spam,
-   and report that it is spam
- - always set the points required to 0
+It was only written to integrate crm114 into exim and hence has a very
+small feature set and doesn't exactly implement the spamd protocol
+perfectly, relies on exact upper/lowercasing of input etc. Hence, it is
+most likely not suited for use in places other than exim calling it.
+
+Here's an example use in an exim acl:
+
+/----------
+| warn
+|   set acl_m_spam_score_int = 0
+|
+| warn
+|   # condition = size restriction!
+|   spam = ${local_part}/defer_ok
+|   set acl_m_spam_score_int = $spam_score_int
+|
+| deny
+|   condition = ${if >{$acl_m_spam_score_int}{300}}
+|   message = We determined that your message is spam.
+\-----
+
+It will currently
+ - if two command line arguments are given, chdir() to the second one
  - setuid() to the user given in the User: header line in the protocol
- - set the dspam mode to the mode given as in the User: header line
-   (for these two, the User: header line looks like this:
-     User: username mode
-   where the mode is optional)
+ - call the binary specified by the first command line argument with the
+   '--report_only' option
+ - report "-score" (from the crm114 status) and SPAM/GOOD/UNSURE depending
+   on what mailreaver thinks of the message.
+ - output "spam threshold" zero (regardless of the crm114 settings)
 
 Due to the way the points are calculated, you probably never want to use the
 spam/not-spam verdict of this tool to make an automatic decision but rather
-go from the points.
+go from the points as in the example above.
 
-It only handles messages smaller than 5mb!
+NOTE: crm114 is only capable of handling messages smaller than around 8 MB
+      unless you use a wrapper that passes the -w flag, hence make sure that
+      you don't pass larger messages.
 
-Please also see http://johannes.sipsolutions.net/Projects/dspam-spamd
+Please also see http://johannes.sipsolutions.net/Projects/crm114-spamd
diff --git a/crm114-spamd.c b/crm114-spamd.c
new file mode 100644 (file)
index 0000000..6237b9f
--- /dev/null
@@ -0,0 +1,181 @@
+#include <sys/types.h>
+#include <pwd.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sysexits.h>
+#include <sys/wait.h>
+#include <ident.h>
+#include <sys/socket.h>
+#include <errno.h>
+
+static void ERROR(int code, char *descr)
+{
+       printf("SPAMD/1.1 %d %s\r\n", code, descr);
+       fflush(stdout);
+       exit(0);
+}
+
+static int read_stdin_line(char *buf, int bufsize)
+{
+       int i = 0;
+       unsigned char c;
+
+       while (1) {
+               if (read(0, &c, 1) != 1)
+                       ERROR(EX_PROTOCOL, "unexpected end of input");
+               if (i >= bufsize - 1)
+                       ERROR(EX_PROTOCOL, "line too long");
+               buf[i] = c;
+               i++;
+               if (c == '\n') {
+                       buf[i-1] = '\0';
+                       break;
+               }
+       }
+
+       return i;
+}
+
+int main(int argc, char **argv)
+{
+       struct sockaddr_in addr;
+       socklen_t addrlen = sizeof(addr);
+       struct passwd *ps;
+       char *id;
+       char buf[20000];
+       char cmd[50];
+       char version[50];
+       char user[50];
+       int length, res;
+       int crm_out[2];
+       pid_t pid;
+       char *statusline, status[100];
+       float score;
+
+       if (argc < 2)
+               ERROR(EX_PROTOCOL, "need mailreaver binary");
+       if (argc > 2)
+               chdir(argv[2]);
+
+       if (getpeername(0, (struct sockaddr *)&addr, &addrlen)) {
+               if (errno == ENOTSOCK) {
+                       /* most likely started from terminal */
+                       ps = getpwuid(getuid());
+                       if (ps) {
+                               id = ps->pw_name;
+                               goto cont_after_permcheck;
+                       }
+                       ERROR(EX_NOPERM, "couldn't look up your username");
+               }
+               ERROR(EX_NOPERM, "couldn't look up your host address");
+       }
+       if (addr.sin_family != AF_INET
+           || addr.sin_addr.s_addr != htonl(0x7f000001))
+               ERROR(EX_NOPERM, "not connected from localhost");
+       id = ident_id(0, 30);
+       if (!id)
+               ERROR(EX_NOPERM, "permission denied, run an ident server");
+
+ cont_after_permcheck:
+       signal(SIGPIPE, SIG_IGN);
+       /* read command */
+       read_stdin_line(buf, sizeof(buf));
+
+       if (sscanf(buf, "%s SPAMC/%s", cmd, version) != 2)
+               ERROR(EX_PROTOCOL, "invalid input line (cmd)");
+       if (strcmp(cmd, "REPORT"))
+               ERROR(EX_PROTOCOL, "can only handle REPORT query");
+       if (strcmp(version, "1.2") != 0)
+               ERROR(EX_PROTOCOL, "can only handle version 1.2");
+
+       /* read user line */
+       read_stdin_line(buf, sizeof(buf));
+       if (sscanf(buf, "User: %s", user) < 1)
+               ERROR(EX_PROTOCOL, "invalid input line (user)");
+
+       /* allow root and Debian-exim to check for anyone */
+       if (strcmp(id, user) &&
+           strcmp(id, "root") && strcmp(id, "Debian-exim"))
+               ERROR(EX_NOPERM, "you can only check spam for yourself"
+                     "(unless priviledged)");
+
+       ps = getpwnam(user);
+       if (!ps)
+               ERROR(EX_TEMPFAIL, "user not found");
+       if (setuid(ps->pw_uid))
+               ERROR(EX_TEMPFAIL, "cannot setuid");
+
+       /* read content-length line */
+       read_stdin_line(buf, sizeof(buf));
+       if (sscanf(buf, "Content-length: %d", &length) != 1)
+               ERROR(EX_PROTOCOL, "invalid input line (length)");
+
+       /* now an empty line */
+       read_stdin_line(buf, sizeof(buf));
+       if (strlen(buf) == 1 && buf[0] == '\r')
+               buf[0] = '\0';
+       if (strlen(buf))
+               ERROR(EX_PROTOCOL, "expected empty line");
+
+       if (pipe(crm_out))
+               ERROR(EX_TEMPFAIL, "failed to create pipe");
+
+       pid = fork();
+       if (pid < 0)
+               ERROR(EX_TEMPFAIL, "failed to fork");
+
+       if (pid == 0) {
+               /* in child */
+               close(1);
+               close(2);
+               close(crm_out[0]);
+               dup2(crm_out[1], 1);
+               dup2(crm_out[1], 2);
+               close(crm_out[1]);
+
+               execl(argv[1], argv[1], "--report_only", NULL);
+               return 1;
+       }
+
+       /* parent */
+
+       /* close stdin, rest of data goes to crm114 now */
+       close(0);
+       /* close child end of pipe */
+       close(crm_out[1]);
+
+       length = 0;
+       res = 1;
+       while (res > 0) {
+               res = read(crm_out[0], buf + length, sizeof(buf) - length);
+               if (res > 0)
+                       length += res;
+               if (res < 0 && errno != EINTR)
+                       ERROR(EX_TEMPFAIL, "failed to read data from crm");
+       }
+       buf[length] = '\0';
+
+       if (waitpid(pid, &res, 0) != pid)
+               ERROR(EX_TEMPFAIL, "failed to wait for crm114");
+
+       if (!WIFEXITED(res) || WEXITSTATUS(res))
+               ERROR(EX_TEMPFAIL, "crm114 failed");
+
+       statusline = strstr(buf, "X-CRM114-Status: ");
+       if (!statusline)
+               ERROR(EX_TEMPFAIL, "crm114 didn't give status");
+
+       sscanf(statusline, "X-CRM114-Status: %s ( %f )", status, &score);
+
+       printf("SPAMD/1.2 0 EX_OK\r\n");
+       printf("Spam: %s ; %.2f / %.2f\r\n", status, -score, 0.0);
+       printf("\r\n");
+
+       printf("%s", buf);
+
+       /* always return ok status to (x)inetd */
+       return 0;
+}
diff --git a/dspam-spamd.c b/dspam-spamd.c
deleted file mode 100644 (file)
index 7911cbc..0000000
+++ /dev/null
@@ -1,263 +0,0 @@
-#include <sys/types.h>
-#include <pwd.h>
-#include <string.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <signal.h>
-#include <sysexits.h>
-#include <sys/wait.h>
-#include <ident.h>
-#include <sys/socket.h>
-#include <errno.h>
-
-#define ERROR(code,descr) do {                                         \
-               printf("SPAMD/1.1 %d %s\r\n", code, descr);             \
-               exit(0);                                                \
-       } while (0)
-
-#define MAXSIZE        (5*1024*1024)
-
-/* we have no arguments ... */
-int main()
-{
-       char buf[200];
-       char cmd[50];
-       char version[50];
-       char user[50], userarg[60], mode[50], modearg[60];
-       char *message;
-       int length;
-       int fork_result;
-       int dspam_in[2], dspam_out[2], dspam_err[2];
-       int status;
-       char *lflf, *crlfcrlf;
-       struct sockaddr_in addr;
-       socklen_t addrlen = sizeof(addr);
-       char *id;
-
-       if (getpeername(0, (struct sockaddr *)&addr, &addrlen)) {
-               if (errno == ENOTSOCK) {
-                       struct passwd *p = getpwuid(getuid());
-                       if (p) {
-                               id = p->pw_name;
-                               goto cont_after_permcheck;
-                       }
-                       ERROR(EX_NOPERM, "couldn't look up your username");
-               }
-               ERROR(EX_NOPERM, "couldn't look up your host address");
-       }
-       if (addr.sin_family != AF_INET
-           || addr.sin_addr.s_addr != htonl(0x7f000001))
-               ERROR(EX_NOPERM, "not connected from localhost");
-       id = ident_id(0, 30);
-       if (!id)
-               ERROR(EX_NOPERM, "permission denied, run an ident server");
-
- cont_after_permcheck:
-       signal(SIGPIPE, SIG_IGN);
-       /* read command */
-       fgets(buf, sizeof(buf), stdin);
-       buf[sizeof(buf) - 1] = '\0';
-       if (strlen(buf) > 50)
-               ERROR(EX_PROTOCOL, "line too long");
-
-       if (sscanf(buf, "%s SPAMC/%s", cmd, version) != 2) {
-               ERROR(EX_PROTOCOL, "invalid input line (cmd)");
-       }
-       if (strcmp(cmd, "REPORT") && strcmp(cmd, "PROCESS"))
-               ERROR(EX_PROTOCOL, "can only handle REPORT and PROCESS");
-       if (strcmp(version, "1.2") != 0)
-               ERROR(EX_PROTOCOL, "can only handle version 1.2");
-
-       /* read user line */
-       fgets(buf, sizeof(buf), stdin);
-       buf[sizeof(buf) - 1] = '\0';
-       if (strlen(buf) > 50)
-               ERROR(EX_PROTOCOL, "line too long");
-
-       mode[0] = '\0';
-       if (sscanf(buf, "User: %s %s", user, mode) < 1)
-               ERROR(EX_PROTOCOL, "invalid input line (user)");
-
-       if (strcmp(id, user) && strcmp(id, "root") && strcmp(id, "Debian-exim"))
-               ERROR(EX_NOPERM,
-                     "you can only check spam for yourself unless priviledged");
-
-       struct passwd *ps;
-       ps = getpwnam(user);
-       if (!ps)
-               ERROR(EX_TEMPFAIL, "user not found");
-       if (setuid(ps->pw_uid))
-               ERROR(EX_TEMPFAIL, "cannot setuid");
-
-       /* read content-length line */
-       fgets(buf, sizeof(buf), stdin);
-       buf[sizeof(buf) - 1] = '\0';
-       if (strlen(buf) > 50)
-               ERROR(EX_PROTOCOL, "line too long");
-
-       if (sscanf(buf, "Content-length: %d", &length) != 1)
-               ERROR(EX_PROTOCOL, "invalid input line (length)");
-
-       /* now an empty line */
-       fgets(buf, sizeof(buf), stdin);
-       buf[sizeof(buf) - 1] = '\0';
-       if (buf[1] == '\n') {
-               buf[1] = '\0';
-               if (buf[0] == '\r')
-                       buf[0] = '\0';
-       }
-       if (strlen(buf) > 2)
-               ERROR(EX_PROTOCOL, "expected empty line");
-
-       /* now read the message */
-       if (length > MAXSIZE)
-               ERROR(EX_IOERR, "message too big");
-       if (length < 0)
-               length = MAXSIZE;
-
-       message = malloc(length + 1);
-       length = fread(message, 1, length, stdin);
-       fclose(stdin);
-
-       if (pipe(dspam_in) || pipe(dspam_out) || pipe(dspam_err))
-               ERROR(EX_TEMPFAIL, "failed to create pipes");
-
-       fork_result = fork();
-       if (fork_result < 0)
-               ERROR(EX_TEMPFAIL, "failed to fork");
-
-       if (fork_result == 0) {
-               close(dspam_in[1]);
-               close(dspam_out[0]);
-               close(dspam_err[0]);
-               dup2(dspam_in[0], 0);
-               dup2(dspam_out[1], 1);
-               dup2(dspam_err[1], 2);
-               sprintf(userarg, "--user=%s", user);
-               sprintf(modearg, "--mode=%s", mode[0] ? mode : "notrain");
-               execl("/usr/bin/dspam", "/usr/bin/dspam", modearg,
-                     "--deliver=innocent,spam", "--stdout", userarg, NULL);
-               return 1;
-       }
-
-       FILE *fdspam_in, *fdspam_out, *fdspam_err;
-       /* assume that at most 1024 bytes are added */
-       char *processed = malloc(length + 1024 + 2);
-       char dspamresult[1024], *dspamres = dspamresult;
-       char *tmp = processed + 1, *tmp2;
-       int readlen = 0;
-
-       /* dirty trick */
-       *processed = '\n';
-
-       close(dspam_in[0]);
-       close(dspam_out[1]);
-       close(dspam_err[1]);
-
-       fdspam_in = fdopen(dspam_in[1], "w");
-       fdspam_out = fdopen(dspam_out[0], "r");
-       fdspam_err = fdopen(dspam_err[0], "r");
-
-       fwrite(message, length, 1, fdspam_in);
-       fclose(fdspam_in);
-
-       while (!feof(fdspam_out)) {
-               int r;
-               r = fread(tmp, 1, length + 1024 - readlen, fdspam_out);
-               if (r == 0)
-                       break;
-               readlen += r;
-               tmp += r;
-               if (readlen == length + 1024) {
-                       /* too long! */
-                       fclose(fdspam_out);
-                       /* leave zombie around */
-                       ERROR(EX_TEMPFAIL, "dspam inflated message too much");
-               }
-       }
-
-       /* rely on the fact that dspam doesn't dump a lot of data on stderr */
-       if ((length = fread(buf, 1, sizeof(buf), fdspam_err)))
-               ERROR(EX_TEMPFAIL, "dspam printed something to stderr");
-
-       status = 0;
-       waitpid(fork_result, &status, 0);
-       if (!(WIFEXITED(status)) || (WEXITSTATUS(status) != 0))
-               ERROR(EX_TEMPFAIL, "dspam exited with non-zero code");
-
-       /* Now we have the message as processed by dspam.
-        * The only valid command right now is REPORT so we
-        * extract the lines dspam added to the header first. */
-
-       /* null terminate message. Yes, due to trick +1 is correct! */
-       processed[readlen + 1] = '\0';
-
-       if (strcmp(cmd, "REPORT") == 0) {
-               lflf = strstr(processed, "\n\n");
-               crlfcrlf = strstr(processed, "\r\n\r\n");
-               if (lflf)
-                       *lflf = '\0';
-               if (crlfcrlf)
-                       *crlfcrlf = '\0';
-               if (!lflf && !crlfcrlf)
-                       ERROR(EX_TEMPFAIL,
-                             "dspam failed to return a proper message");
-
-               /* second part of dirty trick */
-               tmp2 = processed;
-
-               while (tmp2 && (tmp = strstr(tmp2, "\nX-DSPAM"))) {
-                       tmp++;
-                       tmp2 = strchr(tmp, '\n');
-                       if (tmp2)
-                               *tmp2 = '\0';
-                       strcpy(dspamres, tmp);
-                       dspamres += strlen(tmp);
-                       *dspamres = '\n';
-                       dspamres++;
-                       if (tmp2)
-                               *tmp2 = '\n';
-               }
-               *dspamres = '\0';
-
-               /* now we have everything dspam gave us */
-               char *res = strstr(dspamresult, "X-DSPAM-Result: ");
-               if (!res)
-                       ERROR(EX_TEMPFAIL, "no dspam result!");
-
-               char spamtag[20];
-               strncpy(spamtag, res + strlen("X-DSPAM-Result: "),
-                       sizeof(spamtag));
-               int i;
-               for (i = 0; i < 20; i++)
-                       if (spamtag[i] == '\n')
-                               spamtag[i] = '\0';
-               spamtag[19] = '\0';
-
-               char *conf = strstr(dspamresult, "X-DSPAM-Confidence: ");
-               if (!conf)
-                       ERROR(EX_TEMPFAIL, "no dspam confidence!");
-               float confidence;
-               if (sscanf(conf, "X-DSPAM-Confidence: %f", &confidence) != 1)
-                       ERROR(EX_TEMPFAIL, "could not parse dspam confidence");
-               confidence *= 10;
-
-               int isspam = strcasecmp(spamtag, "Spam") == 0;
-               if (!isspam)
-                       confidence = 0;
-               printf("SPAMD/1.1 0 EX_OK\r\n");
-               printf("Spam: %s", isspam ? "True" : "False");
-               printf(" ; %f / 0\r\n\r\n", confidence);
-
-               printf("%s", dspamresult);
-       } else {
-               /* PROCESS */
-               printf("SPAMD/1.1 0 EX_OK\r\n");
-               printf("Content-length: %d\r\n", readlen);
-               printf("\r\n");
-               fwrite(processed + 1, 1, readlen, stdout);
-       }
-       /* always return ok status to (x)inetd */
-       return 0;
-}