aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/upas/filterkit
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/upas/filterkit')
-rw-r--r--src/cmd/upas/filterkit/dat.h8
-rw-r--r--src/cmd/upas/filterkit/deliver.c60
-rw-r--r--src/cmd/upas/filterkit/list.c315
-rw-r--r--src/cmd/upas/filterkit/mkfile21
-rwxr-xr-xsrc/cmd/upas/filterkit/pipefrom.sample24
-rw-r--r--src/cmd/upas/filterkit/pipeto.sample73
-rw-r--r--src/cmd/upas/filterkit/pipeto.sample-hold43
-rw-r--r--src/cmd/upas/filterkit/readaddrs.c98
-rw-r--r--src/cmd/upas/filterkit/token.c89
9 files changed, 731 insertions, 0 deletions
diff --git a/src/cmd/upas/filterkit/dat.h b/src/cmd/upas/filterkit/dat.h
new file mode 100644
index 00000000..dd4572db
--- /dev/null
+++ b/src/cmd/upas/filterkit/dat.h
@@ -0,0 +1,8 @@
+typedef struct Addr Addr;
+struct Addr
+{
+ Addr *next;
+ char *val;
+};
+
+extern Addr* readaddrs(char*, Addr*);
diff --git a/src/cmd/upas/filterkit/deliver.c b/src/cmd/upas/filterkit/deliver.c
new file mode 100644
index 00000000..33903708
--- /dev/null
+++ b/src/cmd/upas/filterkit/deliver.c
@@ -0,0 +1,60 @@
+#include "dat.h"
+#include "common.h"
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s recipient fromaddr-file mbox\n", argv0);
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ int fd;
+ char now[30];
+ Addr *a;
+ char *deliveredto;
+ Mlock *l;
+ int bytes;
+
+ ARGBEGIN{
+ }ARGEND;
+
+ if(argc != 3)
+ usage();
+
+ deliveredto = strrchr(argv[0], '!');
+ if(deliveredto == nil)
+ deliveredto = argv[0];
+ else
+ deliveredto++;
+ a = readaddrs(argv[1], nil);
+ if(a == nil)
+ sysfatal("missing from address");
+
+ l = syslock(argv[2]);
+
+ /* append to mbox */
+ fd = open(argv[2], OWRITE);
+ if(fd < 0)
+ sysfatal("opening mailbox: %r");
+ seek(fd, 0, 2);
+ strncpy(now, ctime(time(0)), sizeof(now));
+ now[28] = 0;
+ if(fprint(fd, "From %s %s\n", a->val, now) < 0)
+ sysfatal("writing mailbox: %r");
+
+ /* copy message handles escapes and any needed new lines */
+ bytes = appendfiletombox(0, fd);
+ if(bytes < 0)
+ sysfatal("writing mailbox: %r");
+
+ close(fd);
+ sysunlock(l);
+
+ /* log it */
+ syslog(0, "mail", "delivered %s From %s %s (%s) %d", deliveredto,
+ a->val, now, argv[0], bytes);
+ exits(0);
+}
diff --git a/src/cmd/upas/filterkit/list.c b/src/cmd/upas/filterkit/list.c
new file mode 100644
index 00000000..6c23dcc1
--- /dev/null
+++ b/src/cmd/upas/filterkit/list.c
@@ -0,0 +1,315 @@
+#include <u.h>
+#include <libc.h>
+#include <regexp.h>
+#include <libsec.h>
+#include <String.h>
+#include <bio.h>
+#include "dat.h"
+
+int debug;
+
+enum
+{
+ Tregexp= (1<<0), /* ~ */
+ Texact= (1<<1), /* = */
+};
+
+typedef struct Pattern Pattern;
+struct Pattern
+{
+ Pattern *next;
+ int type;
+ char *arg;
+ int bang;
+};
+
+String *patternpath;
+Pattern *patterns;
+String *mbox;
+
+static void
+usage(void)
+{
+ fprint(2, "usage: %s 'check|add' patternfile addr [addr*]\n", argv0);
+ exits("usage");
+}
+
+/*
+ * convert string to lower case
+ */
+static void
+mklower(char *p)
+{
+ int c;
+
+ for(; *p; p++){
+ c = *p;
+ if(c <= 'Z' && c >= 'A')
+ *p = c - 'A' + 'a';
+ }
+}
+
+/*
+ * simplify an address, reduce to a domain
+ */
+static String*
+simplify(char *addr)
+{
+ int dots;
+ char *p, *at;
+ String *s;
+
+ mklower(addr);
+ at = strchr(addr, '@');
+ if(at == nil){
+ /* local address, make it an exact match */
+ s = s_copy("=");
+ s_append(s, addr);
+ return s;
+ }
+
+ /* copy up to the '@' sign */
+ at++;
+ s = s_copy("~");
+ for(p = addr; p < at; p++){
+ if(strchr(".*+?(|)\\[]^$", *p))
+ s_putc(s, '\\');
+ s_putc(s, *p);
+ }
+
+ /* just any address matching the two most significant domain elements */
+ s_append(s, "(.*\\.)?");
+ p = addr+strlen(addr);
+ dots = 0;
+ for(; p > at; p--){
+ if(*p != '.')
+ continue;
+ if(dots++ > 0){
+ p++;
+ break;
+ }
+ }
+ for(; *p; p++){
+ if(strchr(".*+?(|)\\[]^$", *p) != 0)
+ s_putc(s, '\\');
+ s_putc(s, *p);
+ }
+ s_terminate(s);
+
+ return s;
+}
+
+/*
+ * link patterns in order
+ */
+static int
+newpattern(int type, char *arg, int bang)
+{
+ Pattern *p;
+ static Pattern *last;
+
+ mklower(arg);
+
+ p = mallocz(sizeof *p, 1);
+ if(p == nil)
+ return -1;
+ if(type == Tregexp){
+ p->arg = malloc(strlen(arg)+3);
+ if(p->arg == nil){
+ free(p);
+ return -1;
+ }
+ p->arg[0] = 0;
+ strcat(p->arg, "^");
+ strcat(p->arg, arg);
+ strcat(p->arg, "$");
+ } else {
+ p->arg = strdup(arg);
+ if(p->arg == nil){
+ free(p);
+ return -1;
+ }
+ }
+ p->type = type;
+ p->bang = bang;
+ if(last == nil)
+ patterns = p;
+ else
+ last->next = p;
+ last = p;
+
+ return 0;
+}
+
+/*
+ * patterns are either
+ * ~ regular expression
+ * = exact match string
+ *
+ * all comparisons are case insensitive
+ */
+static int
+readpatterns(char *path)
+{
+ Biobuf *b;
+ char *p;
+ char *token[2];
+ int n;
+ int bang;
+
+ b = Bopen(path, OREAD);
+ if(b == nil)
+ return -1;
+ while((p = Brdline(b, '\n')) != nil){
+ p[Blinelen(b)-1] = 0;
+ n = tokenize(p, token, 2);
+ if(n == 0)
+ continue;
+
+ mklower(token[0]);
+ p = token[0];
+ if(*p == '!'){
+ p++;
+ bang = 1;
+ } else
+ bang = 0;
+
+ if(*p == '='){
+ if(newpattern(Texact, p+1, bang) < 0)
+ return -1;
+ } else if(*p == '~'){
+ if(newpattern(Tregexp, p+1, bang) < 0)
+ return -1;
+ } else if(strcmp(token[0], "#include") == 0 && n == 2)
+ readpatterns(token[1]);
+ }
+ Bterm(b);
+ return 0;
+}
+
+/* fuck, shit, bugger, damn */
+void regerror(char*)
+{
+}
+
+/*
+ * check lower case version of address agains patterns
+ */
+static Pattern*
+checkaddr(char *arg)
+{
+ Pattern *p;
+ Reprog *rp;
+ String *s;
+
+ s = s_copy(arg);
+ mklower(s_to_c(s));
+
+ for(p = patterns; p != nil; p = p->next)
+ switch(p->type){
+ case Texact:
+ if(strcmp(p->arg, s_to_c(s)) == 0){
+ free(s);
+ return p;
+ }
+ break;
+ case Tregexp:
+ rp = regcomp(p->arg);
+ if(rp == nil)
+ continue;
+ if(regexec(rp, s_to_c(s), nil, 0)){
+ free(rp);
+ free(s);
+ return p;
+ }
+ free(rp);
+ break;
+ }
+ s_free(s);
+ return 0;
+}
+static char*
+check(int argc, char **argv)
+{
+ int i;
+ Addr *a;
+ Pattern *p;
+ int matchedbang;
+
+ matchedbang = 0;
+ for(i = 0; i < argc; i++){
+ a = readaddrs(argv[i], nil);
+ for(; a != nil; a = a->next){
+ p = checkaddr(a->val);
+ if(p == nil)
+ continue;
+ if(p->bang)
+ matchedbang = 1;
+ else
+ return nil;
+ }
+ }
+ if(matchedbang)
+ return "!match";
+ else
+ return "no match";
+}
+
+/*
+ * add anything that isn't already matched, all matches are lower case
+ */
+static char*
+add(char *pp, int argc, char **argv)
+{
+ int fd, i;
+ String *s;
+ char *cp;
+ Addr *a;
+
+ a = nil;
+ for(i = 0; i < argc; i++)
+ a = readaddrs(argv[i], a);
+
+ fd = open(pp, OWRITE);
+ seek(fd, 0, 2);
+ for(; a != nil; a = a->next){
+ if(checkaddr(a->val))
+ continue;
+ s = simplify(a->val);
+ cp = s_to_c(s);
+ fprint(fd, "%q\t%q\n", cp, a->val);
+ if(*cp == '=')
+ newpattern(Texact, cp+1, 0);
+ else if(*cp == '~')
+ newpattern(Tregexp, cp+1, 0);
+ s_free(s);
+ }
+ close(fd);
+ return nil;
+}
+
+void
+main(int argc, char **argv)
+{
+ char *patternpath;
+
+ ARGBEGIN {
+ case 'd':
+ debug++;
+ break;
+ } ARGEND;
+
+ quotefmtinstall();
+
+ if(argc < 3)
+ usage();
+
+ patternpath = argv[1];
+ readpatterns(patternpath);
+ if(strcmp(argv[0], "add") == 0)
+ exits(add(patternpath, argc-2, argv+2));
+ else if(strcmp(argv[0], "check") == 0)
+ exits(check(argc-2, argv+2));
+ else
+ usage();
+}
diff --git a/src/cmd/upas/filterkit/mkfile b/src/cmd/upas/filterkit/mkfile
new file mode 100644
index 00000000..c077d564
--- /dev/null
+++ b/src/cmd/upas/filterkit/mkfile
@@ -0,0 +1,21 @@
+</$objtype/mkfile
+
+TARG=\
+ token\
+ list\
+ deliver\
+
+LIB=../common/libcommon.a$O\
+
+BIN=/$objtype/bin/upas
+OFILES=readaddrs.$O
+UPDATE=\
+ mkfile\
+ ${TARG:%=%.c}\
+ pipeto.sample\
+ pipefrom.sample\
+ pipeto.sample-hold\
+
+</sys/src/cmd/mkmany
+CFLAGS=$CFLAGS -I../common
+
diff --git a/src/cmd/upas/filterkit/pipefrom.sample b/src/cmd/upas/filterkit/pipefrom.sample
new file mode 100755
index 00000000..616bc683
--- /dev/null
+++ b/src/cmd/upas/filterkit/pipefrom.sample
@@ -0,0 +1,24 @@
+#!/bin/rc
+
+rfork e
+TMP=/tmp/myupassend.$pid
+
+# collect upas/send options
+options=()
+while (! ~ $#* 0 && ~ $1 -*) {
+ options=($options $1);
+ shift
+}
+
+# collect addresses and add them to my patterns
+dests=()
+while (! ~ $#* 0) {
+ dests=($dests $1);
+ shift
+}
+echo $dests > $TMP
+upas/list add /mail/box/$user/_pattern $TMP >[2] /dev/null
+rm $TMP
+
+# send mail
+upas/send $options $dests
diff --git a/src/cmd/upas/filterkit/pipeto.sample b/src/cmd/upas/filterkit/pipeto.sample
new file mode 100644
index 00000000..82ef9d1f
--- /dev/null
+++ b/src/cmd/upas/filterkit/pipeto.sample
@@ -0,0 +1,73 @@
+#!/bin/rc
+
+# create a /tmp for here documents
+rfork en
+bind -c /mail/tmp /tmp
+
+KEY=whocares
+USER=ken
+
+RECIP=$1
+MBOX=$2
+PF=/mail/box/$USER/_pattern
+TMP=/mail/tmp/mine.$pid
+BIN=/bin/upas
+D=/mail/fs/mbox/1
+
+# save and parse the mail file
+{sed '/^$/,$ s/^From / From /'; echo} > $TMP
+upas/fs -f $TMP
+
+# if we like the source
+# or if the subject contains a valid token
+# then deliver the mail and allow all the addresses
+if( $BIN/list check $PF $D/from $D/sender $D/replyto )
+{
+ $BIN/deliver $RECIP $D/from $MBOX < $D/raw
+ $BIN/list add $PF $D/from $D/to $D/cc $D/sender
+ rm $TMP
+ exit 0
+}
+switch($status){
+case *!match*
+ echo `{date} dropped $RECIP From `{cat $D/replyto} >> /mail/box/$USER/_bounced >[2] /dev/null
+ rm $TMP
+ exit 0
+}
+if ( $BIN/token $KEY $D/subject )
+{
+ $BIN/deliver $RECIP $D/from $MBOX < $D/raw
+ $BIN/list add $PF $D/from $D/to $D/cc $D/sender
+ rm $TMP
+ echo `{date} added $RECIP From `{cat $D/replyto} \
+ >> /mail/box/$USER/_bounced >[2] /dev/null
+ exit 0
+}
+
+# don't recognize the sender so
+# return the message with instructions
+TOKEN=`{upas/token $KEY}
+upasname=/dev/null
+{{cat; cat $D/raw} | upas/send `{cat $D/replyto}}<<EOF
+Subject: $USER's mail filter
+I've been getting so much junk mail that I'm resorting to
+a draconian mechanism to avoid the mail. In order
+to make sure that there's a real person sending mail, I'm
+asking you to explicitly enable access. To do that, send
+mail to $USER at this domain with the token:
+ $TOKEN
+in the subject of your mail message. After that, you
+shouldn't get any bounces from me. Sorry if this is
+an inconvenience.
+
+----------------
+Original message
+----------------
+EOF
+
+echo `{date} bounced $RECIP From `{cat $D/replyto} \
+ >> /mail/box/$USER/_bounced >[2] /dev/null
+
+rv=$status
+rm $TMP
+exit $status
diff --git a/src/cmd/upas/filterkit/pipeto.sample-hold b/src/cmd/upas/filterkit/pipeto.sample-hold
new file mode 100644
index 00000000..5794f732
--- /dev/null
+++ b/src/cmd/upas/filterkit/pipeto.sample-hold
@@ -0,0 +1,43 @@
+#!/bin/rc
+
+# create a /tmp for here documents
+rfork en
+bind -c /mail/tmp /tmp
+
+KEY=whocares
+USER=ken
+
+RECIP=$1
+MBOX=$2
+PF=/mail/box/$USER/_pattern
+TMP=/mail/tmp/mine.$pid
+BIN=/bin/upas
+D=/mail/fs/mbox/1
+
+# save and parse the mail file
+{sed '/^$/,$ s/^From / From /'; echo} > $TMP
+upas/fs -f $TMP
+
+# if we like the source
+# or if the subject contains a valid token
+# then deliver the mail and allow all the addresses
+if( $BIN/list check $PF $D/from $D/sender $D/replyto )
+{
+ $BIN/deliver $RECIP $D/from $MBOX < $D/raw
+ $BIN/list add $PF $D/from $D/to $D/cc $D/sender
+ rm $TMP
+ exit 0
+}
+switch($status){
+case *!match*
+ echo `{date} dropped $RECIP From `{cat $D/replyto} >> /mail/box/$USER/_bounced >[2] /dev/null
+ rm $TMP
+ exit 0
+}
+
+# don't recognize the sender so hold the message
+$BIN/deliver $RECIP $D/from /mail/box/$USER/_held < $D/raw
+
+rv=$status
+rm $TMP
+exit $status
diff --git a/src/cmd/upas/filterkit/readaddrs.c b/src/cmd/upas/filterkit/readaddrs.c
new file mode 100644
index 00000000..9aadc1ab
--- /dev/null
+++ b/src/cmd/upas/filterkit/readaddrs.c
@@ -0,0 +1,98 @@
+#include <u.h>
+#include <libc.h>
+#include "dat.h"
+
+void*
+emalloc(int size)
+{
+ void *a;
+
+ a = mallocz(size, 1);
+ if(a == nil)
+ sysfatal("%r");
+ return a;
+}
+
+char*
+estrdup(char *s)
+{
+ s = strdup(s);
+ if(s == nil)
+ sysfatal("%r");
+ return s;
+}
+
+/*
+ * like tokenize but obey "" quoting
+ */
+int
+tokenize822(char *str, char **args, int max)
+{
+ int na;
+ int intok = 0, inquote = 0;
+
+ if(max <= 0)
+ return 0;
+ for(na=0; ;str++)
+ switch(*str) {
+ case ' ':
+ case '\t':
+ if(inquote)
+ goto Default;
+ /* fall through */
+ case '\n':
+ *str = 0;
+ if(!intok)
+ continue;
+ intok = 0;
+ if(na < max)
+ continue;
+ /* fall through */
+ case 0:
+ return na;
+ case '"':
+ inquote ^= 1;
+ /* fall through */
+ Default:
+ default:
+ if(intok)
+ continue;
+ args[na++] = str;
+ intok = 1;
+ }
+ return 0; /* can't get here; silence compiler */
+}
+
+Addr*
+readaddrs(char *file, Addr *a)
+{
+ int fd;
+ int i, n;
+ char buf[8*1024];
+ char *f[128];
+ Addr **l;
+ Addr *first;
+
+ /* add to end */
+ first = a;
+ for(l = &first; *l != nil; l = &(*l)->next)
+ ;
+
+ /* read in the addresses */
+ fd = open(file, OREAD);
+ if(fd < 0)
+ return first;
+ n = read(fd, buf, sizeof(buf)-1);
+ close(fd);
+ if(n <= 0)
+ return first;
+ buf[n] = 0;
+
+ n = tokenize822(buf, f, nelem(f));
+ for(i = 0; i < n; i++){
+ *l = a = emalloc(sizeof *a);
+ l = &a->next;
+ a->val = estrdup(f[i]);
+ }
+ return first;
+}
diff --git a/src/cmd/upas/filterkit/token.c b/src/cmd/upas/filterkit/token.c
new file mode 100644
index 00000000..8fbcda66
--- /dev/null
+++ b/src/cmd/upas/filterkit/token.c
@@ -0,0 +1,89 @@
+#include <u.h>
+#include <libc.h>
+#include <libsec.h>
+#include <String.h>
+#include "dat.h"
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s key [token]\n", argv0);
+ exits("usage");
+}
+
+static String*
+mktoken(char *key, long thetime)
+{
+ char *now;
+ uchar digest[SHA1dlen];
+ char token[64];
+ String *s;
+
+ now = ctime(thetime);
+ memset(now+11, ':', 8);
+ hmac_sha1((uchar*)now, strlen(now), (uchar*)key, strlen(key), digest, nil);
+ enc64(token, sizeof token, digest, sizeof digest);
+ s = s_new();
+ s_nappend(s, token, 5);
+ return s;
+}
+
+static char*
+check_token(char *key, char *file)
+{
+ String *s;
+ long now;
+ int i;
+ char buf[1024];
+ int fd;
+
+ fd = open(file, OREAD);
+ if(fd < 0)
+ return "no match";
+ i = read(fd, buf, sizeof(buf)-1);
+ close(fd);
+ if(i < 0)
+ return "no match";
+ buf[i] = 0;
+
+ now = time(0);
+
+ for(i = 0; i < 14; i++){
+ s = mktoken(key, now-24*60*60*i);
+ if(strstr(buf, s_to_c(s)) != nil){
+ s_free(s);
+ return nil;
+ }
+ s_free(s);
+ }
+ return "no match";
+}
+
+static char*
+create_token(char *key)
+{
+ String *s;
+
+ s = mktoken(key, time(0));
+ print("%s", s_to_c(s));
+ return nil;
+}
+
+void
+main(int argc, char **argv)
+{
+ ARGBEGIN {
+ } ARGEND;
+
+ switch(argc){
+ case 2:
+ exits(check_token(argv[0], argv[1]));
+ break;
+ case 1:
+ exits(create_token(argv[0]));
+ break;
+ default:
+ usage();
+ }
+ exits(0);
+}