diff options
Diffstat (limited to 'src/cmd/upas/filterkit')
-rw-r--r-- | src/cmd/upas/filterkit/dat.h | 8 | ||||
-rw-r--r-- | src/cmd/upas/filterkit/deliver.c | 60 | ||||
-rw-r--r-- | src/cmd/upas/filterkit/list.c | 315 | ||||
-rw-r--r-- | src/cmd/upas/filterkit/mkfile | 21 | ||||
-rwxr-xr-x | src/cmd/upas/filterkit/pipefrom.sample | 24 | ||||
-rw-r--r-- | src/cmd/upas/filterkit/pipeto.sample | 73 | ||||
-rw-r--r-- | src/cmd/upas/filterkit/pipeto.sample-hold | 43 | ||||
-rw-r--r-- | src/cmd/upas/filterkit/readaddrs.c | 98 | ||||
-rw-r--r-- | src/cmd/upas/filterkit/token.c | 89 |
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); +} |