Thanks to John Cummings.

This commit is contained in:
rsc
2005-10-29 16:26:44 +00:00
parent cd37451963
commit 5cdb17983a
94 changed files with 26853 additions and 0 deletions

1001
src/cmd/upas/common/libsys.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,57 @@
#include "common.h"
/* format of REMOTE FROM lines */
char *REMFROMRE =
"^>?From[ \t]+((\".*\")?[^\" \t]+?(\".*\")?[^\" \t]+?)[ \t]+(.+)[ \t]+remote[ \t]+from[ \t]+(.*)\n$";
int REMSENDERMATCH = 1;
int REMDATEMATCH = 4;
int REMSYSMATCH = 5;
/* format of LOCAL FROM lines */
char *FROMRE =
"^>?From[ \t]+((\".*\")?[^\" \t]+?(\".*\")?[^\" \t]+?)[ \t]+(.+)\n$";
int SENDERMATCH = 1;
int DATEMATCH = 4;
/* output a unix style local header */
int
print_header(Biobuf *fp, char *sender, char *date)
{
return Bprint(fp, "From %s %s\n", sender, date);
}
/* output a unix style remote header */
int
print_remote_header(Biobuf *fp, char *sender, char *date, char *system)
{
return Bprint(fp, "From %s %s remote from %s\n", sender, date, system);
}
/* parse a mailbox style header */
int
parse_header(char *line, String *sender, String *date)
{
if (!IS_HEADER(line))
return -1;
line += sizeof("From ") - 1;
s_restart(sender);
while(*line==' '||*line=='\t')
line++;
if(*line == '"'){
s_putc(sender, *line++);
while(*line && *line != '"')
s_putc(sender, *line++);
s_putc(sender, *line++);
} else {
while(*line && *line != ' ' && *line != '\t')
s_putc(sender, *line++);
}
s_terminate(sender);
s_restart(date);
while(*line==' '||*line=='\t')
line++;
while(*line)
s_putc(date, *line++);
s_terminate(date);
return 0;
}

View File

@@ -0,0 +1,18 @@
CFLAGS=${UNIX} -g -I. -I../libc -I../common -I/usr/include ${SCFLAGS}
OBJS=mail.o aux.o string.o ${SYSOBJ}
AR=ar
.c.o: ; ${CC} -c ${CFLAGS} $*.c
common.a: ${OBJS}
${AR} cr common.a ${OBJS}
-ranlib common.a
aux.o: aux.h string.h mail.h
string.o: string.h mail.h
mail.o: mail.h
syslog.o: sys.h
mail.h: sys.h
clean:
-rm -f *.[oO] core a.out *.a *.sL common.a

View File

@@ -0,0 +1,20 @@
<$PLAN9/src/mkhdr
LIB=libcommon.a
OFILES=aux.$O\
become.$O\
mail.$O\
process.$O\
libsys.$O\
config.$O\
appendfiletombox.$O\
HFILES=common.h\
sys.h\
<$PLAN9/src/mklib
nuke:V:
mk clean
rm -f libcommon.a

View File

@@ -0,0 +1,175 @@
#include "common.h"
/* make a stream to a child process */
extern stream *
instream(void)
{
stream *rv;
int pfd[2];
if ((rv = (stream *)malloc(sizeof(stream))) == 0)
return 0;
memset(rv, 0, sizeof(stream));
if (pipe(pfd) < 0)
return 0;
if(Binit(&rv->bb, pfd[1], OWRITE) < 0){
close(pfd[0]);
close(pfd[1]);
return 0;
}
rv->fp = &rv->bb;
rv->fd = pfd[0];
return rv;
}
/* make a stream from a child process */
extern stream *
outstream(void)
{
stream *rv;
int pfd[2];
if ((rv = (stream *)malloc(sizeof(stream))) == 0)
return 0;
memset(rv, 0, sizeof(stream));
if (pipe(pfd) < 0)
return 0;
if (Binit(&rv->bb, pfd[0], OREAD) < 0){
close(pfd[0]);
close(pfd[1]);
return 0;
}
rv->fp = &rv->bb;
rv->fd = pfd[1];
return rv;
}
extern void
stream_free(stream *sp)
{
int fd;
close(sp->fd);
fd = Bfildes(sp->fp);
Bterm(sp->fp);
close(fd);
free((char *)sp);
}
/* start a new process */
extern process *
noshell_proc_start(char **av, stream *inp, stream *outp, stream *errp, int newpg, char *who)
{
process *pp;
int i, n;
if ((pp = (process *)malloc(sizeof(process))) == 0) {
if (inp != 0)
stream_free(inp);
if (outp != 0)
stream_free(outp);
if (errp != 0)
stream_free(errp);
return 0;
}
pp->std[0] = inp;
pp->std[1] = outp;
pp->std[2] = errp;
switch (pp->pid = fork()) {
case -1:
proc_free(pp);
return 0;
case 0:
if(newpg)
sysdetach();
for (i=0; i<3; i++)
if (pp->std[i] != 0){
close(Bfildes(pp->std[i]->fp));
while(pp->std[i]->fd < 3)
pp->std[i]->fd = dup(pp->std[i]->fd, -1);
}
for (i=0; i<3; i++)
if (pp->std[i] != 0)
dup(pp->std[i]->fd, i);
for (n = sysfiles(); i < n; i++)
close(i);
if(who) {
fprint(2,"process.c: trying to become(%s,%s)\n",av,who);
// jpc become(av, who);
}
exec(av[0], av);
perror("proc_start");
exits("proc_start");
default:
for (i=0; i<3; i++)
if (pp->std[i] != 0) {
close(pp->std[i]->fd);
pp->std[i]->fd = -1;
}
return pp;
}
}
/* start a new process under a shell */
extern process *
proc_start(char *cmd, stream *inp, stream *outp, stream *errp, int newpg, char *who)
{
char *av[4];
av[0] = unsharp(SHELL);
av[1] = "-c";
av[2] = cmd;
av[3] = 0;
return noshell_proc_start(av, inp, outp, errp, newpg, who);
}
/* wait for a process to stop */
extern int
proc_wait(process *pp)
{
Waitmsg *status;
char err[Errlen];
for(;;){
status = wait();
if(status == nil){
errstr(err, sizeof(err));
if(strstr(err, "interrupt") == 0)
break;
}
if (status->pid==pp->pid)
break;
}
pp->pid = -1;
if(status == nil)
pp->status = -1;
else
pp->status = status->msg[0];
pp->waitmsg = status;
return pp->status;
}
/* free a process */
extern int
proc_free(process *pp)
{
int i;
if(pp->std[1] == pp->std[2])
pp->std[2] = 0; /* avoid freeing it twice */
for (i = 0; i < 3; i++)
if (pp->std[i])
stream_free(pp->std[i]);
if (pp->pid >= 0)
proc_wait(pp);
free(pp->waitmsg);
free((char *)pp);
return 0;
}
/* kill a process */
extern int
proc_kill(process *pp)
{
return syskill(pp->pid);
}

85
src/cmd/upas/common/sys.h Normal file
View File

@@ -0,0 +1,85 @@
/*
* System dependent header files for research
*/
#include <u.h>
#include <libc.h>
#include <regexp.h>
#include <bio.h>
#include "libString.h" /* jpc String.h -> libString.h */
/*
* for the lock routines in libsys.c
*/
typedef struct Mlock Mlock;
struct Mlock {
int fd;
int pid;
String *name;
};
/*
* from config.c
*/
extern char *MAILROOT; /* root of mail system */
extern char *UPASLOG; /* log directory */
extern char *UPASLIB; /* upas library directory */
extern char *UPASBIN; /* upas binary directory */
extern char *UPASTMP; /* temporary directory */
extern char *SHELL; /* path name of shell */
extern char *POST; /* path name of post server addresses */
extern int MBOXMODE; /* default mailbox protection mode */
/*
* files in libsys.c
*/
extern char *sysname_read(void);
extern char *alt_sysname_read(void);
extern char *domainname_read(void);
extern char **sysnames_read(void);
extern char *getlog(void);
extern char *thedate(void);
extern Biobuf *sysopen(char*, char*, ulong);
extern int sysopentty(void);
extern int sysclose(Biobuf*);
extern int sysmkdir(char*, ulong);
extern int syschgrp(char*, char*);
extern Mlock *syslock(char *);
extern void sysunlock(Mlock *);
extern void syslockrefresh(Mlock *);
extern int e_nonexistent(void);
extern int e_locked(void);
extern long sysfilelen(Biobuf*);
extern int sysremove(char*);
extern int sysrename(char*, char*);
extern int sysexist(char*);
extern int sysisdir(char*);
extern int syskill(int);
extern int syskillpg(int);
extern int syscreate(char*, int, ulong);
extern Mlock *trylock(char *);
extern void exit9(int);
extern void pipesig(int*);
extern void pipesigoff(void);
extern int holdon(void);
extern void holdoff(int);
extern int syscreatelocked(char*, int, int);
extern int sysopenlocked(char*, int);
extern int sysunlockfile(int);
extern int sysfiles(void);
extern int become(char**, char*);
extern int sysdetach(void);
extern int sysdirreadall(int, Dir**);
extern String *username(String*);
extern char* remoteaddr(int, char*);
extern int creatembox(char*, char*);
extern String *readlock(String*);
extern char *homedir(char*);
extern String *mboxname(char*, String*);
extern String *deadletter(String*);
/*
* maximum size for a file path
*/
#define MAXPATHLEN 128

View File

@@ -0,0 +1,8 @@
typedef struct Addr Addr;
struct Addr
{
Addr *next;
char *val;
};
extern Addr* readaddrs(char*, Addr*);

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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);
}

221
src/cmd/upas/fs/dat.h Normal file
View File

@@ -0,0 +1,221 @@
typedef struct Message Message;
struct Message
{
int id;
int refs;
int subname;
char name[Elemlen];
// pointers into message
char *start; // start of message
char *end; // end of message
char *header; // start of header
char *hend; // end of header
int hlen; // length of header minus ignored fields
char *mheader; // start of mime header
char *mhend; // end of mime header
char *body; // start of body
char *bend; // end of body
char *rbody; // raw (unprocessed) body
char *rbend; // end of raw (unprocessed) body
char *lim;
char deleted;
char inmbox;
char mallocd; // message is malloc'd
char ballocd; // body is malloc'd
char hallocd; // header is malloce'd
// mail info
String *unixheader;
String *unixfrom;
String *unixdate;
String *from822;
String *sender822;
String *to822;
String *bcc822;
String *cc822;
String *replyto822;
String *date822;
String *inreplyto822;
String *subject822;
String *messageid822;
String *addrs;
String *mimeversion;
String *sdigest;
// mime info
String *boundary;
String *type;
int encoding;
int disposition;
String *charset;
String *filename;
int converted;
int decoded;
char lines[10]; // number of lines in rawbody
Message *next; // same level
Message *part; // down a level
Message *whole; // up a level
uchar digest[SHA1dlen];
vlong imapuid; // used by imap4
char uidl[80]; // used by pop3
int mesgno;
};
enum
{
// encodings
Enone= 0,
Ebase64,
Equoted,
// disposition possibilities
Dnone= 0,
Dinline,
Dfile,
Dignore,
PAD64= '=',
};
typedef struct Mailbox Mailbox;
struct Mailbox
{
QLock ql; /* jpc named Qlock */
int refs;
Mailbox *next;
int id;
int dolock; // lock when syncing?
int std;
char name[Elemlen];
char path[Pathlen];
Dir *d;
Message *root;
int vers; // goes up each time mailbox is read
ulong waketime;
char *(*sync)(Mailbox*, int);
void (*close)(Mailbox*);
char *(*fetch)(Mailbox*, Message*);
char *(*ctl)(Mailbox*, int, char**);
void *aux; // private to Mailbox implementation
};
typedef char *Mailboxinit(Mailbox*, char*);
extern Message *root;
extern Mailboxinit plan9mbox;
extern Mailboxinit pop3mbox;
extern Mailboxinit imap4mbox;
char* syncmbox(Mailbox*, int);
char* geterrstr(void);
void* emalloc(ulong);
void* erealloc(void*, ulong);
Message* newmessage(Message*);
void delmessage(Mailbox*, Message*);
void delmessages(int, char**);
int newid(void);
void mailplumb(Mailbox*, Message*, int);
char* newmbox(char*, char*, int);
void freembox(char*);
void logmsg(char*, Message*);
void msgincref(Message*);
void msgdecref(Mailbox*, Message*);
void mboxincref(Mailbox*);
void mboxdecref(Mailbox*);
void convert(Message*);
void decode(Message*);
int cistrncmp(char*, char*, int);
int cistrcmp(char*, char*);
int latin1toutf(char*, char*, char*);
int windows1257toutf(char*, char*, char*);
int decquoted(char*, char*, char*);
int xtoutf(char*, char**, char*, char*);
void countlines(Message*);
int headerlen(Message*);
void parse(Message*, int, Mailbox*, int);
void parseheaders(Message*, int, Mailbox*, int);
void parsebody(Message*, Mailbox*);
void parseunix(Message*);
String* date822tounix(char*);
int fidmboxrefs(Mailbox*);
int hashmboxrefs(Mailbox*);
void checkmboxrefs(void);
extern int debug;
extern int fflag;
extern int logging;
extern char user[Elemlen];
extern char stdmbox[Pathlen];
extern QLock mbllock;
extern Mailbox *mbl;
extern char *mntpt;
extern int biffing;
extern int plumbing;
extern char* Enotme;
enum
{
/* mail subobjects */
Qbody,
Qbcc,
Qcc,
Qdate,
Qdigest,
Qdisposition,
Qfilename,
Qfrom,
Qheader,
Qinreplyto,
Qlines,
Qmimeheader,
Qmessageid,
Qraw,
Qrawbody,
Qrawheader,
Qrawunix,
Qreplyto,
Qsender,
Qsubject,
Qto,
Qtype,
Qunixheader,
Qinfo,
Qunixdate,
Qmax,
/* other files */
Qtop,
Qmbox,
Qdir,
Qctl,
Qmboxctl,
};
#define PATH(id, f) ((((id)&0xfffff)<<10) | (f))
#define FILE(p) ((p) & 0x3ff)
/* char *dirtab[]; jpc */
// hash table to aid in name lookup, all files have an entry
typedef struct Hash Hash;
struct Hash {
Hash *next;
char *name;
ulong ppath;
Qid qid;
Mailbox *mb;
Message *m;
};
Hash *hlook(ulong, char*);
void henter(ulong, char*, Qid, Message*, Mailbox*);
void hfree(ulong, char*);
ulong msgallocd, msgfreed;

1704
src/cmd/upas/fs/fs.c Normal file

File diff suppressed because it is too large Load Diff

876
src/cmd/upas/fs/imap4.c Normal file
View File

@@ -0,0 +1,876 @@
#include "common.h"
#include <ctype.h>
#include <plumb.h>
#include <libsec.h>
#include <auth.h>
#include "dat.h"
#pragma varargck argpos imap4cmd 2
#pragma varargck type "Z" char*
int doublequote(Fmt*);
int pipeline = 1;
/* static char Eio[] = "i/o error"; jpc */
typedef struct Imap Imap;
struct Imap {
char *freep; // free this to free the strings below
char *host;
char *user;
char *mbox;
int mustssl;
int refreshtime;
int debug;
ulong tag;
ulong validity;
int nmsg;
int size;
char *base;
char *data;
vlong *uid;
int nuid;
int muid;
Thumbprint *thumb;
// open network connection
Biobuf bin;
Biobuf bout;
int fd;
};
static char*
removecr(char *s)
{
char *r, *w;
for(r=w=s; *r; r++)
if(*r != '\r')
*w++ = *r;
*w = '\0';
return s;
}
//
// send imap4 command
//
static void
imap4cmd(Imap *imap, char *fmt, ...)
{
char buf[128], *p;
va_list va;
va_start(va, fmt);
p = buf+sprint(buf, "9X%lud ", imap->tag);
vseprint(p, buf+sizeof(buf), fmt, va);
va_end(va);
p = buf+strlen(buf);
if(p > (buf+sizeof(buf)-3))
sysfatal("imap4 command too long");
if(imap->debug)
fprint(2, "-> %s\n", buf);
strcpy(p, "\r\n");
Bwrite(&imap->bout, buf, strlen(buf));
Bflush(&imap->bout);
}
enum {
OK,
NO,
BAD,
BYE,
EXISTS,
STATUS,
FETCH,
UNKNOWN,
};
static char *verblist[] = {
[OK] "OK",
[NO] "NO",
[BAD] "BAD",
[BYE] "BYE",
[EXISTS] "EXISTS",
[STATUS] "STATUS",
[FETCH] "FETCH",
};
static int
verbcode(char *verb)
{
int i;
char *q;
if(q = strchr(verb, ' '))
*q = '\0';
for(i=0; i<nelem(verblist); i++)
if(verblist[i] && strcmp(verblist[i], verb)==0){
if(q)
*q = ' ';
return i;
}
if(q)
*q = ' ';
return UNKNOWN;
}
static void
strupr(char *s)
{
for(; *s; s++)
if('a' <= *s && *s <= 'z')
*s += 'A'-'a';
}
static void
imapgrow(Imap *imap, int n)
{
int i;
if(imap->data == nil){
imap->base = emalloc(n+1);
imap->data = imap->base;
imap->size = n+1;
}
if(n >= imap->size){
// friggin microsoft - reallocate
i = imap->data - imap->base;
imap->base = erealloc(imap->base, i+n+1);
imap->data = imap->base + i;
imap->size = n+1;
}
}
//
// get imap4 response line. there might be various
// data or other informational lines mixed in.
//
static char*
imap4resp(Imap *imap)
{
char *line, *p, *ep, *op, *q, *r, *en, *verb;
int i, n;
static char error[256];
while(p = Brdline(&imap->bin, '\n')){
ep = p+Blinelen(&imap->bin);
while(ep > p && (ep[-1]=='\n' || ep[-1]=='\r'))
*--ep = '\0';
if(imap->debug)
fprint(2, "<- %s\n", p);
strupr(p);
switch(p[0]){
case '+':
if(imap->tag == 0)
fprint(2, "unexpected: %s\n", p);
break;
// ``unsolicited'' information; everything happens here.
case '*':
if(p[1]!=' ')
continue;
p += 2;
line = p;
n = strtol(p, &p, 10);
if(*p==' ')
p++;
verb = p;
if(p = strchr(verb, ' '))
p++;
else
p = verb+strlen(verb);
switch(verbcode(verb)){
case OK:
case NO:
case BAD:
// human readable text at p;
break;
case BYE:
// early disconnect
// human readable text at p;
break;
// * 32 EXISTS
case EXISTS:
imap->nmsg = n;
break;
// * STATUS Inbox (MESSAGES 2 UIDVALIDITY 960164964)
case STATUS:
if(q = strstr(p, "MESSAGES"))
imap->nmsg = atoi(q+8);
if(q = strstr(p, "UIDVALIDITY"))
imap->validity = strtoul(q+11, 0, 10);
break;
case FETCH:
// * 1 FETCH (uid 8889 RFC822.SIZE 3031 body[] {3031}
// <3031 bytes of data>
// )
if(strstr(p, "RFC822.SIZE") && strstr(p, "BODY[]")){
if((q = strchr(p, '{'))
&& (n=strtol(q+1, &en, 0), *en=='}')){
if(imap->data == nil || n >= imap->size)
imapgrow(imap, n);
if((i = Bread(&imap->bin, imap->data, n)) != n){
snprint(error, sizeof error,
"short read %d != %d: %r\n",
i, n);
return error;
}
if(imap->debug)
fprint(2, "<- read %d bytes\n", n);
imap->data[n] = '\0';
if(imap->debug)
fprint(2, "<- %s\n", imap->data);
imap->data += n;
imap->size -= n;
p = Brdline(&imap->bin, '\n');
if(imap->debug)
fprint(2, "<- ignoring %.*s\n",
Blinelen(&imap->bin), p);
}else if((q = strchr(p, '"')) && (r = strchr(q+1, '"'))){
*r = '\0';
q++;
n = r-q;
if(imap->data == nil || n >= imap->size)
imapgrow(imap, n);
memmove(imap->data, q, n);
imap->data[n] = '\0';
imap->data += n;
imap->size -= n;
}else
return "confused about FETCH response";
break;
}
// * 1 FETCH (UID 1 RFC822.SIZE 511)
if(q=strstr(p, "RFC822.SIZE")){
imap->size = atoi(q+11);
break;
}
// * 1 FETCH (UID 1 RFC822.HEADER {496}
// <496 bytes of data>
// )
// * 1 FETCH (UID 1 RFC822.HEADER "data")
if(strstr(p, "RFC822.HEADER") || strstr(p, "RFC822.TEXT")){
if((q = strchr(p, '{'))
&& (n=strtol(q+1, &en, 0), *en=='}')){
if(imap->data == nil || n >= imap->size)
imapgrow(imap, n);
if((i = Bread(&imap->bin, imap->data, n)) != n){
snprint(error, sizeof error,
"short read %d != %d: %r\n",
i, n);
return error;
}
if(imap->debug)
fprint(2, "<- read %d bytes\n", n);
imap->data[n] = '\0';
if(imap->debug)
fprint(2, "<- %s\n", imap->data);
imap->data += n;
imap->size -= n;
p = Brdline(&imap->bin, '\n');
if(imap->debug)
fprint(2, "<- ignoring %.*s\n",
Blinelen(&imap->bin), p);
}else if((q = strchr(p, '"')) && (r = strchr(q+1, '"'))){
*r = '\0';
q++;
n = r-q;
if(imap->data == nil || n >= imap->size)
imapgrow(imap, n);
memmove(imap->data, q, n);
imap->data[n] = '\0';
imap->data += n;
imap->size -= n;
}else
return "confused about FETCH response";
break;
}
// * 1 FETCH (UID 1)
// * 2 FETCH (UID 6)
if(q = strstr(p, "UID")){
if(imap->nuid < imap->muid)
imap->uid[imap->nuid++] = ((vlong)imap->validity<<32)|strtoul(q+3, nil, 10);
break;
}
}
if(imap->tag == 0)
return line;
break;
case '9': // response to our message
op = p;
if(p[1]=='X' && strtoul(p+2, &p, 10)==imap->tag){
while(*p==' ')
p++;
imap->tag++;
return p;
}
fprint(2, "expected %lud; got %s\n", imap->tag, op);
break;
default:
if(imap->debug || *p)
fprint(2, "unexpected line: %s\n", p);
}
}
snprint(error, sizeof error, "i/o error: %r\n");
return error;
}
static int
isokay(char *resp)
{
return strncmp(resp, "OK", 2)==0;
}
//
// log in to IMAP4 server, select mailbox, no SSL at the moment
//
static char*
imap4login(Imap *imap)
{
char *s;
UserPasswd *up;
imap->tag = 0;
s = imap4resp(imap);
if(!isokay(s))
return "error in initial IMAP handshake";
if(imap->user != nil)
up = auth_getuserpasswd(auth_getkey, "proto=pass service=imap server=%q user=%q", imap->host, imap->user);
else
up = auth_getuserpasswd(auth_getkey, "proto=pass service=imap server=%q", imap->host);
if(up == nil)
return "cannot find IMAP password";
imap->tag = 1;
imap4cmd(imap, "LOGIN %Z %Z", up->user, up->passwd);
free(up);
if(!isokay(s = imap4resp(imap)))
return s;
imap4cmd(imap, "SELECT %Z", imap->mbox);
if(!isokay(s = imap4resp(imap)))
return s;
return nil;
}
//
// push tls onto a connection
//
int
mypushtls(int fd)
{
int p[2];
char buf[10];
if(pipe(p) < 0)
return -1;
switch(fork()){
case -1:
close(p[0]);
close(p[1]);
return -1;
case 0:
close(p[1]);
dup(p[0], 0);
dup(p[0], 1);
sprint(buf, "/fd/%d", fd);
execl("/bin/tlsrelay", "tlsrelay", "-f", buf, nil);
_exits(nil);
default:
break;
}
close(fd);
close(p[0]);
return p[1];
}
//
// dial and handshake with the imap server
//
static char*
imap4dial(Imap *imap)
{
char *err, *port;
uchar digest[SHA1dlen];
int sfd;
TLSconn conn;
if(imap->fd >= 0){
imap4cmd(imap, "noop");
if(isokay(imap4resp(imap)))
return nil;
close(imap->fd);
imap->fd = -1;
}
if(imap->mustssl)
port = "imaps";
else
port = "imap4";
if((imap->fd = dial(netmkaddr(imap->host, "net", port), 0, 0, 0)) < 0)
return geterrstr();
if(imap->mustssl){
memset(&conn, 0, sizeof conn);
sfd = tlsClient(imap->fd, &conn);
if(sfd < 0)
sysfatal("tlsClient: %r");
if(conn.cert==nil || conn.certlen <= 0)
sysfatal("server did not provide TLS certificate");
sha1(conn.cert, conn.certlen, digest, nil);
if(!imap->thumb || !okThumbprint(digest, imap->thumb)){
fmtinstall('H', encodefmt);
sysfatal("server certificate %.*H not recognized", SHA1dlen, digest);
}
free(conn.cert);
close(imap->fd);
imap->fd = sfd;
if(imap->debug){
char fn[128];
int fd;
snprint(fn, sizeof fn, "%s/ctl", conn.dir);
fd = open(fn, ORDWR);
if(fd < 0)
fprint(2, "opening ctl: %r\n");
if(fprint(fd, "debug") < 0)
fprint(2, "writing ctl: %r\n");
close(fd);
}
}
Binit(&imap->bin, imap->fd, OREAD);
Binit(&imap->bout, imap->fd, OWRITE);
if(err = imap4login(imap)) {
close(imap->fd);
return err;
}
return nil;
}
//
// close connection
//
#if 0 /* jpc */
static void
imap4hangup(Imap *imap)
{
imap4cmd(imap, "LOGOUT");
imap4resp(imap);
close(imap->fd);
}
#endif
//
// download a single message
//
static char*
imap4fetch(Mailbox *mb, Message *m)
{
int i;
char *p, *s, sdigest[2*SHA1dlen+1];
Imap *imap;
imap = mb->aux;
imap->size = 0;
if(!isokay(s = imap4resp(imap)))
return s;
p = imap->base;
if(p == nil)
return "did not get message body";
removecr(p);
free(m->start);
m->start = p;
m->end = p+strlen(p);
m->bend = m->rbend = m->end;
m->header = m->start;
imap->base = nil;
imap->data = nil;
parse(m, 0, mb, 1);
// digest headers
sha1((uchar*)m->start, m->end - m->start, m->digest, nil);
for(i = 0; i < SHA1dlen; i++)
sprint(sdigest+2*i, "%2.2ux", m->digest[i]);
m->sdigest = s_copy(sdigest);
return nil;
}
//
// check for new messages on imap4 server
// download new messages, mark deleted messages
//
static char*
imap4read(Imap *imap, Mailbox *mb, int doplumb)
{
char *s;
int i, ignore, nnew, t;
Message *m, *next, **l;
imap4cmd(imap, "STATUS %Z (MESSAGES UIDVALIDITY)", imap->mbox);
if(!isokay(s = imap4resp(imap)))
return s;
imap->nuid = 0;
imap->uid = erealloc(imap->uid, imap->nmsg*sizeof(imap->uid[0]));
imap->muid = imap->nmsg;
if(imap->nmsg > 0){
imap4cmd(imap, "UID FETCH 1:* UID");
if(!isokay(s = imap4resp(imap)))
return s;
}
l = &mb->root->part;
for(i=0; i<imap->nuid; i++){
ignore = 0;
while(*l != nil){
if((*l)->imapuid == imap->uid[i]){
ignore = 1;
l = &(*l)->next;
break;
}else{
// old mail, we don't have it anymore
if(doplumb)
mailplumb(mb, *l, 1);
(*l)->inmbox = 0;
(*l)->deleted = 1;
l = &(*l)->next;
}
}
if(ignore)
continue;
// new message
m = newmessage(mb->root);
m->mallocd = 1;
m->inmbox = 1;
m->imapuid = imap->uid[i];
// add to chain, will download soon
*l = m;
l = &m->next;
}
// whatever is left at the end of the chain is gone
while(*l != nil){
if(doplumb)
mailplumb(mb, *l, 1);
(*l)->inmbox = 0;
(*l)->deleted = 1;
l = &(*l)->next;
}
// download new messages
t = imap->tag;
if(pipeline)
switch(rfork(RFPROC|RFMEM)){
case -1:
sysfatal("rfork: %r");
default:
break;
case 0:
for(m = mb->root->part; m != nil; m = m->next){
if(m->start != nil)
continue;
if(imap->debug)
fprint(2, "9X%d UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n",
t, (ulong)m->imapuid);
Bprint(&imap->bout, "9X%d UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n",
t++, (ulong)m->imapuid);
}
Bflush(&imap->bout);
_exits(nil);
}
nnew = 0;
for(m=mb->root->part; m!=nil; m=next){
next = m->next;
if(m->start != nil)
continue;
if(!pipeline){
Bprint(&imap->bout, "9X%lud UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n",
(ulong)imap->tag, (ulong)m->imapuid);
Bflush(&imap->bout);
}
if(s = imap4fetch(mb, m)){
// message disappeared? unchain
fprint(2, "download %lud: %s\n", (ulong)m->imapuid, s);
delmessage(mb, m);
mb->root->subname--;
continue;
}
nnew++;
if(doplumb)
mailplumb(mb, m, 0);
}
if(pipeline)
waitpid();
if(nnew || mb->vers == 0){
mb->vers++;
henter(PATH(0, Qtop), mb->name,
(Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
}
return nil;
}
//
// sync mailbox
//
static void
imap4purge(Imap *imap, Mailbox *mb)
{
int ndel;
Message *m, *next;
ndel = 0;
for(m=mb->root->part; m!=nil; m=next){
next = m->next;
if(m->deleted && m->refs==0){
if(m->inmbox && (ulong)(m->imapuid>>32)==imap->validity){
imap4cmd(imap, "UID STORE %lud +FLAGS (\\Deleted)", (ulong)m->imapuid);
if(isokay(imap4resp(imap))){
ndel++;
delmessage(mb, m);
}
}else
delmessage(mb, m);
}
}
if(ndel){
imap4cmd(imap, "EXPUNGE");
imap4resp(imap);
}
}
//
// connect to imap4 server, sync mailbox
//
static char*
imap4sync(Mailbox *mb, int doplumb)
{
char *err;
Imap *imap;
imap = mb->aux;
if(err = imap4dial(imap)){
mb->waketime = time(0) + imap->refreshtime;
return err;
}
if((err = imap4read(imap, mb, doplumb)) == nil){
imap4purge(imap, mb);
mb->d->atime = mb->d->mtime = time(0);
}
/*
* don't hang up; leave connection open for next time.
*/
// imap4hangup(imap);
mb->waketime = time(0) + imap->refreshtime;
return err;
}
static char Eimap4ctl[] = "bad imap4 control message";
static char*
imap4ctl(Mailbox *mb, int argc, char **argv)
{
int n;
Imap *imap;
imap = mb->aux;
if(argc < 1)
return Eimap4ctl;
if(argc==1 && strcmp(argv[0], "debug")==0){
imap->debug = 1;
return nil;
}
if(argc==1 && strcmp(argv[0], "nodebug")==0){
imap->debug = 0;
return nil;
}
if(argc==1 && strcmp(argv[0], "thumbprint")==0){
if(imap->thumb)
freeThumbprints(imap->thumb);
imap->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude");
}
if(strcmp(argv[0], "refresh")==0){
if(argc==1){
imap->refreshtime = 60;
return nil;
}
if(argc==2){
n = atoi(argv[1]);
if(n < 15)
return Eimap4ctl;
imap->refreshtime = n;
return nil;
}
}
return Eimap4ctl;
}
//
// free extra memory associated with mb
//
static void
imap4close(Mailbox *mb)
{
Imap *imap;
imap = mb->aux;
free(imap->freep);
free(imap->base);
free(imap->uid);
if(imap->fd >= 0)
close(imap->fd);
free(imap);
}
//
// open mailboxes of the form /imap/host/user
//
char*
imap4mbox(Mailbox *mb, char *path)
{
char *f[10];
int mustssl, nf;
Imap *imap;
quotefmtinstall();
fmtinstall('Z', doublequote);
if(strncmp(path, "/imap/", 6) != 0 && strncmp(path, "/imaps/", 7) != 0)
return Enotme;
mustssl = (strncmp(path, "/imaps/", 7) == 0);
path = strdup(path);
if(path == nil)
return "out of memory";
nf = getfields(path, f, 5, 0, "/");
if(nf < 3){
free(path);
return "bad imap path syntax /imap[s]/system[/user[/mailbox]]";
}
imap = emalloc(sizeof(*imap));
imap->fd = -1;
imap->debug = debug;
imap->freep = path;
imap->mustssl = mustssl;
imap->host = f[2];
if(nf < 4)
imap->user = nil;
else
imap->user = f[3];
if(nf < 5)
imap->mbox = "Inbox";
else
imap->mbox = f[4];
imap->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude");
mb->aux = imap;
mb->sync = imap4sync;
mb->close = imap4close;
mb->ctl = imap4ctl;
mb->d = emalloc(sizeof(*mb->d));
//mb->fetch = imap4fetch;
return nil;
}
//
// Formatter for %"
// Use double quotes to protect white space, frogs, \ and "
//
enum
{
Qok = 0,
Qquote,
Qbackslash,
};
static int
needtoquote(Rune r)
{
if(r >= Runeself)
return Qquote;
if(r <= ' ')
return Qquote;
if(r=='\\' || r=='"')
return Qbackslash;
return Qok;
}
int
doublequote(Fmt *f)
{
char *s, *t;
int w, quotes;
Rune r;
s = va_arg(f->args, char*);
if(s == nil || *s == '\0')
return fmtstrcpy(f, "\"\"");
quotes = 0;
for(t=s; *t; t+=w){
w = chartorune(&r, t);
quotes |= needtoquote(r);
}
if(quotes == 0)
return fmtstrcpy(f, s);
fmtrune(f, '"');
for(t=s; *t; t+=w){
w = chartorune(&r, t);
if(needtoquote(r) == Qbackslash)
fmtrune(f, '\\');
fmtrune(f, r);
}
return fmtrune(f, '"');
}

1601
src/cmd/upas/fs/mbox.c Normal file

File diff suppressed because it is too large Load Diff

29
src/cmd/upas/fs/mkfile Normal file
View File

@@ -0,0 +1,29 @@
<$PLAN9/src/mkhdr
TARG= fs\
OFILES=\
fs.$O\
imap4.$O\
mbox.$O\
plan9.$O\
pop3.$O\
strtotm.$O\
LIB=../common/libcommon.a\
# /usr/local/plan9/lib/libthread.a
HFILES= ../common/common.h\
dat.h
BIN=$PLAN9/bin/upas
UPDATE=\
mkfile\
$HFILES\
${TARG:%=%.c}\
${OFILES:%.$O=%.c}\
<$PLAN9/src/mkone
CFLAGS=$CFLAGS -I../common
# CFLAGS=$CFLAGS -I/sys/include -I../common

27
src/cmd/upas/fs/mkfile.9 Normal file
View File

@@ -0,0 +1,27 @@
</$objtype/mkfile
TARG= fs\
OFILES=\
fs.$O\
imap4.$O\
mbox.$O\
plan9.$O\
pop3.$O\
strtotm.$O\
LIB=../common/libcommon.a$O\
HFILES= ../common/common.h\
dat.h
BIN=/$objtype/bin/upas
UPDATE=\
mkfile\
$HFILES\
${TARG:%=%.c}\
${OFILES:%.$O=%.c}\
</sys/src/cmd/mkone
CFLAGS=$CFLAGS -I/sys/include -I../common

405
src/cmd/upas/fs/plan9.c Normal file
View File

@@ -0,0 +1,405 @@
#include "common.h"
#include <ctype.h>
#include <plumb.h>
#include <libsec.h>
#include "dat.h"
enum {
Buffersize = 64*1024,
};
typedef struct Inbuf Inbuf;
struct Inbuf
{
int fd;
uchar *lim;
uchar *rptr;
uchar *wptr;
uchar data[Buffersize+7];
};
static void
addtomessage(Message *m, uchar *p, int n, int done)
{
int i, len;
// add to message (+ 1 in malloc is for a trailing null)
if(m->lim - m->end < n){
if(m->start != nil){
i = m->end-m->start;
if(done)
len = i + n;
else
len = (4*(i+n))/3;
m->start = erealloc(m->start, len + 1);
m->end = m->start + i;
} else {
if(done)
len = n;
else
len = 2*n;
m->start = emalloc(len + 1);
m->end = m->start;
}
m->lim = m->start + len;
}
memmove(m->end, p, n);
m->end += n;
}
//
// read in a single message
//
static int
readmessage(Message *m, Inbuf *inb)
{
int i, n, done;
uchar *p, *np;
char sdigest[SHA1dlen*2+1];
char tmp[64];
for(done = 0; !done;){
n = inb->wptr - inb->rptr;
if(n < 6){
if(n)
memmove(inb->data, inb->rptr, n);
inb->rptr = inb->data;
inb->wptr = inb->rptr + n;
i = read(inb->fd, inb->wptr, Buffersize);
if(i < 0){
/* if(fd2path(inb->fd, tmp, sizeof tmp) < 0)
strcpy(tmp, "unknown mailbox"); jpc */
fprint(2, "error reading '%s': %r\n", tmp);
return -1;
}
if(i == 0){
if(n != 0)
addtomessage(m, inb->rptr, n, 1);
if(m->end == m->start)
return -1;
break;
}
inb->wptr += i;
}
// look for end of message
for(p = inb->rptr; p < inb->wptr; p = np+1){
// first part of search for '\nFrom '
np = memchr(p, '\n', inb->wptr - p);
if(np == nil){
p = inb->wptr;
break;
}
/*
* if we've found a \n but there's
* not enough room for '\nFrom ', don't do
* the comparison till we've read in more.
*/
if(inb->wptr - np < 6){
p = np;
break;
}
if(strncmp((char*)np, "\nFrom ", 6) == 0){
done = 1;
p = np+1;
break;
}
}
// add to message (+ 1 in malloc is for a trailing null)
n = p - inb->rptr;
addtomessage(m, inb->rptr, n, done);
inb->rptr += n;
}
// if it doesn't start with a 'From ', this ain't a mailbox
if(strncmp(m->start, "From ", 5) != 0)
return -1;
// dump trailing newline, make sure there's a trailing null
// (helps in body searches)
if(*(m->end-1) == '\n')
m->end--;
*m->end = 0;
m->bend = m->rbend = m->end;
// digest message
sha1((uchar*)m->start, m->end - m->start, m->digest, nil);
for(i = 0; i < SHA1dlen; i++)
sprint(sdigest+2*i, "%2.2ux", m->digest[i]);
m->sdigest = s_copy(sdigest);
return 0;
}
// throw out deleted messages. return number of freshly deleted messages
int
purgedeleted(Mailbox *mb)
{
Message *m, *next;
int newdels;
// forget about what's no longer in the mailbox
newdels = 0;
for(m = mb->root->part; m != nil; m = next){
next = m->next;
if(m->deleted && m->refs == 0){
if(m->inmbox)
newdels++;
delmessage(mb, m);
}
}
return newdels;
}
//
// read in the mailbox and parse into messages.
//
static char*
_readmbox(Mailbox *mb, int doplumb, Mlock *lk)
{
int fd;
String *tmp;
Dir *d;
static char err[128];
Message *m, **l;
Inbuf *inb;
char *x;
l = &mb->root->part;
/*
* open the mailbox. If it doesn't exist, try the temporary one.
*/
retry:
fd = open(mb->path, OREAD);
if(fd < 0){
errstr(err, sizeof(err));
if(strstr(err, "exist") != 0){
tmp = s_copy(mb->path);
s_append(tmp, ".tmp");
if(sysrename(s_to_c(tmp), mb->path) == 0){
s_free(tmp);
goto retry;
}
s_free(tmp);
}
return err;
}
/*
* a new qid.path means reread the mailbox, while
* a new qid.vers means read any new messages
*/
d = dirfstat(fd);
if(d == nil){
close(fd);
errstr(err, sizeof(err));
return err;
}
if(mb->d != nil){
if(d->qid.path == mb->d->qid.path && d->qid.vers == mb->d->qid.vers){
close(fd);
free(d);
return nil;
}
if(d->qid.path == mb->d->qid.path){
while(*l != nil)
l = &(*l)->next;
seek(fd, mb->d->length, 0);
}
free(mb->d);
}
mb->d = d;
mb->vers++;
henter(PATH(0, Qtop), mb->name,
(Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
inb = emalloc(sizeof(Inbuf));
inb->rptr = inb->wptr = inb->data;
inb->fd = fd;
// read new messages
snprint(err, sizeof err, "reading '%s'", mb->path);
logmsg(err, nil);
for(;;){
if(lk != nil)
syslockrefresh(lk);
m = newmessage(mb->root);
m->mallocd = 1;
m->inmbox = 1;
if(readmessage(m, inb) < 0){
delmessage(mb, m);
mb->root->subname--;
break;
}
// merge mailbox versions
while(*l != nil){
if(memcmp((*l)->digest, m->digest, SHA1dlen) == 0){
// matches mail we already read, discard
logmsg("duplicate", *l);
delmessage(mb, m);
mb->root->subname--;
m = nil;
l = &(*l)->next;
break;
} else {
// old mail no longer in box, mark deleted
logmsg("disappeared", *l);
if(doplumb)
mailplumb(mb, *l, 1);
(*l)->inmbox = 0;
(*l)->deleted = 1;
l = &(*l)->next;
}
}
if(m == nil)
continue;
x = strchr(m->start, '\n');
if(x == nil)
m->header = m->end;
else
m->header = x + 1;
m->mheader = m->mhend = m->header;
parseunix(m);
parse(m, 0, mb, 0);
logmsg("new", m);
/* chain in */
*l = m;
l = &m->next;
if(doplumb)
mailplumb(mb, m, 0);
}
logmsg("mbox read", nil);
// whatever is left has been removed from the mbox, mark deleted
while(*l != nil){
if(doplumb)
mailplumb(mb, *l, 1);
(*l)->inmbox = 0;
(*l)->deleted = 1;
l = &(*l)->next;
}
close(fd);
free(inb);
return nil;
}
static void
_writembox(Mailbox *mb, Mlock *lk)
{
Dir *d;
Message *m;
String *tmp;
int mode, errs;
Biobuf *b;
tmp = s_copy(mb->path);
s_append(tmp, ".tmp");
/*
* preserve old files permissions, if possible
*/
d = dirstat(mb->path);
if(d != nil){
mode = d->mode&0777;
free(d);
} else
mode = MBOXMODE;
sysremove(s_to_c(tmp));
b = sysopen(s_to_c(tmp), "alc", mode);
if(b == 0){
fprint(2, "can't write temporary mailbox %s: %r\n", s_to_c(tmp));
return;
}
logmsg("writing new mbox", nil);
errs = 0;
for(m = mb->root->part; m != nil; m = m->next){
if(lk != nil)
syslockrefresh(lk);
if(m->deleted)
continue;
logmsg("writing", m);
if(Bwrite(b, m->start, m->end - m->start) < 0)
errs = 1;
if(Bwrite(b, "\n", 1) < 0)
errs = 1;
}
logmsg("wrote new mbox", nil);
if(sysclose(b) < 0)
errs = 1;
if(errs){
fprint(2, "error writing temporary mail file\n");
s_free(tmp);
return;
}
sysremove(mb->path);
if(sysrename(s_to_c(tmp), mb->path) < 0)
fprint(2, "%s: can't rename %s to %s: %r\n", argv0,
s_to_c(tmp), mb->path);
s_free(tmp);
if(mb->d != nil)
free(mb->d);
mb->d = dirstat(mb->path);
}
char*
plan9syncmbox(Mailbox *mb, int doplumb)
{
Mlock *lk;
char *rv;
lk = nil;
if(mb->dolock){
lk = syslock(mb->path);
if(lk == nil)
return "can't lock mailbox";
}
rv = _readmbox(mb, doplumb, lk); /* interpolate */
if(purgedeleted(mb) > 0)
_writembox(mb, lk);
if(lk != nil)
sysunlock(lk);
return rv;
}
//
// look to see if we can open this mail box
//
char*
plan9mbox(Mailbox *mb, char *path)
{
static char err[64];
String *tmp;
if(access(path, AEXIST) < 0){
errstr(err, sizeof(err));
tmp = s_copy(path);
s_append(tmp, ".tmp");
if(access(s_to_c(tmp), AEXIST) < 0){
s_free(tmp);
return err;
}
s_free(tmp);
}
mb->sync = plan9syncmbox;
return nil;
}

700
src/cmd/upas/fs/pop3.c Normal file
View File

@@ -0,0 +1,700 @@
#include "common.h"
#include <ctype.h>
#include <plumb.h>
#include <libsec.h>
#include <auth.h>
#include <thread.h>
#include "dat.h"
#pragma varargck type "M" uchar*
#pragma varargck argpos pop3cmd 2
typedef struct Pop Pop;
struct Pop {
char *freep; // free this to free the strings below
char *host;
char *user;
char *port;
int ppop;
int refreshtime;
int debug;
int pipeline;
int encrypted;
int needtls;
int notls;
int needssl;
// open network connection
Biobuf bin;
Biobuf bout;
int fd;
char *lastline; // from Brdstr
Thumbprint *thumb;
};
char*
geterrstr(void)
{
static char err[64];
err[0] = '\0';
errstr(err, sizeof(err));
return err;
}
//
// get pop3 response line , without worrying
// about multiline responses; the clients
// will deal with that.
//
static int
isokay(char *s)
{
return s!=nil && strncmp(s, "+OK", 3)==0;
}
static void
pop3cmd(Pop *pop, char *fmt, ...)
{
char buf[128], *p;
va_list va;
va_start(va, fmt);
vseprint(buf, buf+sizeof(buf), fmt, va);
va_end(va);
p = buf+strlen(buf);
if(p > (buf+sizeof(buf)-3))
sysfatal("pop3 command too long");
if(pop->debug)
fprint(2, "<- %s\n", buf);
strcpy(p, "\r\n");
Bwrite(&pop->bout, buf, strlen(buf));
Bflush(&pop->bout);
}
static char*
pop3resp(Pop *pop)
{
char *s;
char *p;
alarm(60*1000);
if((s = Brdstr(&pop->bin, '\n', 0)) == nil){
close(pop->fd);
pop->fd = -1;
alarm(0);
return "unexpected eof";
}
alarm(0);
p = s+strlen(s)-1;
while(p >= s && (*p == '\r' || *p == '\n'))
*p-- = '\0';
if(pop->debug)
fprint(2, "-> %s\n", s);
free(pop->lastline);
pop->lastline = s;
return s;
}
#if 0 /* jpc */
static int
pop3log(char *fmt, ...)
{
va_list ap;
va_start(ap,fmt);
syslog(0, "/sys/log/pop3", fmt, ap);
va_end(ap);
return 0;
}
#endif
static char*
pop3pushtls(Pop *pop)
{
int fd;
uchar digest[SHA1dlen];
TLSconn conn;
memset(&conn, 0, sizeof conn);
// conn.trace = pop3log;
fd = tlsClient(pop->fd, &conn);
if(fd < 0)
return "tls error";
if(conn.cert==nil || conn.certlen <= 0){
close(fd);
return "server did not provide TLS certificate";
}
sha1(conn.cert, conn.certlen, digest, nil);
if(!pop->thumb || !okThumbprint(digest, pop->thumb)){
fmtinstall('H', encodefmt);
close(fd);
free(conn.cert);
fprint(2, "upas/fs pop3: server certificate %.*H not recognized\n", SHA1dlen, digest);
return "bad server certificate";
}
free(conn.cert);
close(pop->fd);
pop->fd = fd;
pop->encrypted = 1;
Binit(&pop->bin, pop->fd, OREAD);
Binit(&pop->bout, pop->fd, OWRITE);
return nil;
}
//
// get capability list, possibly start tls
//
static char*
pop3capa(Pop *pop)
{
char *s;
int hastls;
pop3cmd(pop, "CAPA");
if(!isokay(pop3resp(pop)))
return nil;
hastls = 0;
for(;;){
s = pop3resp(pop);
if(strcmp(s, ".") == 0 || strcmp(s, "unexpected eof") == 0)
break;
if(strcmp(s, "STLS") == 0)
hastls = 1;
if(strcmp(s, "PIPELINING") == 0)
pop->pipeline = 1;
}
if(hastls && !pop->notls){
pop3cmd(pop, "STLS");
if(!isokay(s = pop3resp(pop)))
return s;
if((s = pop3pushtls(pop)) != nil)
return s;
}
return nil;
}
//
// log in using APOP if possible, password if allowed by user
//
static char*
pop3login(Pop *pop)
{
int n;
char *s, *p, *q;
char ubuf[128], user[128];
char buf[500];
UserPasswd *up;
s = pop3resp(pop);
if(!isokay(s))
return "error in initial handshake";
if(pop->user)
snprint(ubuf, sizeof ubuf, " user=%q", pop->user);
else
ubuf[0] = '\0';
// look for apop banner
if(pop->ppop==0 && (p = strchr(s, '<')) && (q = strchr(p+1, '>'))) {
*++q = '\0';
if((n=auth_respond(p, q-p, user, sizeof user, buf, sizeof buf, auth_getkey, "proto=apop role=client server=%q%s",
pop->host, ubuf)) < 0)
return "factotum failed";
if(user[0]=='\0')
return "factotum did not return a user name";
if(s = pop3capa(pop))
return s;
pop3cmd(pop, "APOP %s %.*s", user, n, buf);
if(!isokay(s = pop3resp(pop)))
return s;
return nil;
} else {
if(pop->ppop == 0)
return "no APOP hdr from server";
if(s = pop3capa(pop))
return s;
if(pop->needtls && !pop->encrypted)
return "could not negotiate TLS";
up = auth_getuserpasswd(auth_getkey, "role=client proto=pass service=pop dom=%q%s",
pop->host, ubuf);
/* up = auth_getuserpasswd(auth_getkey, "proto=pass service=pop dom=%q%s",
pop->host, ubuf); jpc */
if(up == nil)
return "no usable keys found";
pop3cmd(pop, "USER %s", up->user);
if(!isokay(s = pop3resp(pop))){
free(up);
return s;
}
pop3cmd(pop, "PASS %s", up->passwd);
free(up);
if(!isokay(s = pop3resp(pop)))
return s;
return nil;
}
}
//
// dial and handshake with pop server
//
static char*
pop3dial(Pop *pop)
{
char *err;
if((pop->fd = dial(netmkaddr(pop->host, "net", pop->needssl ? "pop3s" : "pop3"), 0, 0, 0)) < 0)
return geterrstr();
if(pop->needssl){
if((err = pop3pushtls(pop)) != nil)
return err;
}else{
Binit(&pop->bin, pop->fd, OREAD);
Binit(&pop->bout, pop->fd, OWRITE);
}
if(err = pop3login(pop)) {
close(pop->fd);
return err;
}
return nil;
}
//
// close connection
//
static void
pop3hangup(Pop *pop)
{
pop3cmd(pop, "QUIT");
pop3resp(pop);
close(pop->fd);
}
//
// download a single message
//
static char*
pop3download(Pop *pop, Message *m)
{
char *s, *f[3], *wp, *ep;
char sdigest[SHA1dlen*2+1];
int i, l, sz;
if(!pop->pipeline)
pop3cmd(pop, "LIST %d", m->mesgno);
if(!isokay(s = pop3resp(pop)))
return s;
if(tokenize(s, f, 3) != 3)
return "syntax error in LIST response";
if(atoi(f[1]) != m->mesgno)
return "out of sync with pop3 server";
sz = atoi(f[2])+200; /* 200 because the plan9 pop3 server lies */
if(sz == 0)
return "invalid size in LIST response";
m->start = wp = emalloc(sz+1);
ep = wp+sz;
if(!pop->pipeline)
pop3cmd(pop, "RETR %d", m->mesgno);
if(!isokay(s = pop3resp(pop))) {
m->start = nil;
free(wp);
return s;
}
s = nil;
while(wp <= ep) {
s = pop3resp(pop);
if(strcmp(s, "unexpected eof") == 0) {
free(m->start);
m->start = nil;
return "unexpected end of conversation";
}
if(strcmp(s, ".") == 0)
break;
l = strlen(s)+1;
if(s[0] == '.') {
s++;
l--;
}
/*
* grow by 10%/200bytes - some servers
* lie about message sizes
*/
if(wp+l > ep) {
int pos = wp - m->start;
sz += ((sz / 10) < 200)? 200: sz/10;
m->start = erealloc(m->start, sz+1);
wp = m->start+pos;
ep = m->start+sz;
}
memmove(wp, s, l-1);
wp[l-1] = '\n';
wp += l;
}
if(s == nil || strcmp(s, ".") != 0)
return "out of sync with pop3 server";
m->end = wp;
// make sure there's a trailing null
// (helps in body searches)
*m->end = 0;
m->bend = m->rbend = m->end;
m->header = m->start;
// digest message
sha1((uchar*)m->start, m->end - m->start, m->digest, nil);
for(i = 0; i < SHA1dlen; i++)
sprint(sdigest+2*i, "%2.2ux", m->digest[i]);
m->sdigest = s_copy(sdigest);
return nil;
}
//
// check for new messages on pop server
// UIDL is not required by RFC 1939, but
// netscape requires it, so almost every server supports it.
// we'll use it to make our lives easier.
//
static char*
pop3read(Pop *pop, Mailbox *mb, int doplumb)
{
char *s, *p, *uidl, *f[2];
int mesgno, ignore, nnew;
Message *m, *next, **l;
// Some POP servers disallow UIDL if the maildrop is empty.
pop3cmd(pop, "STAT");
if(!isokay(s = pop3resp(pop)))
return s;
// fetch message listing; note messages to grab
l = &mb->root->part;
if(strncmp(s, "+OK 0 ", 6) != 0) {
pop3cmd(pop, "UIDL");
if(!isokay(s = pop3resp(pop)))
return s;
for(;;){
p = pop3resp(pop);
if(strcmp(p, ".") == 0 || strcmp(p, "unexpected eof") == 0)
break;
if(tokenize(p, f, 2) != 2)
continue;
mesgno = atoi(f[0]);
uidl = f[1];
if(strlen(uidl) > 75) // RFC 1939 says 70 characters max
continue;
ignore = 0;
while(*l != nil) {
if(strcmp((*l)->uidl, uidl) == 0) {
// matches mail we already have, note mesgno for deletion
(*l)->mesgno = mesgno;
ignore = 1;
l = &(*l)->next;
break;
} else {
// old mail no longer in box mark deleted
if(doplumb)
mailplumb(mb, *l, 1);
(*l)->inmbox = 0;
(*l)->deleted = 1;
l = &(*l)->next;
}
}
if(ignore)
continue;
m = newmessage(mb->root);
m->mallocd = 1;
m->inmbox = 1;
m->mesgno = mesgno;
strcpy(m->uidl, uidl);
// chain in; will fill in message later
*l = m;
l = &m->next;
}
}
// whatever is left has been removed from the mbox, mark as deleted
while(*l != nil) {
if(doplumb)
mailplumb(mb, *l, 1);
(*l)->inmbox = 0;
(*l)->deleted = 1;
l = &(*l)->next;
}
// download new messages
nnew = 0;
if(pop->pipeline){
switch(rfork(RFPROC|RFMEM)){
case -1:
fprint(2, "rfork: %r\n");
pop->pipeline = 0;
default:
break;
case 0:
for(m = mb->root->part; m != nil; m = m->next){
if(m->start != nil)
continue;
Bprint(&pop->bout, "LIST %d\r\nRETR %d\r\n", m->mesgno, m->mesgno);
}
Bflush(&pop->bout);
threadexits(nil);
/* _exits(nil); jpc */
}
}
for(m = mb->root->part; m != nil; m = next) {
next = m->next;
if(m->start != nil)
continue;
if(s = pop3download(pop, m)) {
// message disappeared? unchain
fprint(2, "download %d: %s\n", m->mesgno, s);
delmessage(mb, m);
mb->root->subname--;
continue;
}
nnew++;
parse(m, 0, mb, 1);
if(doplumb)
mailplumb(mb, m, 0);
}
if(pop->pipeline)
waitpid();
if(nnew || mb->vers == 0) {
mb->vers++;
henter(PATH(0, Qtop), mb->name,
(Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
}
return nil;
}
//
// delete marked messages
//
static void
pop3purge(Pop *pop, Mailbox *mb)
{
Message *m, *next;
if(pop->pipeline){
switch(rfork(RFPROC|RFMEM)){
case -1:
fprint(2, "rfork: %r\n");
pop->pipeline = 0;
default:
break;
case 0:
for(m = mb->root->part; m != nil; m = next){
next = m->next;
if(m->deleted && m->refs == 0){
if(m->inmbox)
Bprint(&pop->bout, "DELE %d\r\n", m->mesgno);
}
}
Bflush(&pop->bout);
/* _exits(nil); jpc */
threadexits(nil);
}
}
for(m = mb->root->part; m != nil; m = next) {
next = m->next;
if(m->deleted && m->refs == 0) {
if(m->inmbox) {
if(!pop->pipeline)
pop3cmd(pop, "DELE %d", m->mesgno);
if(isokay(pop3resp(pop)))
delmessage(mb, m);
} else
delmessage(mb, m);
}
}
}
// connect to pop3 server, sync mailbox
static char*
pop3sync(Mailbox *mb, int doplumb)
{
char *err;
Pop *pop;
pop = mb->aux;
if(err = pop3dial(pop)) {
mb->waketime = time(0) + pop->refreshtime;
return err;
}
if((err = pop3read(pop, mb, doplumb)) == nil){
pop3purge(pop, mb);
mb->d->atime = mb->d->mtime = time(0);
}
pop3hangup(pop);
mb->waketime = time(0) + pop->refreshtime;
return err;
}
static char Epop3ctl[] = "bad pop3 control message";
static char*
pop3ctl(Mailbox *mb, int argc, char **argv)
{
int n;
Pop *pop;
char *m, *me;
pop = mb->aux;
if(argc < 1)
return Epop3ctl;
if(argc==1 && strcmp(argv[0], "debug")==0){
pop->debug = 1;
return nil;
}
if(argc==1 && strcmp(argv[0], "nodebug")==0){
pop->debug = 0;
return nil;
}
if(argc==1 && strcmp(argv[0], "thumbprint")==0){
if(pop->thumb)
freeThumbprints(pop->thumb);
/* pop->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude"); jpc */
m = unsharp("#9/sys/lib/tls/mail");
me = unsharp("#9/sys/lib/tls/mail.exclude");
pop->thumb = initThumbprints(m, me);
}
if(strcmp(argv[0], "refresh")==0){
if(argc==1){
pop->refreshtime = 60;
return nil;
}
if(argc==2){
n = atoi(argv[1]);
if(n < 15)
return Epop3ctl;
pop->refreshtime = n;
return nil;
}
}
return Epop3ctl;
}
// free extra memory associated with mb
static void
pop3close(Mailbox *mb)
{
Pop *pop;
pop = mb->aux;
free(pop->freep);
free(pop);
}
//
// open mailboxes of the form /pop/host/user or /apop/host/user
//
char*
pop3mbox(Mailbox *mb, char *path)
{
char *f[10];
int nf, apop, ppop, popssl, apopssl, apoptls, popnotls, apopnotls, poptls;
Pop *pop;
char *m, *me;
quotefmtinstall();
popssl = strncmp(path, "/pops/", 6) == 0;
apopssl = strncmp(path, "/apops/", 7) == 0;
poptls = strncmp(path, "/poptls/", 8) == 0;
popnotls = strncmp(path, "/popnotls/", 10) == 0;
ppop = popssl || poptls || popnotls || strncmp(path, "/pop/", 5) == 0;
apoptls = strncmp(path, "/apoptls/", 9) == 0;
apopnotls = strncmp(path, "/apopnotls/", 11) == 0;
apop = apopssl || apoptls || apopnotls || strncmp(path, "/apop/", 6) == 0;
if(!ppop && !apop)
return Enotme;
path = strdup(path);
if(path == nil)
return "out of memory";
nf = getfields(path, f, nelem(f), 0, "/");
if(nf != 3 && nf != 4) {
free(path);
return "bad pop3 path syntax /[a]pop[tls|ssl]/system[/user]";
}
pop = emalloc(sizeof(*pop));
pop->freep = path;
pop->host = f[2];
if(nf < 4)
pop->user = nil;
else
pop->user = f[3];
pop->ppop = ppop;
pop->needssl = popssl || apopssl;
pop->needtls = poptls || apoptls;
pop->refreshtime = 60;
pop->notls = popnotls || apopnotls;
/* pop->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude"); jpc */
m = unsharp("#9/sys/lib/tls/mail");
me = unsharp("#9/sys/lib/tls/mail.exclude");
pop->thumb = initThumbprints(m, me);
mb->aux = pop;
mb->sync = pop3sync;
mb->close = pop3close;
mb->ctl = pop3ctl;
mb->d = emalloc(sizeof(*mb->d));
return nil;
}

15
src/cmd/upas/fs/readdir.c Normal file
View File

@@ -0,0 +1,15 @@
#include <u.h>
#include <libc.h>
void
main(void)
{
Dir d;
int fd, n;
fd = open("/mail/fs", OREAD);
while((n = dirread(fd, &d, sizeof(d))) > 0){
print("%s\n", d.name);
}
print("n = %d\n", n);
}

113
src/cmd/upas/fs/strtotm.c Normal file
View File

@@ -0,0 +1,113 @@
#include <u.h>
#include <libc.h>
#include <ctype.h>
static char*
skiptext(char *q)
{
while(*q!='\0' && *q!=' ' && *q!='\t' && *q!='\r' && *q!='\n')
q++;
return q;
}
static char*
skipwhite(char *q)
{
while(*q==' ' || *q=='\t' || *q=='\r' || *q=='\n')
q++;
return q;
}
static char* months[] = {
"jan", "feb", "mar", "apr",
"may", "jun", "jul", "aug",
"sep", "oct", "nov", "dec"
};
static int
strcmplwr(char *a, char *b, int n)
{
char *eb;
eb = b+n;
while(*a && *b && b<eb){
if(tolower(*a) != tolower(*b))
return 1;
a++;
b++;
}
if(b==eb)
return 0;
return *a != *b;
}
int
strtotm(char *p, Tm *tmp)
{
char *q, *r;
int j;
Tm tm;
int delta;
delta = 0;
memset(&tm, 0, sizeof(tm));
tm.mon = -1;
tm.hour = -1;
tm.min = -1;
tm.year = -1;
tm.mday = -1;
for(p=skipwhite(p); *p; p=skipwhite(q)){
q = skiptext(p);
/* look for time in hh:mm[:ss] */
if(r = memchr(p, ':', q-p)){
tm.hour = strtol(p, 0, 10);
tm.min = strtol(r+1, 0, 10);
if(r = memchr(r+1, ':', q-(r+1)))
tm.sec = strtol(r+1, 0, 10);
else
tm.sec = 0;
continue;
}
/* look for month */
for(j=0; j<12; j++)
if(strcmplwr(p, months[j], 3)==0){
tm.mon = j;
break;
}
if(j!=12)
continue;
/* look for time zone [A-Z][A-Z]T */
if(q-p==3 && 'A' <= p[0] && p[0] <= 'Z'
&& 'A' <= p[1] && p[1] <= 'Z' && p[2] == 'T'){
strecpy(tm.zone, tm.zone+4, p);
continue;
}
if(p[0]=='+'||p[0]=='-')
if(q-p==5 && strspn(p+1, "0123456789") == 4){
delta = (((p[1]-'0')*10+p[2]-'0')*60+(p[3]-'0')*10+p[4]-'0')*60;
if(p[0] == '-')
delta = -delta;
continue;
}
if(strspn(p, "0123456789") == q-p){
j = strtol(p, nil, 10);
if(1 <= j && j <= 31)
tm.mday = j;
if(j >= 1900)
tm.year = j-1900;
}
}
if(tm.mon<0 || tm.year<0
|| tm.hour<0 || tm.min<0
|| tm.mday<0)
return -1;
*tmp = *localtime(tm2sec(&tm)-delta);
return 0;
}

81
src/cmd/upas/fs/tester.c Normal file
View File

@@ -0,0 +1,81 @@
#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <String.h>
#include "message.h"
Message *root;
void
prindent(int i)
{
for(; i > 0; i--)
print(" ");
}
void
prstring(int indent, char *tag, String *s)
{
if(s == nil)
return;
prindent(indent+1);
print("%s %s\n", tag, s_to_c(s));
}
void
info(int indent, int mno, Message *m)
{
int i;
Message *nm;
prindent(indent);
print("%d%c %d ", mno, m->allocated?'*':' ', m->end - m->start);
if(m->unixfrom != nil)
print("uf %s ", s_to_c(m->unixfrom));
if(m->unixdate != nil)
print("ud %s ", s_to_c(m->unixdate));
print("\n");
prstring(indent, "from:", m->from822);
prstring(indent, "sender:", m->sender822);
prstring(indent, "to:", m->to822);
prstring(indent, "cc:", m->cc822);
prstring(indent, "reply-to:", m->replyto822);
prstring(indent, "subject:", m->subject822);
prstring(indent, "date:", m->date822);
prstring(indent, "filename:", m->filename);
prstring(indent, "type:", m->type);
prstring(indent, "charset:", m->charset);
i = 1;
for(nm = m->part; nm != nil; nm = nm->next){
info(indent+1, i++, nm);
}
}
void
main(int argc, char **argv)
{
char *err;
char *mboxfile;
ARGBEGIN{
}ARGEND;
if(argc > 0)
mboxfile = argv[0];
else
mboxfile = "./mbox";
root = newmessage(nil);
err = readmbox(mboxfile, &root->part);
if(err != nil){
fprint(2, "boom: %s\n", err);
exits(0);
}
info(0, 1, root);
exits(0);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,20 @@
<$PLAN9/src/mkhdr
TARG=marshal
LIB=../common/libcommon.a\
HFILES= ../common/common.h\
OFILES= marshal.$O
BIN=$PLAN9/bin/upas
UPDATE=\
mkfile\
$HFILES\
${OFILES:%.$O=%.c}\
<$PLAN9/src/mkone
CFLAGS=$CFLAGS -I../common

View File

@@ -0,0 +1,9 @@
#!/bin/sh
PATH=/bin:/usr/bin
message=${1-/usr/lib/upas/gone.msg}
return=`sed '2,$s/^From[ ]/>&/'|tee -a $HOME/gone.mail|sed -n '1s/^From[ ]\([^ ]*\)[ ].*$/\1/p'`
echo '' >>$HOME/gone.mail
grep "^$return" $HOME/gone.addrs >/dev/null 2>/dev/null || {
echo $return >>$HOME/gone.addrs
mail $return < $message
}

View File

@@ -0,0 +1,4 @@
This is a recorded message. I am currently out of contact with my
computer system. Your message to me has been saved and will be
read upon my return. This is the last time you will receive this
message during my absence. Thank you.

51
src/cmd/upas/misc/mail.c Normal file
View File

@@ -0,0 +1,51 @@
/*
* #!/bin/sh
* case $1 in
* -n)
* exit 0 ;;
* -m*|-f*|-r*|-p*|-e*|"")
* exec /usr/lib/upas/edmail $*
* exit $? ;;
* *)
* exec /usr/lib/upas/send $*
* exit $? ;;
* esac
*/
extern *UPASROOT;
#define EDMAIL "edmail"
#define SEND "send"
main (argc, argv)
int argc;
char **argv;
{
char *progname = SEND;
char realprog[500];
if (argc > 1) {
if (argv[1][0] == '-') {
switch (argv[1][1]) {
case 'n':
exit (0);
case 'm':
case 'f':
case 'r':
case 'p':
case 'e':
case '\0':
progname = EDMAIL;
}
}
} else
progname = EDMAIL;
sprint(realprog, "%s/%s", UPASROOT, progname);
execv (realprog, argv);
perror (realprog);
exit (1);
}

12
src/cmd/upas/misc/mail.rc Executable file
View File

@@ -0,0 +1,12 @@
#!/bin/rc
switch($#*){
case 0
exec upas/nedmail
}
switch($1){
case -f* -r* -c* -m*
exec upas/nedmail $*
case *
exec upas/marshal $*
}

12
src/cmd/upas/misc/mail.sh Normal file
View File

@@ -0,0 +1,12 @@
#!/bin/sh
case $1 in
-n)
exec LIBDIR/notify
exit $? ;;
-m*|-f*|-r*|-p*|-e*|"")
exec LIBDIR/edmail $*
exit $? ;;
*)
exec LIBDIR/send $*
exit $? ;;
esac

View File

@@ -0,0 +1,44 @@
LIB=/usr/lib/upas
CFLAGS=${UNIX} -g -I. -I../libc -I../common -I/usr/include -I/usr/include/sys
LFLAGS=-g
HOSTNAME=cat /etc/whoami
.c.o: ; $(CC) -c $(CFLAGS) $*.c
all: mail
sedfile:
echo 's+LIBDIR+$(LIB)+g' >sed.file
echo 's+HOSTNAME+$(HOSTNAME)+g' >>sed.file
install: sedfile install.fish install.mail.sh
install.fish:
cp gone.msg $(LIB)
sed -f sed.file gone.fishing >$(LIB)/gone.fishing
-chmod 775 $(LIB)/gone.fishing
-chown bin $(LIB)/gone.fishing $(LIB)/gone.msg
install.mail.sh:
sed -f sed.file mail.sh >/bin/mail
-chown bin /bin/mail
-chmod 775 /bin/mail
install.notify: notify
cp notify $(LIB)/notify
-chmod 775 $(LIB)/notify
-chown bin $(LIB)/notify
install.mail: mail
cp mail /bin
strip /bin/mail
notify: notify.o
cc $(LFLAGS) notify.o -o notify
mail: mail.o ../config/config.o
cc $(LFLAGS) mail.o ../config/config.o -o mail
clean:
-rm -f *.[oOa] core a.out *.sL notify
-rm -f sed.file mail

39
src/cmd/upas/misc/mkfile Normal file
View File

@@ -0,0 +1,39 @@
RCFILES=mail.rc\
all:Q:
;
installall:Q: install
;
install:V:
cp mail.rc /rc/bin/mail
safeinstall:V:
cp mail.rc /rc/bin/mail
safeinstallall:V:
cp mail.rc /rc/bin/mail
clean:Q:
;
nuke:V:
rm /rc/bin/mail
UPDATE=\
gone.fishing\
gone.msg\
mail.c\
mail.rc\
mail.sh\
makefile\
mkfile\
namefiles\
omail.rc\
qmail\
remotemail\
rewrite\
update:V:
update $UPDATEFLAGS $UPDATE

View File

@@ -0,0 +1,2 @@
names.local
names.global

14
src/cmd/upas/misc/omail.rc Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/rc
switch($#*){
case 0
exec upas/edmail -m
}
switch($1){
case -F* -m* -f* -r* -p* -e* -c* -D*
exec upas/edmail -m $*
case '-#'* -a*
exec upas/sendmail $*
case *
exec upas/sendmail $*
}

6
src/cmd/upas/misc/qmail Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/rc
sender=$1
shift
addr=$1
shift
qer /mail/queue mail $sender $addr $* && runq /mail/queue /mail/lib/remotemail

7
src/cmd/upas/misc/remotemail Executable file
View File

@@ -0,0 +1,7 @@
#!/bin/rc
shift
sender=$1
shift
addr=$1
shift
/bin/upas/smtp -g research.research.bell-labs.com $addr $sender $*

20
src/cmd/upas/misc/rewrite Normal file
View File

@@ -0,0 +1,20 @@
# case conversion for postmaster
pOsTmAsTeR alias postmaster
# local mail
[^!@]+ translate "/bin/upas/aliasmail '&'"
local!(.*) >> /mail/box/\1/mbox
\l!(.*) alias \1
(helix|helix.bell-labs.com)!(.*) alias \2
# we can be just as complicated as BSD sendmail...
# convert source domain address to a chain a@b@c@d...
@([^@!,]*):([^!@]*)@([^!]*) alias \2@\3@\1
@([^@!]*),([^!@,]*):([^!@]*)@([^!]*) alias @\1:\3@\4@\2
# convert a chain a@b@c@d... to ...d!c!b!a
([^@]+)@([^@]+)@(.+) alias \2!\1@\3
([^@]+)@([^@]+) alias \2!\1
# /mail/lib/remotemail will take care of gating to systems we don't know
([^!]*)!(.*) | "/mail/lib/qmail '\s' 'net!\1'" "'\2'"

197
src/cmd/upas/ml/common.c Normal file
View File

@@ -0,0 +1,197 @@
#include "common.h"
#include "dat.h"
String*
getaddr(Node *p)
{
for(; p; p = p->next){
if(p->s && p->addr)
return p->s;
}
return nil;
}
/* send messae adding our own reply-to and precedence */
void
getaddrs(void)
{
Field *f;
for(f = firstfield; f; f = f->next){
if(f->node->c == FROM && from == nil)
from = getaddr(f->node);
if(f->node->c == SENDER && sender == nil)
sender = getaddr(f->node);
}
}
/* write address file, should be append only */
void
writeaddr(char *file, char *addr, int rem, char *listname)
{
int fd;
Dir nd;
fd = open(file, OWRITE);
if(fd < 0){
fd = create(file, OWRITE, DMAPPEND|0666);
if(fd < 0)
sysfatal("creating address list %s: %r", file);
nulldir(&nd);
nd.mode = DMAPPEND|0666;
dirwstat(file, &nd);
} else
seek(fd, 0, 2);
if(rem)
fprint(fd, "!%s\n", addr);
else
fprint(fd, "%s\n", addr);
close(fd);
if(*addr != '#')
sendnotification(addr, listname, rem);
}
void
remaddr(char *addr)
{
Addr **l;
Addr *a;
for(l = &al; *l; l = &(*l)->next){
a = *l;
if(strcmp(addr, a->addr) == 0){
(*l) = a->next;
free(a);
na--;
break;
}
}
}
int
addaddr(char *addr)
{
Addr **l;
Addr *a;
for(l = &al; *l; l = &(*l)->next){
if(strcmp(addr, (*l)->addr) == 0)
return 0;
}
na++;
*l = a = malloc(sizeof(*a)+strlen(addr)+1);
if(a == nil)
sysfatal("allocating: %r");
a->addr = (char*)&a[1];
strcpy(a->addr, addr);
a->next = nil;
*l = a;
return 1;
}
/* read address file */
void
readaddrs(char *file)
{
Biobuf *b;
char *p;
b = Bopen(file, OREAD);
if(b == nil)
return;
while((p = Brdline(b, '\n')) != nil){
p[Blinelen(b)-1] = 0;
if(*p == '#')
continue;
if(*p == '!')
remaddr(p+1);
else
addaddr(p);
}
Bterm(b);
}
/* start a mailer sending to all the receivers */
int
startmailer(char *name)
{
int pfd[2];
char **av;
int ac;
Addr *a;
putenv("upasname", "/dev/null");
if(pipe(pfd) < 0)
sysfatal("creating pipe: %r");
switch(fork()){
case -1:
sysfatal("starting mailer: %r");
case 0:
close(pfd[1]);
break;
default:
close(pfd[0]);
return pfd[1];
}
dup(pfd[0], 0);
close(pfd[0]);
av = malloc(sizeof(char*)*(na+2));
if(av == nil)
sysfatal("starting mailer: %r");
ac = 0;
av[ac++] = name;
for(a = al; a != nil; a = a->next)
av[ac++] = a->addr;
av[ac] = 0;
exec("/bin/upas/send", av);
sysfatal("execing mailer: %r");
/* not reached */
return -1;
}
void
sendnotification(char *addr, char *listname, int rem)
{
int pfd[2];
Waitmsg *w;
putenv("upasname", "/dev/null");
if(pipe(pfd) < 0)
sysfatal("creating pipe: %r");
switch(fork()){
case -1:
sysfatal("starting mailer: %r");
case 0:
close(pfd[1]);
dup(pfd[0], 0);
close(pfd[0]);
execl("/bin/upas/send", "mlnotify", addr, nil);
sysfatal("execing mailer: %r");
break;
default:
close(pfd[0]);
fprint(pfd[1], "From: %s-owner\n\n", listname);
if(rem)
fprint(pfd[1], "You have removed from the %s mailing list\n", listname);
else{
fprint(pfd[1], "You have been added to the %s mailing list\n", listname);
fprint(pfd[1], "To be removed, send an email to %s-owner containing\n",
listname);
fprint(pfd[1], "the word 'remove' in the subject or body.\n");
}
close(pfd[1]);
/* wait for mailer to end */
while(w = wait()){
if(w->msg != nil && w->msg[0])
sysfatal("%s", w->msg);
free(w);
}
break;
}
}

25
src/cmd/upas/ml/dat.h Normal file
View File

@@ -0,0 +1,25 @@
#include "../smtp/smtp.h"
#include "../smtp/y.tab.h"
typedef struct Addr Addr;
struct Addr
{
char *addr;
Addr *next;
};
String *from;
String *sender;
Field *firstfield;
int na;
Addr *al;
extern String* getaddr(Node *p);
extern void getaddrs(void);
extern void writeaddr(char *file, char *addr, int, char *);
extern void remaddr(char *addr);
extern int addaddr(char *addr);
extern void readaddrs(char *file);
extern int startmailer(char *name);
extern void sendnotification(char *addr, char *listname, int rem);

40
src/cmd/upas/ml/mkfile Normal file
View File

@@ -0,0 +1,40 @@
</$objtype/mkfile
TARG=ml\
mlowner\
mlmgr\
OFILES=\
common.$O\
LIB=../common/libcommon.av\
UHFILES= ../common/common.h\
../common/sys.h\
dat.h\
HFILES=$UHFILES\
../smtp/y.tab.h\
LIB=../common/libcommon.a$O\
BIN=/$objtype/bin/upas
UPDATE=\
mkfile\
$UHFILES\
${TARG:%=%.c}\
${OFILES:%.$O=%.c}\
../smtp/rfc822.y\
</sys/src/cmd/mkmany
CFLAGS=$CFLAGS -I../common
$O.ml: ../smtp/rfc822.tab.$O
$O.mlowner: ../smtp/rfc822.tab.$O
../smtp/y.tab.h ../smtp/rfc822.tab.$O:
@{
cd ../smtp
mk rfc822.tab.$O
}

167
src/cmd/upas/ml/ml.c Normal file
View File

@@ -0,0 +1,167 @@
#include "common.h"
#include "dat.h"
Biobuf in;
Addr *al;
int na;
String *from;
String *sender;
void printmsg(int fd, String *msg, char *replyto, char *listname);
void appendtoarchive(char* listname, String *firstline, String *msg);
void printsubject(int fd, Field *f, char *listname);
void
usage(void)
{
fprint(2, "usage: %s address-list-file listname\n", argv0);
exits("usage");
}
void
main(int argc, char **argv)
{
String *msg;
String *firstline;
char *listname, *alfile;
Waitmsg *w;
int fd;
char *replytoname = nil;
ARGBEGIN{
case 'r':
replytoname = ARGF();
break;
}ARGEND;
rfork(RFENVG|RFREND);
if(argc < 2)
usage();
alfile = argv[0];
listname = argv[1];
if(replytoname == nil)
replytoname = listname;
readaddrs(alfile);
if(Binit(&in, 0, OREAD) < 0)
sysfatal("opening input: %r");
msg = s_new();
firstline = s_new();
/* discard the 'From ' line */
if(s_read_line(&in, firstline) == nil)
sysfatal("reading input: %r");
/* read up to the first 128k of the message. more is redculous.
Not if word documents are distributed. Upped it to 2MB (pb) */
if(s_read(&in, msg, 2*1024*1024) <= 0)
sysfatal("reading input: %r");
/* parse the header */
yyinit(s_to_c(msg), s_len(msg));
yyparse();
/* get the sender */
getaddrs();
if(from == nil)
from = sender;
if(from == nil)
sysfatal("message must contain From: or Sender:");
if(strcmp(listname, s_to_c(from)) == 0)
sysfatal("can't remail messages from myself");
addaddr(s_to_c(from));
/* start the mailer up and return a pipe to it */
fd = startmailer(listname);
/* send message adding our own reply-to and precedence */
printmsg(fd, msg, replytoname, listname);
close(fd);
/* wait for mailer to end */
while(w = wait()){
if(w->msg != nil && w->msg[0])
sysfatal("%s", w->msg);
free(w);
}
/* if the mailbox exits, cat the mail to the end of it */
appendtoarchive(listname, firstline, msg);
exits(0);
}
/* send message filtering Reply-to out of messages */
void
printmsg(int fd, String *msg, char *replyto, char *listname)
{
Field *f, *subject;
Node *p;
char *cp, *ocp;
subject = nil;
cp = s_to_c(msg);
for(f = firstfield; f; f = f->next){
ocp = cp;
for(p = f->node; p; p = p->next)
cp = p->end+1;
if(f->node->c == REPLY_TO)
continue;
if(f->node->c == PRECEDENCE)
continue;
if(f->node->c == SUBJECT){
subject = f;
continue;
}
write(fd, ocp, cp-ocp);
}
printsubject(fd, subject, listname);
fprint(fd, "Reply-To: %s\nPrecedence: bulk\n", replyto);
write(fd, cp, s_len(msg) - (cp - s_to_c(msg)));
}
/* if the mailbox exits, cat the mail to the end of it */
void
appendtoarchive(char* listname, String *firstline, String *msg)
{
String *mbox;
int fd;
mbox = s_new();
mboxpath("mbox", listname, mbox, 0);
if(access(s_to_c(mbox), 0) < 0)
return;
fd = open(s_to_c(mbox), OWRITE);
if(fd < 0)
return;
s_append(msg, "\n");
write(fd, s_to_c(firstline), s_len(firstline));
write(fd, s_to_c(msg), s_len(msg));
}
/* add the listname to the subject */
void
printsubject(int fd, Field *f, char *listname)
{
char *s, *e;
Node *p;
char *ln;
if(f == nil || f->node == nil){
fprint(fd, "Subject: [%s]\n", listname);
return;
}
s = e = f->node->end + 1;
for(p = f->node; p; p = p->next)
e = p->end;
*e = 0;
ln = smprint("[%s]", listname);
if(ln != nil && strstr(s, ln) == nil)
fprint(fd, "Subject: %s%s\n", ln, s);
else
fprint(fd, "Subject:%s\n", s);
free(ln);
}

110
src/cmd/upas/ml/mlmgr.c Normal file
View File

@@ -0,0 +1,110 @@
#include "common.h"
#include "dat.h"
int cflag;
int aflag;
int rflag;
int createpipeto(char *alfile, char *user, char *listname, int owner);
void
usage(void)
{
fprint(2, "usage:\t%s -c listname\n", argv0);
fprint(2, "\t%s -[ar] listname addr\n", argv0);
exits("usage");
}
void
main(int argc, char **argv)
{
char *listname, *addr;
String *owner, *alfile;
rfork(RFENVG|RFREND);
ARGBEGIN{
case 'c':
cflag = 1;
break;
case 'r':
rflag = 1;
break;
case 'a':
aflag = 1;
break;
}ARGEND;
if(aflag + rflag + cflag > 1){
fprint(2, "%s: -a, -r, and -c are mutually exclusive\n", argv0);
exits("usage");
}
if(argc < 1)
usage();
listname = argv[0];
alfile = s_new();
mboxpath("address-list", listname, alfile, 0);
if(cflag){
owner = s_copy(listname);
s_append(owner, "-owner");
if(creatembox(listname, nil) < 0)
sysfatal("creating %s's mbox: %r", listname);
if(creatembox(s_to_c(owner), nil) < 0)
sysfatal("creating %s's mbox: %r", s_to_c(owner));
if(createpipeto(s_to_c(alfile), listname, listname, 0) < 0)
sysfatal("creating %s's pipeto: %r", s_to_c(owner));
if(createpipeto(s_to_c(alfile), s_to_c(owner), listname, 1) < 0)
sysfatal("creating %s's pipeto: %r", s_to_c(owner));
writeaddr(s_to_c(alfile), "# mlmgr c flag", 0, listname);
} else if(rflag){
if(argc != 2)
usage();
addr = argv[1];
writeaddr(s_to_c(alfile), "# mlmgr r flag", 0, listname);
writeaddr(s_to_c(alfile), addr, 1, listname);
} else if(aflag){
if(argc != 2)
usage();
addr = argv[1];
writeaddr(s_to_c(alfile), "# mlmgr a flag", 0, listname);
writeaddr(s_to_c(alfile), addr, 0, listname);
} else
usage();
exits(0);
}
int
createpipeto(char *alfile, char *user, char *listname, int owner)
{
String *f;
int fd;
Dir *d;
f = s_new();
mboxpath("pipeto", user, f, 0);
fprint(2, "creating new pipeto: %s\n", s_to_c(f));
fd = create(s_to_c(f), OWRITE, 0775);
if(fd < 0)
return -1;
d = dirfstat(fd);
if(d == nil){
fprint(fd, "Couldn't stat %s: %r\n", s_to_c(f));
return -1;
}
d->mode |= 0775;
if(dirfwstat(fd, d) < 0)
fprint(fd, "Couldn't wstat %s: %r\n", s_to_c(f));
free(d);
fprint(fd, "#!/bin/rc\n");
if(owner)
fprint(fd, "/bin/upas/mlowner %s %s\n", alfile, listname);
else
fprint(fd, "/bin/upas/ml %s %s\n", alfile, user);
close(fd);
return 0;
}

64
src/cmd/upas/ml/mlowner.c Normal file
View File

@@ -0,0 +1,64 @@
#include "common.h"
#include "dat.h"
Biobuf in;
String *from;
String *sender;
void
usage(void)
{
fprint(2, "usage: %s address-list-file listname\n", argv0);
exits("usage");
}
void
main(int argc, char **argv)
{
String *msg;
char *alfile;
char *listname;
ARGBEGIN{
}ARGEND;
rfork(RFENVG|RFREND);
if(argc < 2)
usage();
alfile = argv[0];
listname = argv[1];
if(Binit(&in, 0, OREAD) < 0)
sysfatal("opening input: %r");
msg = s_new();
/* discard the 'From ' line */
if(s_read_line(&in, msg) == nil)
sysfatal("reading input: %r");
/* read up to the first 128k of the message. more is redculous */
if(s_read(&in, s_restart(msg), 128*1024) <= 0)
sysfatal("reading input: %r");
/* parse the header */
yyinit(s_to_c(msg), s_len(msg));
yyparse();
/* get the sender */
getaddrs();
if(from == nil)
from = sender;
if(from == nil)
sysfatal("message must contain From: or Sender:");
if(strstr(s_to_c(msg), "remove")||strstr(s_to_c(msg), "unsubscribe"))
writeaddr(alfile, s_to_c(from), 1, listname);
else if(strstr(s_to_c(msg), "subscribe"))
writeaddr(alfile, s_to_c(from), 0, listname);
exits(0);
}

20
src/cmd/upas/ned/mkfile Normal file
View File

@@ -0,0 +1,20 @@
<$PLAN9/src/mkhdr
TARG=nedmail
LIB=../common/libcommon.a\
HFILES= ../common/common.h\
OFILES=nedmail.$O
BIN=$PLAN9/bin/upas
UPDATE=\
mkfile\
${OFILES:%.$O=%.c}\
$HFILES\
<$PLAN9/src/mkone
CFLAGS=$CFLAGS -I../common

2586
src/cmd/upas/ned/nedmail.c Normal file

File diff suppressed because it is too large Load Diff

16
src/cmd/upas/pop3/mkfile Normal file
View File

@@ -0,0 +1,16 @@
</$objtype/mkfile
TARG=pop3
OFILES=pop3.$O
BIN=/$objtype/bin/upas
LIB=../common/libcommon.a$O
UPDATE=\
mkfile\
${OFILES:%.$O=%.c}\
</sys/src/cmd/mkone
CFLAGS=$CFLAGS -I../common

804
src/cmd/upas/pop3/pop3.c Normal file
View File

@@ -0,0 +1,804 @@
#include "common.h"
#include <ctype.h>
#include <auth.h>
#include <libsec.h>
typedef struct Cmd Cmd;
struct Cmd
{
char *name;
int needauth;
int (*f)(char*);
};
static void hello(void);
static int apopcmd(char*);
static int capacmd(char*);
static int delecmd(char*);
static int listcmd(char*);
static int noopcmd(char*);
static int passcmd(char*);
static int quitcmd(char*);
static int rsetcmd(char*);
static int retrcmd(char*);
static int statcmd(char*);
static int stlscmd(char*);
static int topcmd(char*);
static int synccmd(char*);
static int uidlcmd(char*);
static int usercmd(char*);
static char *nextarg(char*);
static int getcrnl(char*, int);
static int readmbox(char*);
static void sendcrnl(char*, ...);
static int senderr(char*, ...);
static int sendok(char*, ...);
#pragma varargck argpos sendcrnl 1
#pragma varargck argpos senderr 1
#pragma varargck argpos sendok 1
Cmd cmdtab[] =
{
"apop", 0, apopcmd,
"capa", 0, capacmd,
"dele", 1, delecmd,
"list", 1, listcmd,
"noop", 0, noopcmd,
"pass", 0, passcmd,
"quit", 0, quitcmd,
"rset", 0, rsetcmd,
"retr", 1, retrcmd,
"stat", 1, statcmd,
"stls", 0, stlscmd,
"sync", 1, synccmd,
"top", 1, topcmd,
"uidl", 1, uidlcmd,
"user", 0, usercmd,
0, 0, 0,
};
static Biobuf in;
static Biobuf out;
static int passwordinclear;
static int didtls;
typedef struct Msg Msg;
struct Msg
{
int upasnum;
char digest[64];
int bytes;
int deleted;
};
static int totalbytes;
static int totalmsgs;
static Msg *msg;
static int nmsg;
static int loggedin;
static int debug;
static uchar *tlscert;
static int ntlscert;
static char *peeraddr;
static char tmpaddr[64];
void
usage(void)
{
fprint(2, "usage: upas/pop3 [-a authmboxfile] [-d debugfile] [-p]\n");
exits("usage");
}
void
main(int argc, char **argv)
{
int fd;
char *arg, cmdbuf[1024];
Cmd *c;
rfork(RFNAMEG);
Binit(&in, 0, OREAD);
Binit(&out, 1, OWRITE);
ARGBEGIN{
case 'a':
loggedin = 1;
if(readmbox(EARGF(usage())) < 0)
exits(nil);
break;
case 'd':
debug++;
if((fd = create(EARGF(usage()), OWRITE, 0666)) >= 0 && fd != 2){
dup(fd, 2);
close(fd);
}
break;
case 'r':
strecpy(tmpaddr, tmpaddr+sizeof tmpaddr, EARGF(usage()));
if(arg = strchr(tmpaddr, '!'))
*arg = '\0';
peeraddr = tmpaddr;
break;
case 't':
tlscert = readcert(EARGF(usage()), &ntlscert);
if(tlscert == nil){
senderr("cannot read TLS certificate: %r");
exits(nil);
}
break;
case 'p':
passwordinclear = 1;
break;
}ARGEND
/* do before TLS */
if(peeraddr == nil)
peeraddr = remoteaddr(0,0);
hello();
while(Bflush(&out), getcrnl(cmdbuf, sizeof cmdbuf) > 0){
arg = nextarg(cmdbuf);
for(c=cmdtab; c->name; c++)
if(cistrcmp(c->name, cmdbuf) == 0)
break;
if(c->name == 0){
senderr("unknown command %s", cmdbuf);
continue;
}
if(c->needauth && !loggedin){
senderr("%s requires authentication", cmdbuf);
continue;
}
(*c->f)(arg);
}
exits(nil);
}
/* sort directories in increasing message number order */
static int
dircmp(void *a, void *b)
{
return atoi(((Dir*)a)->name) - atoi(((Dir*)b)->name);
}
static int
readmbox(char *box)
{
int fd, i, n, nd, lines, pid;
char buf[100], err[ERRMAX];
char *p;
Biobuf *b;
Dir *d, *draw;
Msg *m;
Waitmsg *w;
unmount(nil, "/mail/fs");
switch(pid = fork()){
case -1:
return senderr("can't fork to start upas/fs");
case 0:
close(0);
close(1);
open("/dev/null", OREAD);
open("/dev/null", OWRITE);
execl("/bin/upas/fs", "upas/fs", "-np", "-f", box, nil);
snprint(err, sizeof err, "upas/fs: %r");
_exits(err);
break;
default:
break;
}
if((w = wait()) == nil || w->pid != pid || w->msg[0] != '\0'){
if(w && w->pid==pid)
return senderr("%s", w->msg);
else
return senderr("can't initialize upas/fs");
}
free(w);
if(chdir("/mail/fs/mbox") < 0)
return senderr("can't initialize upas/fs: %r");
if((fd = open(".", OREAD)) < 0)
return senderr("cannot open /mail/fs/mbox: %r");
nd = dirreadall(fd, &d);
close(fd);
if(nd < 0)
return senderr("cannot read from /mail/fs/mbox: %r");
msg = mallocz(sizeof(Msg)*nd, 1);
if(msg == nil)
return senderr("out of memory");
if(nd == 0)
return 0;
qsort(d, nd, sizeof(d[0]), dircmp);
for(i=0; i<nd; i++){
m = &msg[nmsg];
m->upasnum = atoi(d[i].name);
sprint(buf, "%d/digest", m->upasnum);
if((fd = open(buf, OREAD)) < 0)
continue;
n = readn(fd, m->digest, sizeof m->digest - 1);
close(fd);
if(n < 0)
continue;
m->digest[n] = '\0';
/*
* We need the number of message lines so that we
* can adjust the byte count to include \r's.
* Upas/fs gives us the number of lines in the raw body
* in the lines file, but we have to count rawheader ourselves.
* There is one blank line between raw header and raw body.
*/
sprint(buf, "%d/rawheader", m->upasnum);
if((b = Bopen(buf, OREAD)) == nil)
continue;
lines = 0;
for(;;){
p = Brdline(b, '\n');
if(p == nil){
if((n = Blinelen(b)) == 0)
break;
Bseek(b, n, 1);
}else
lines++;
}
Bterm(b);
lines++;
sprint(buf, "%d/lines", m->upasnum);
if((fd = open(buf, OREAD)) < 0)
continue;
n = readn(fd, buf, sizeof buf - 1);
close(fd);
if(n < 0)
continue;
buf[n] = '\0';
lines += atoi(buf);
sprint(buf, "%d/raw", m->upasnum);
if((draw = dirstat(buf)) == nil)
continue;
m->bytes = lines+draw->length;
free(draw);
nmsg++;
totalmsgs++;
totalbytes += m->bytes;
}
return 0;
}
/*
* get a line that ends in crnl or cr, turn terminating crnl into a nl
*
* return 0 on EOF
*/
static int
getcrnl(char *buf, int n)
{
int c;
char *ep;
char *bp;
Biobuf *fp = &in;
Bflush(&out);
bp = buf;
ep = bp + n - 1;
while(bp != ep){
c = Bgetc(fp);
if(debug) {
seek(2, 0, 2);
fprint(2, "%c", c);
}
switch(c){
case -1:
*bp = 0;
if(bp==buf)
return 0;
else
return bp-buf;
case '\r':
c = Bgetc(fp);
if(c == '\n'){
if(debug) {
seek(2, 0, 2);
fprint(2, "%c", c);
}
*bp = 0;
return bp-buf;
}
Bungetc(fp);
c = '\r';
break;
case '\n':
*bp = 0;
return bp-buf;
}
*bp++ = c;
}
*bp = 0;
return bp-buf;
}
static void
sendcrnl(char *fmt, ...)
{
char buf[1024];
va_list arg;
va_start(arg, fmt);
vseprint(buf, buf+sizeof(buf), fmt, arg);
va_end(arg);
if(debug)
fprint(2, "-> %s\n", buf);
Bprint(&out, "%s\r\n", buf);
}
static int
senderr(char *fmt, ...)
{
char buf[1024];
va_list arg;
va_start(arg, fmt);
vseprint(buf, buf+sizeof(buf), fmt, arg);
va_end(arg);
if(debug)
fprint(2, "-> -ERR %s\n", buf);
Bprint(&out, "-ERR %s\r\n", buf);
return -1;
}
static int
sendok(char *fmt, ...)
{
char buf[1024];
va_list arg;
va_start(arg, fmt);
vseprint(buf, buf+sizeof(buf), fmt, arg);
va_end(arg);
if(*buf){
if(debug)
fprint(2, "-> +OK %s\n", buf);
Bprint(&out, "+OK %s\r\n", buf);
} else {
if(debug)
fprint(2, "-> +OK\n");
Bprint(&out, "+OK\r\n");
}
return 0;
}
static int
capacmd(char*)
{
sendok("");
sendcrnl("TOP");
if(passwordinclear || didtls)
sendcrnl("USER");
sendcrnl("PIPELINING");
sendcrnl("UIDL");
sendcrnl("STLS");
sendcrnl(".");
return 0;
}
static int
delecmd(char *arg)
{
int n;
if(*arg==0)
return senderr("DELE requires a message number");
n = atoi(arg)-1;
if(n < 0 || n >= nmsg || msg[n].deleted)
return senderr("no such message");
msg[n].deleted = 1;
totalmsgs--;
totalbytes -= msg[n].bytes;
sendok("message %d deleted", n+1);
return 0;
}
static int
listcmd(char *arg)
{
int i, n;
if(*arg == 0){
sendok("+%d message%s (%d octets)", totalmsgs, totalmsgs==1 ? "":"s", totalbytes);
for(i=0; i<nmsg; i++){
if(msg[i].deleted)
continue;
sendcrnl("%d %d", i+1, msg[i].bytes);
}
sendcrnl(".");
}else{
n = atoi(arg)-1;
if(n < 0 || n >= nmsg || msg[n].deleted)
return senderr("no such message");
sendok("%d %d", n+1, msg[n].bytes);
}
return 0;
}
static int
noopcmd(char *arg)
{
USED(arg);
sendok("");
return 0;
}
static void
_synccmd(char*)
{
int i, fd;
char *s;
Fmt f;
if(!loggedin){
sendok("");
return;
}
fmtstrinit(&f);
fmtprint(&f, "delete mbox");
for(i=0; i<nmsg; i++)
if(msg[i].deleted)
fmtprint(&f, " %d", msg[i].upasnum);
s = fmtstrflush(&f);
if(strcmp(s, "delete mbox") != 0){ /* must have something to delete */
if((fd = open("../ctl", OWRITE)) < 0){
senderr("open ctl to delete messages: %r");
return;
}
if(write(fd, s, strlen(s)) < 0){
senderr("error deleting messages: %r");
return;
}
}
sendok("");
}
static int
synccmd(char*)
{
_synccmd(nil);
return 0;
}
static int
quitcmd(char*)
{
synccmd(nil);
exits(nil);
return 0;
}
static int
retrcmd(char *arg)
{
int n;
Biobuf *b;
char buf[40], *p;
if(*arg == 0)
return senderr("RETR requires a message number");
n = atoi(arg)-1;
if(n < 0 || n >= nmsg || msg[n].deleted)
return senderr("no such message");
snprint(buf, sizeof buf, "%d/raw", msg[n].upasnum);
if((b = Bopen(buf, OREAD)) == nil)
return senderr("message disappeared");
sendok("");
while((p = Brdstr(b, '\n', 1)) != nil){
if(p[0]=='.')
Bwrite(&out, ".", 1);
Bwrite(&out, p, strlen(p));
Bwrite(&out, "\r\n", 2);
free(p);
}
Bterm(b);
sendcrnl(".");
return 0;
}
static int
rsetcmd(char*)
{
int i;
for(i=0; i<nmsg; i++){
if(msg[i].deleted){
msg[i].deleted = 0;
totalmsgs++;
totalbytes += msg[i].bytes;
}
}
return sendok("");
}
static int
statcmd(char*)
{
return sendok("%d %d", totalmsgs, totalbytes);
}
static int
trace(char *fmt, ...)
{
va_list arg;
int n;
va_start(arg, fmt);
n = vfprint(2, fmt, arg);
va_end(arg);
return n;
}
static int
stlscmd(char*)
{
int fd;
TLSconn conn;
if(didtls)
return senderr("tls already started");
if(!tlscert)
return senderr("don't have any tls credentials");
sendok("");
Bflush(&out);
memset(&conn, 0, sizeof conn);
conn.cert = tlscert;
conn.certlen = ntlscert;
if(debug)
conn.trace = trace;
fd = tlsServer(0, &conn);
if(fd < 0)
sysfatal("tlsServer: %r");
dup(fd, 0);
dup(fd, 1);
close(fd);
Binit(&in, 0, OREAD);
Binit(&out, 1, OWRITE);
didtls = 1;
return 0;
}
static int
topcmd(char *arg)
{
int done, i, lines, n;
char buf[40], *p;
Biobuf *b;
if(*arg == 0)
return senderr("TOP requires a message number");
n = atoi(arg)-1;
if(n < 0 || n >= nmsg || msg[n].deleted)
return senderr("no such message");
arg = nextarg(arg);
if(*arg == 0)
return senderr("TOP requires a line count");
lines = atoi(arg);
if(lines < 0)
return senderr("bad args to TOP");
snprint(buf, sizeof buf, "%d/raw", msg[n].upasnum);
if((b = Bopen(buf, OREAD)) == nil)
return senderr("message disappeared");
sendok("");
while(p = Brdstr(b, '\n', 1)){
if(p[0]=='.')
Bputc(&out, '.');
Bwrite(&out, p, strlen(p));
Bwrite(&out, "\r\n", 2);
done = p[0]=='\0';
free(p);
if(done)
break;
}
for(i=0; i<lines; i++){
p = Brdstr(b, '\n', 1);
if(p == nil)
break;
if(p[0]=='.')
Bwrite(&out, ".", 1);
Bwrite(&out, p, strlen(p));
Bwrite(&out, "\r\n", 2);
free(p);
}
sendcrnl(".");
Bterm(b);
return 0;
}
static int
uidlcmd(char *arg)
{
int n;
if(*arg==0){
sendok("");
for(n=0; n<nmsg; n++){
if(msg[n].deleted)
continue;
sendcrnl("%d %s", n+1, msg[n].digest);
}
sendcrnl(".");
}else{
n = atoi(arg)-1;
if(n < 0 || n >= nmsg || msg[n].deleted)
return senderr("no such message");
sendok("%d %s", n+1, msg[n].digest);
}
return 0;
}
static char*
nextarg(char *p)
{
while(*p && *p != ' ' && *p != '\t')
p++;
while(*p == ' ' || *p == '\t')
*p++ = 0;
return p;
}
/*
* authentication
*/
Chalstate *chs;
char user[256];
char box[256];
char cbox[256];
static void
hello(void)
{
fmtinstall('H', encodefmt);
if((chs = auth_challenge("proto=apop role=server")) == nil){
senderr("auth server not responding, try later");
exits(nil);
}
sendok("POP3 server ready %s", chs->chal);
}
static int
setuser(char *arg)
{
char *p;
strcpy(box, "/mail/box/");
strecpy(box+strlen(box), box+sizeof box-7, arg);
strcpy(cbox, box);
cleanname(cbox);
if(strcmp(cbox, box) != 0)
return senderr("bad mailbox name");
strcat(box, "/mbox");
strecpy(user, user+sizeof user, arg);
if(p = strchr(user, '/'))
*p = '\0';
return 0;
}
static int
usercmd(char *arg)
{
if(loggedin)
return senderr("already authenticated");
if(*arg == 0)
return senderr("USER requires argument");
if(setuser(arg) < 0)
return -1;
return sendok("");
}
static void
enableaddr(void)
{
int fd;
char buf[64];
/* hide the peer IP address under a rock in the ratifier FS */
if(peeraddr == 0 || *peeraddr == 0)
return;
sprint(buf, "/mail/ratify/trusted/%s#32", peeraddr);
/*
* if the address is already there and the user owns it,
* remove it and recreate it to give him a new time quanta.
*/
if(access(buf, 0) >= 0 && remove(buf) < 0)
return;
fd = create(buf, OREAD, 0666);
if(fd >= 0){
close(fd);
// syslog(0, "pop3", "ratified %s", peeraddr);
}
}
static int
dologin(char *response)
{
AuthInfo *ai;
static int tries;
chs->user = user;
chs->resp = response;
chs->nresp = strlen(response);
if((ai = auth_response(chs)) == nil){
if(tries++ >= 5){
senderr("authentication failed: %r; server exiting");
exits(nil);
}
return senderr("authentication failed");
}
if(auth_chuid(ai, nil) < 0){
senderr("chuid failed: %r; server exiting");
exits(nil);
}
auth_freeAI(ai);
auth_freechal(chs);
chs = nil;
loggedin = 1;
if(newns(user, 0) < 0){
senderr("newns failed: %r; server exiting");
exits(nil);
}
enableaddr();
if(readmbox(box) < 0)
exits(nil);
return sendok("mailbox is %s", box);
}
static int
passcmd(char *arg)
{
DigestState *s;
uchar digest[MD5dlen];
char response[2*MD5dlen+1];
if(passwordinclear==0 && didtls==0)
return senderr("password in the clear disallowed");
/* use password to encode challenge */
if((chs = auth_challenge("proto=apop role=server")) == nil)
return senderr("couldn't get apop challenge");
// hash challenge with secret and convert to ascii
s = md5((uchar*)chs->chal, chs->nchal, 0, 0);
md5((uchar*)arg, strlen(arg), digest, s);
snprint(response, sizeof response, "%.*H", MD5dlen, digest);
return dologin(response);
}
static int
apopcmd(char *arg)
{
char *resp;
resp = nextarg(arg);
if(setuser(arg) < 0)
return -1;
return dologin(resp);
}

22
src/cmd/upas/q/mkfile Normal file
View File

@@ -0,0 +1,22 @@
<$PLAN9/src/mkhdr
TARG = qer\
runq\
OFILES=
HFILES=../common/common.h\
../common/sys.h\
LIB=../common/libcommon.a\
BIN=$PLAN9/bin/upas
UPDATE=\
mkfile\
$HFILES\
${OFILES:%.$O=%.c}\
${TARG:%=%.c}\
<$PLAN9/src/mkmany
CFLAGS=$CFLAGS -I../common

193
src/cmd/upas/q/qer.c Normal file
View File

@@ -0,0 +1,193 @@
#include "common.h"
typedef struct Qfile Qfile;
struct Qfile
{
Qfile *next;
char *name;
char *tname;
} *files;
char *user;
int isnone;
int copy(Qfile*);
void
usage(void)
{
fprint(2, "usage: qer [-f file] [-q dir] q-root description reply-to arg-list\n");
exits("usage");
}
void
error(char *f, char *a)
{
char err[Errlen+1];
char buf[256];
rerrstr(err, sizeof(err));
snprint(buf, sizeof(buf), f, a);
fprint(2, "qer: %s: %s\n", buf, err);
exits(buf);
}
void
main(int argc, char**argv)
{
Dir *dir;
String *f, *c;
int fd;
char file[1024];
char buf[1024];
long n;
char *cp, *qdir;
int i;
Qfile *q, **l;
l = &files;
qdir = 0;
ARGBEGIN {
case 'f':
q = malloc(sizeof(Qfile));
q->name = ARGF();
q->next = *l;
*l = q;
break;
case 'q':
qdir = ARGF();
if(qdir == 0)
usage();
break;
default:
usage();
} ARGEND;
if(argc < 3)
usage();
user = getuser();
isnone = (qdir != 0) || (strcmp(user, "none") == 0);
if(qdir == 0) {
qdir = user;
if(qdir == 0)
error("unknown user", 0);
}
snprint(file, sizeof(file), "%s/%s", argv[0], qdir);
/*
* data file name
*/
f = s_copy(file);
s_append(f, "/D.XXXXXX");
mktemp(s_to_c(f));
cp = utfrrune(s_to_c(f), '/');
cp++;
/*
* create directory and data file. once the data file
* exists, runq won't remove the directory
*/
fd = -1;
for(i = 0; i < 10; i++){
int perm;
dir = dirstat(file);
if(dir == nil){
perm = isnone?0777:0775;
if(sysmkdir(file, perm) < 0)
continue;
} else {
if((dir->qid.type&QTDIR)==0)
error("not a directory %s", file);
}
perm = isnone?0664:0660;
fd = create(s_to_c(f), OWRITE, perm);
if(fd >= 0)
break;
sleep(250);
}
if(fd < 0)
error("creating data file %s", s_to_c(f));
/*
* copy over associated files
*/
if(files){
*cp = 'F';
for(q = files; q; q = q->next){
q->tname = strdup(s_to_c(f));
if(copy(q) < 0)
error("copying %s to queue", q->name);
(*cp)++;
}
}
/*
* copy in the data file
*/
i = 0;
while((n = read(0, buf, sizeof(buf)-1)) > 0){
if(i++ == 0 && strncmp(buf, "From", 4) != 0){
buf[n] = 0;
syslog(0, "smtp", "qer usys data starts with %-40.40s\n", buf);
}
if(write(fd, buf, n) != n)
error("writing data file %s", s_to_c(f));
}
/* if(n < 0)
error("reading input"); */
close(fd);
/*
* create control file
*/
*cp = 'C';
fd = syscreatelocked(s_to_c(f), OWRITE, 0664);
if(fd < 0)
error("creating control file %s", s_to_c(f));
c = s_new();
for(i = 1; i < argc; i++){
s_append(c, argv[i]);
s_append(c, " ");
}
for(q = files; q; q = q->next){
s_append(c, q->tname);
s_append(c, " ");
}
s_append(c, "\n");
if(write(fd, s_to_c(c), strlen(s_to_c(c))) < 0) {
sysunlockfile(fd);
error("writing control file %s", s_to_c(f));
}
sysunlockfile(fd);
exits(0);
}
int
copy(Qfile *q)
{
int from, to, n;
char buf[4096];
from = open(q->name, OREAD);
if(from < 0)
return -1;
to = create(q->tname, OWRITE, 0660);
if(to < 0){
close(from);
return -1;
}
for(;;){
n = read(from, buf, sizeof(buf));
if(n <= 0)
break;
n = write(to, buf, n);
if(n < 0)
break;
}
close(to);
close(from);
return n;
}

766
src/cmd/upas/q/runq.c Normal file
View File

@@ -0,0 +1,766 @@
#include "common.h"
#include <ctype.h>
void doalldirs(void);
void dodir(char*);
void dofile(Dir*);
void rundir(char*);
char* file(char*, char);
void warning(char*, void*);
void error(char*, void*);
int returnmail(char**, char*, char*);
void logit(char*, char*, char**);
void doload(int);
#define HUNK 32
char *cmd;
char *root;
int debug;
int giveup = 2*24*60*60;
int load;
int limit;
/* the current directory */
Dir *dirbuf;
long ndirbuf = 0;
int nfiles;
char *curdir;
char *runqlog = "runq";
int *pidlist;
char **badsys; /* array of recalcitrant systems */
int nbad;
int npid = 50;
int sflag; /* single thread per directory */
int aflag; /* all directories */
int Eflag; /* ignore E.xxxxxx dates */
int Rflag; /* no giving up, ever */
void
usage(void)
{
fprint(2, "usage: runq [-adsE] [-q dir] [-l load] [-t time] [-r nfiles] [-n nprocs] q-root cmd\n");
exits("");
}
void
main(int argc, char **argv)
{
char *qdir, *x;
qdir = 0;
ARGBEGIN{
case 'l':
x = ARGF();
if(x == 0)
usage();
load = atoi(x);
if(load < 0)
load = 0;
break;
case 'E':
Eflag++;
break;
case 'R': /* no giving up -- just leave stuff in the queue */
Rflag++;
break;
case 'a':
aflag++;
break;
case 'd':
debug++;
break;
case 'r':
limit = atoi(ARGF());
break;
case 's':
sflag++;
break;
case 't':
giveup = 60*60*atoi(ARGF());
break;
case 'q':
qdir = ARGF();
if(qdir == 0)
usage();
break;
case 'n':
npid = atoi(ARGF());
if(npid == 0)
usage();
break;
}ARGEND;
if(argc != 2)
usage();
pidlist = malloc(npid*sizeof(*pidlist));
if(pidlist == 0)
error("can't malloc", 0);
if(aflag == 0 && qdir == 0) {
qdir = getuser();
if(qdir == 0)
error("unknown user", 0);
}
root = argv[0];
cmd = argv[1];
if(chdir(root) < 0)
error("can't cd to %s", root);
doload(1);
if(aflag)
doalldirs();
else
dodir(qdir);
doload(0);
exits(0);
}
int
emptydir(char *name)
{
int fd;
long n;
char buf[2048];
fd = open(name, OREAD);
if(fd < 0)
return 1;
n = read(fd, buf, sizeof(buf));
close(fd);
if(n <= 0) {
if(debug)
fprint(2, "removing directory %s\n", name);
syslog(0, runqlog, "rmdir %s", name);
sysremove(name);
return 1;
}
return 0;
}
int
forkltd(void)
{
int i;
int pid;
for(i = 0; i < npid; i++){
if(pidlist[i] <= 0)
break;
}
while(i >= npid){
pid = waitpid();
if(pid < 0){
syslog(0, runqlog, "forkltd confused");
exits(0);
}
for(i = 0; i < npid; i++)
if(pidlist[i] == pid)
break;
}
pidlist[i] = fork();
return pidlist[i];
}
/*
* run all user directories, must be bootes (or root on unix) to do this
*/
void
doalldirs(void)
{
Dir *db;
int fd;
long i, n;
fd = open(".", OREAD);
if(fd == -1){
warning("reading %s", root);
return;
}
n = sysdirreadall(fd, &db);
if(n > 0){
for(i=0; i<n; i++){
if(db[i].qid.type & QTDIR){
if(emptydir(db[i].name))
continue;
switch(forkltd()){
case -1:
syslog(0, runqlog, "out of procs");
doload(0);
exits(0);
case 0:
if(sysdetach() < 0)
error("%r", 0);
dodir(db[i].name);
exits(0);
default:
break;
}
}
}
free(db);
}
close(fd);
}
/*
* cd to a user directory and run it
*/
void
dodir(char *name)
{
curdir = name;
if(chdir(name) < 0){
warning("cd to %s", name);
return;
}
if(debug)
fprint(2, "running %s\n", name);
rundir(name);
chdir("..");
}
/*
* run the current directory
*/
void
rundir(char *name)
{
int fd;
long i;
if(aflag && sflag)
fd = sysopenlocked(".", OREAD);
else
fd = open(".", OREAD);
if(fd == -1){
warning("reading %s", name);
return;
}
nfiles = sysdirreadall(fd, &dirbuf);
if(nfiles > 0){
for(i=0; i<nfiles; i++){
if(dirbuf[i].name[0]!='C' || dirbuf[i].name[1]!='.')
continue;
dofile(&dirbuf[i]);
}
free(dirbuf);
}
if(aflag && sflag)
sysunlockfile(fd);
else
close(fd);
}
/*
* free files matching name in the current directory
*/
void
remmatch(char *name)
{
long i;
syslog(0, runqlog, "removing %s/%s", curdir, name);
for(i=0; i<nfiles; i++){
if(strcmp(&dirbuf[i].name[1], &name[1]) == 0)
sysremove(dirbuf[i].name);
}
/* error file (may have) appeared after we read the directory */
/* stomp on data file in case of phase error */
sysremove(file(name, 'D'));
sysremove(file(name, 'E'));
}
/*
* like trylock, but we've already got the lock on fd,
* and don't want an L. lock file.
*/
static Mlock *
keeplockalive(char *path, int fd)
{
char buf[1];
Mlock *l;
l = malloc(sizeof(Mlock));
if(l == 0)
return 0;
l->fd = fd;
l->name = s_new();
s_append(l->name, path);
/* fork process to keep lock alive until sysunlock(l) */
switch(l->pid = rfork(RFPROC)){
default:
break;
case 0:
fd = l->fd;
for(;;){
sleep(1000*60);
if(pread(fd, buf, 1, 0) < 0)
break;
}
_exits(0);
}
return l;
}
/*
* try a message
*/
void
dofile(Dir *dp)
{
Dir *d;
int dfd, ac, dtime, efd, pid, i, etime;
char *buf, *cp, **av;
Waitmsg *wm;
Biobuf *b;
Mlock *l = nil;
if(debug)
fprint(2, "dofile %s\n", dp->name);
/*
* if no data file or empty control or data file, just clean up
* the empty control file must be 15 minutes old, to minimize the
* chance of a race.
*/
d = dirstat(file(dp->name, 'D'));
if(d == nil){
syslog(0, runqlog, "no data file for %s", dp->name);
remmatch(dp->name);
return;
}
if(dp->length == 0){
if(time(0)-dp->mtime > 15*60){
syslog(0, runqlog, "empty ctl file for %s", dp->name);
remmatch(dp->name);
}
return;
}
dtime = d->mtime;
free(d);
/*
* retry times depend on the age of the errors file
*/
if(!Eflag && (d = dirstat(file(dp->name, 'E'))) != nil){
etime = d->mtime;
free(d);
if(etime - dtime < 60*60){
/* up to the first hour, try every 15 minutes */
if(time(0) - etime < 15*60)
return;
} else {
/* after the first hour, try once an hour */
if(time(0) - etime < 60*60)
return;
}
}
/*
* open control and data
*/
b = sysopen(file(dp->name, 'C'), "rl", 0660);
if(b == 0) {
if(debug)
fprint(2, "can't open %s: %r\n", file(dp->name, 'C'));
return;
}
dfd = open(file(dp->name, 'D'), OREAD);
if(dfd < 0){
if(debug)
fprint(2, "can't open %s: %r\n", file(dp->name, 'D'));
Bterm(b);
sysunlockfile(Bfildes(b));
return;
}
/*
* make arg list
* - read args into (malloc'd) buffer
* - malloc a vector and copy pointers to args into it
*/
buf = malloc(dp->length+1);
if(buf == 0){
warning("buffer allocation", 0);
Bterm(b);
sysunlockfile(Bfildes(b));
close(dfd);
return;
}
if(Bread(b, buf, dp->length) != dp->length){
warning("reading control file %s\n", dp->name);
Bterm(b);
sysunlockfile(Bfildes(b));
close(dfd);
free(buf);
return;
}
buf[dp->length] = 0;
av = malloc(2*sizeof(char*));
if(av == 0){
warning("argv allocation", 0);
close(dfd);
free(buf);
Bterm(b);
sysunlockfile(Bfildes(b));
return;
}
for(ac = 1, cp = buf; *cp; ac++){
while(isspace(*cp))
*cp++ = 0;
if(*cp == 0)
break;
av = realloc(av, (ac+2)*sizeof(char*));
if(av == 0){
warning("argv allocation", 0);
close(dfd);
free(buf);
Bterm(b);
sysunlockfile(Bfildes(b));
return;
}
av[ac] = cp;
while(*cp && !isspace(*cp)){
if(*cp++ == '"'){
while(*cp && *cp != '"')
cp++;
if(*cp)
cp++;
}
}
}
av[0] = cmd;
av[ac] = 0;
if(!Eflag &&time(0) - dtime > giveup){
if(returnmail(av, dp->name, "Giveup") != 0)
logit("returnmail failed", dp->name, av);
remmatch(dp->name);
goto done;
}
for(i = 0; i < nbad; i++){
if(strcmp(av[3], badsys[i]) == 0)
goto done;
}
/*
* Ken's fs, for example, gives us 5 minutes of inactivity before
* the lock goes stale, so we have to keep reading it.
*/
l = keeplockalive(file(dp->name, 'C'), Bfildes(b));
/*
* transfer
*/
pid = fork();
switch(pid){
case -1:
sysunlock(l);
sysunlockfile(Bfildes(b));
syslog(0, runqlog, "out of procs");
exits(0);
case 0:
if(debug) {
fprint(2, "Starting %s", cmd);
for(ac = 0; av[ac]; ac++)
fprint(2, " %s", av[ac]);
fprint(2, "\n");
}
logit("execing", dp->name, av);
close(0);
dup(dfd, 0);
close(dfd);
close(2);
efd = open(file(dp->name, 'E'), OWRITE);
if(efd < 0){
if(debug) syslog(0, "runq", "open %s as %s: %r", file(dp->name,'E'), getuser());
efd = create(file(dp->name, 'E'), OWRITE, 0666);
if(efd < 0){
if(debug) syslog(0, "runq", "create %s as %s: %r", file(dp->name, 'E'), getuser());
exits("could not open error file - Retry");
}
}
seek(efd, 0, 2);
exec(cmd, av);
error("can't exec %s", cmd);
break;
default:
for(;;){
wm = wait();
if(wm == nil)
error("wait failed: %r", "");
if(wm->pid == pid)
break;
free(wm);
}
if(debug)
fprint(2, "wm->pid %d wm->msg == %s\n", wm->pid, wm->msg);
if(wm->msg[0]){
if(debug)
fprint(2, "[%d] wm->msg == %s\n", getpid(), wm->msg);
if(!Rflag && strstr(wm->msg, "Retry")==0){
/* return the message and remove it */
if(returnmail(av, dp->name, wm->msg) != 0)
logit("returnmail failed", dp->name, av);
remmatch(dp->name);
} else {
/* add sys to bad list and try again later */
nbad++;
badsys = realloc(badsys, nbad*sizeof(char*));
badsys[nbad-1] = strdup(av[3]);
}
} else {
/* it worked remove the message */
remmatch(dp->name);
}
free(wm);
}
done:
if (l)
sysunlock(l);
Bterm(b);
sysunlockfile(Bfildes(b));
free(buf);
free(av);
close(dfd);
}
/*
* return a name starting with the given character
*/
char*
file(char *name, char type)
{
static char nname[Elemlen+1];
strncpy(nname, name, Elemlen);
nname[Elemlen] = 0;
nname[0] = type;
return nname;
}
/*
* send back the mail with an error message
*
* return 0 if successful
*/
int
returnmail(char **av, char *name, char *msg)
{
int pfd[2];
Waitmsg *wm;
int fd;
char buf[256];
char attachment[256];
int i;
long n;
String *s;
char *sender;
if(av[1] == 0 || av[2] == 0){
logit("runq - dumping bad file", name, av);
return 0;
}
s = unescapespecial(s_copy(av[2]));
sender = s_to_c(s);
if(!returnable(sender) || strcmp(sender, "postmaster") == 0) {
logit("runq - dumping p to p mail", name, av);
return 0;
}
if(pipe(pfd) < 0){
logit("runq - pipe failed", name, av);
return -1;
}
switch(rfork(RFFDG|RFPROC|RFENVG)){
case -1:
logit("runq - fork failed", name, av);
return -1;
case 0:
logit("returning", name, av);
close(pfd[1]);
close(0);
dup(pfd[0], 0);
close(pfd[0]);
putenv("upasname", "/dev/null");
snprint(buf, sizeof(buf), "%s/marshal", UPASBIN);
snprint(attachment, sizeof(attachment), "%s", file(name, 'D'));
execl(buf, "send", "-A", attachment, "-s", "permanent failure", sender, nil);
error("can't exec", 0);
break;
default:
break;
}
close(pfd[0]);
fprint(pfd[1], "\n"); /* get out of headers */
if(av[1]){
fprint(pfd[1], "Your request ``%.20s ", av[1]);
for(n = 3; av[n]; n++)
fprint(pfd[1], "%s ", av[n]);
}
fprint(pfd[1], "'' failed (code %s).\nThe symptom was:\n\n", msg);
fd = open(file(name, 'E'), OREAD);
if(fd >= 0){
for(;;){
n = read(fd, buf, sizeof(buf));
if(n <= 0)
break;
if(write(pfd[1], buf, n) != n){
close(fd);
goto out;
}
}
close(fd);
}
close(pfd[1]);
out:
wm = wait();
if(wm == nil){
syslog(0, "runq", "wait: %r");
logit("wait failed", name, av);
return -1;
}
i = 0;
if(wm->msg[0]){
i = -1;
syslog(0, "runq", "returnmail child: %s", wm->msg);
logit("returnmail child failed", name, av);
}
free(wm);
return i;
}
/*
* print a warning and continue
*/
void
warning(char *f, void *a)
{
char err[65];
char buf[256];
rerrstr(err, sizeof(err));
snprint(buf, sizeof(buf), f, a);
fprint(2, "runq: %s: %s\n", buf, err);
}
/*
* print an error and die
*/
void
error(char *f, void *a)
{
char err[Errlen];
char buf[256];
rerrstr(err, sizeof(err));
snprint(buf, sizeof(buf), f, a);
fprint(2, "runq: %s: %s\n", buf, err);
exits(buf);
}
void
logit(char *msg, char *file, char **av)
{
int n, m;
char buf[256];
n = snprint(buf, sizeof(buf), "%s/%s: %s", curdir, file, msg);
for(; *av; av++){
m = strlen(*av);
if(n + m + 4 > sizeof(buf))
break;
sprint(buf + n, " '%s'", *av);
n += m + 3;
}
syslog(0, runqlog, "%s", buf);
}
char *loadfile = ".runqload";
/*
* load balancing
*/
void
doload(int start)
{
int fd;
char buf[32];
int i, n;
Mlock *l;
Dir *d;
if(load <= 0)
return;
if(chdir(root) < 0){
load = 0;
return;
}
l = syslock(loadfile);
fd = open(loadfile, ORDWR);
if(fd < 0){
fd = create(loadfile, 0666, ORDWR);
if(fd < 0){
load = 0;
sysunlock(l);
return;
}
}
/* get current load */
i = 0;
n = read(fd, buf, sizeof(buf)-1);
if(n >= 0){
buf[n] = 0;
i = atoi(buf);
}
if(i < 0)
i = 0;
/* ignore load if file hasn't been changed in 30 minutes */
d = dirfstat(fd);
if(d != nil){
if(d->mtime + 30*60 < time(0))
i = 0;
free(d);
}
/* if load already too high, give up */
if(start && i >= load){
sysunlock(l);
exits(0);
}
/* increment/decrement load */
if(start)
i++;
else
i--;
seek(fd, 0, 0);
fprint(fd, "%d\n", i);
sysunlock(l);
close(fd);
}

View File

@@ -0,0 +1,667 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <regexp.h>
#include "spam.h"
enum {
Quanta = 8192,
Minbody = 6000,
HdrMax = 15,
};
typedef struct keyword Keyword;
typedef struct word Word;
struct word{
char *string;
int n;
};
struct keyword{
char *string;
int value;
};
Word htmlcmds[] =
{
"html", 4,
"!doctype html", 13,
0,
};
Word hrefs[] =
{
"a href=", 7,
"a title=", 8,
"a target=", 9,
"base href=", 10,
"img src=", 8,
"img border=", 11,
"form action=", 12,
"!--", 3,
0,
};
/*
* RFC822 header keywords to look for for fractured header.
* all lengths must be less than HdrMax defined above.
*/
Word hdrwords[] =
{
"cc:", 3,
"bcc:", 4,
"to:", 3,
0, 0,
};
Keyword keywords[] =
{
"header", HoldHeader,
"line", SaveLine,
"hold", Hold,
"dump", Dump,
"loff", Lineoff,
0, Nactions,
};
Patterns patterns[] = {
[Dump] { "DUMP:", 0, 0 },
[HoldHeader] { "HEADER:", 0, 0 },
[Hold] { "HOLD:", 0, 0 },
[SaveLine] { "LINE:", 0, 0 },
[Lineoff] { "LINEOFF:", 0, 0 },
[Nactions] { 0, 0, 0 },
};
static char* endofhdr(char*, char*);
static int escape(char**);
static int extract(char*);
static int findkey(char*);
static int hash(int);
static int isword(Word*, char*, int);
static void parsealt(Biobuf*, char*, Spat**);
/*
* The canonicalizer: convert input to canonical representation
*/
char*
readmsg(Biobuf *bp, int *hsize, int *bufsize)
{
char *p, *buf;
int n, offset, eoh, bsize, delta;
buf = 0;
offset = 0;
if(bufsize)
*bufsize = 0;
if(hsize)
*hsize = 0;
for(;;) {
buf = Realloc(buf, offset+Quanta+1);
n = Bread(bp, buf+offset, Quanta);
if(n < 0){
free(buf);
return 0;
}
p = buf+offset; /* start of this chunk */
offset += n; /* end of this chunk */
buf[offset] = 0;
if(n == 0){
if(offset == 0)
return 0;
break;
}
if(hsize == 0) /* don't process header */
break;
if(p != buf && p[-1] == '\n') /* check for EOH across buffer split */
p--;
p = endofhdr(p, buf+offset);
if(p)
break;
if(offset >= Maxread) /* gargantuan header - just punt*/
{
if(hsize)
*hsize = offset;
if(bufsize)
*bufsize = offset;
return buf;
}
}
eoh = p-buf; /* End of header */
bsize = offset - eoh; /* amount of body already read */
/* Read at least Minbody bytes of the body */
if (bsize < Minbody){
delta = Minbody-bsize;
buf = Realloc(buf, offset+delta+1);
n = Bread(bp, buf+offset, delta);
if(n > 0) {
offset += n;
buf[offset] = 0;
}
}
if(hsize)
*hsize = eoh;
if(bufsize)
*bufsize = offset;
return buf;
}
static int
isword(Word *wp, char *text, int len)
{
for(;wp->string; wp++)
if(len >= wp->n && strncmp(text, wp->string, wp->n) == 0)
return 1;
return 0;
}
static char*
endofhdr(char *raw, char *end)
{
int i;
char *p, *q;
char buf[HdrMax];
/*
* can't use strchr to search for newlines because
* there may be embedded NULL's.
*/
for(p = raw; p < end; p++){
if(*p != '\n' || p[1] != '\n')
continue;
p++;
for(i = 0, q = p+1; i < sizeof(buf) && *q; q++){
buf[i++] = tolower(*q);
if(*q == ':' || *q == '\n')
break;
}
if(!isword(hdrwords, buf, i))
return p+1;
}
return 0;
}
static int
htmlmatch(Word *wp, char *text, char *end, int *n)
{
char *cp;
int i, c, lastc;
char buf[MaxHtml];
/*
* extract a string up to '>'
*/
i = lastc = 0;
cp = text;
while (cp < end && i < sizeof(buf)-1){
c = *cp++;
if(c == '=')
c = escape(&cp);
switch(c){
case 0:
case '\r':
continue;
case '>':
goto out;
case '\n':
case ' ':
case '\t':
if(lastc == ' ')
continue;
c = ' ';
break;
default:
c = tolower(c);
break;
}
buf[i++] = lastc = c;
}
out:
buf[i] = 0;
if(n)
*n = cp-text;
return isword(wp, buf, i);
}
static int
escape(char **msg)
{
int c;
char *p;
p = *msg;
c = *p;
if(c == '\n'){
p++;
c = *p++;
} else
if(c == '2'){
c = tolower(p[1]);
if(c == 'e'){
p += 2;
c = '.';
}else
if(c == 'f'){
p += 2;
c = '/';
}else
if(c == '0'){
p += 2;
c = ' ';
}
else c = '=';
} else {
if(c == '3' && tolower(p[1]) == 'd')
p += 2;
c = '=';
}
*msg = p;
return c;
}
static int
htmlchk(char **msg, char *end)
{
int n;
char *p;
static int ishtml;
p = *msg;
if(ishtml == 0){
ishtml = htmlmatch(htmlcmds, p, end, &n);
/* If not an HTML keyword, check if it's
* an HTML comment (<!comment>). if so,
* skip over it; otherwise copy it in.
*/
if(ishtml == 0 && *p != '!') /* not comment */
return '<'; /* copy it */
} else if(htmlmatch(hrefs, p, end, &n)) /* if special HTML string */
return '<'; /* copy it */
/*
* this is an uninteresting HTML command; skip over it.
*/
p += n;
*msg = p+1;
return *p;
}
/*
* decode a base 64 encode body
*/
void
conv64(char *msg, char *end, char *buf, int bufsize)
{
int len, i;
char *cp;
len = end - msg;
i = (len*3)/4+1; // room for max chars + null
cp = Malloc(i);
len = dec64((uchar*)cp, i, msg, len);
convert(cp, cp+len, buf, bufsize, 1);
free(cp);
}
int
convert(char *msg, char *end, char *buf, int bufsize, int isbody)
{
char *p;
int c, lastc, base64;
lastc = 0;
base64 = 0;
while(msg < end && bufsize > 0){
c = *msg++;
/*
* In the body only, try to strip most HTML and
* replace certain MIME escape sequences with the character
*/
if(isbody) {
do{
p = msg;
if(c == '<')
c = htmlchk(&msg, end);
if(c == '=')
c = escape(&msg);
} while(p != msg && p < end);
}
switch(c){
case 0:
case '\r':
continue;
case '\t':
case ' ':
case '\n':
if(lastc == ' ')
continue;
c = ' ';
break;
case 'C': /* check for MIME base 64 encoding in header */
case 'c':
if(isbody == 0)
if(msg < end-32 && *msg == 'o' && msg[1] == 'n')
if(cistrncmp(msg+2, "tent-transfer-encoding: base64", 30) == 0)
base64 = 1;
c = 'c';
break;
default:
c = tolower(c);
break;
}
*buf++ = c;
lastc = c;
bufsize--;
}
*buf = 0;
return base64;
}
/*
* The pattern parser: build data structures from the pattern file
*/
static int
hash(int c)
{
return c & 127;
}
static int
findkey(char *val)
{
Keyword *kp;
for(kp = keywords; kp->string; kp++)
if(strcmp(val, kp->string) == 0)
break;
return kp->value;
}
#define whitespace(c) ((c) == ' ' || (c) == '\t')
void
parsepats(Biobuf *bp)
{
Pattern *p, *new;
char *cp, *qp;
int type, action, n, h;
Spat *spat;
for(;;){
cp = Brdline(bp, '\n');
if(cp == 0)
break;
cp[Blinelen(bp)-1] = 0;
while(*cp == ' ' || *cp == '\t')
cp++;
if(*cp == '#' || *cp == 0)
continue;
type = regexp;
if(*cp == '*'){
type = string;
cp++;
}
qp = strchr(cp, ':');
if(qp == 0)
continue;
*qp = 0;
if(debug)
fprint(2, "action = %s\n", cp);
action = findkey(cp);
if(action >= Nactions)
continue;
cp = qp+1;
n = extract(cp);
if(n <= 0 || *cp == 0)
continue;
qp = strstr(cp, "~~");
if(qp){
*qp = 0;
n = strlen(cp);
}
if(debug)
fprint(2, " Pattern: `%s'\n", cp);
/* Hook regexps into a chain */
if(type == regexp) {
new = Malloc(sizeof(Pattern));
new->action = action;
new->pat = regcomp(cp);
if(new->pat == 0){
free(new);
continue;
}
new->type = regexp;
new->alt = 0;
new->next = 0;
if(qp)
parsealt(bp, qp+2, &new->alt);
new->next = patterns[action].regexps;
patterns[action].regexps = new;
continue;
}
/* not a Regexp - hook strings into Pattern hash chain */
spat = Malloc(sizeof(*spat));
spat->next = 0;
spat->alt = 0;
spat->len = n;
spat->string = Malloc(n+1);
spat->c1 = cp[1];
strcpy(spat->string, cp);
if(qp)
parsealt(bp, qp+2, &spat->alt);
p = patterns[action].strings;
if(p == 0) {
p = Malloc(sizeof(Pattern));
memset(p, 0, sizeof(*p));
p->action = action;
p->type = string;
patterns[action].strings = p;
}
h = hash(*spat->string);
spat->next = p->spat[h];
p->spat[h] = spat;
}
}
static void
parsealt(Biobuf *bp, char *cp, Spat** head)
{
char *p;
Spat *alt;
while(cp){
if(*cp == 0){ /*escaped newline*/
do{
cp = Brdline(bp, '\n');
if(cp == 0)
return;
cp[Blinelen(bp)-1] = 0;
} while(extract(cp) <= 0 || *cp == 0);
}
p = cp;
cp = strstr(p, "~~");
if(cp){
*cp = 0;
cp += 2;
}
if(strlen(p)){
alt = Malloc(sizeof(*alt));
alt->string = strdup(p);
alt->next = *head;
*head = alt;
}
}
}
static int
extract(char *cp)
{
int c;
char *p, *q, *r;
p = q = r = cp;
while(whitespace(*p))
p++;
while(c = *p++){
if (c == '#')
break;
if(c == '"'){
while(*p && *p != '"'){
if(*p == '\\' && p[1] == '"')
p++;
if('A' <= *p && *p <= 'Z')
*q++ = *p++ + ('a'-'A');
else
*q++ = *p++;
}
if(*p)
p++;
r = q; /* never back up over a quoted string */
} else {
if('A' <= c && c <= 'Z')
c += ('a'-'A');
*q++ = c;
}
}
while(q > r && whitespace(q[-1]))
q--;
*q = 0;
return q-cp;
}
/*
* The matching engine: compare canonical input to pattern structures
*/
static Spat*
isalt(char *message, Spat *alt)
{
while(alt) {
if(*cmd)
if(message != cmd && strstr(cmd, alt->string))
break;
if(message != header+1 && strstr(header+1, alt->string))
break;
if(strstr(message, alt->string))
break;
alt = alt->next;
}
return alt;
}
int
matchpat(Pattern *p, char *message, Resub *m)
{
Spat *spat;
char *s;
int c, c1;
if(p->type == string){
c1 = *message;
for(s=message; c=c1; s++){
c1 = s[1];
for(spat=p->spat[hash(c)]; spat; spat=spat->next){
if(c1 == spat->c1)
if(memcmp(s, spat->string, spat->len) == 0)
if(!isalt(message, spat->alt)){
m->sp = s;
m->ep = s + spat->len;
return 1;
}
}
}
return 0;
}
m->sp = m->ep = 0;
if(regexec(p->pat, message, m, 1) == 0)
return 0;
if(isalt(message, p->alt))
return 0;
return 1;
}
void
xprint(int fd, char *type, Resub *m)
{
char *p, *q;
int i;
if(m->sp == 0 || m->ep == 0)
return;
/* back up approx 30 characters to whitespace */
for(p = m->sp, i = 0; *p && i < 30; i++, p--)
;
while(*p && *p != ' ')
p--;
p++;
/* grab about 30 more chars beyond the end of the match */
for(q = m->ep, i = 0; *q && i < 30; i++, q++)
;
while(*q && *q != ' ')
q++;
fprint(fd, "%s %.*s~%.*s~%.*s\n", type, (int)(m->sp-p), p, (int)(m->ep-m->sp), m->sp, (int)(q-m->ep), m->ep);
}
enum {
INVAL= 255
};
static uchar t64d[256] = {
/*00 */ INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
/*10*/ INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
/*20*/ INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
INVAL, INVAL, INVAL, 62, INVAL, INVAL, INVAL, 63,
/*30*/ 52, 53, 54, 55, 56, 57, 58, 59,
60, 61, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
/*40*/ INVAL, 0, 1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12, 13, 14,
/*50*/ 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, INVAL, INVAL, INVAL, INVAL, INVAL,
/*60*/ INVAL, 26, 27, 28, 29, 30, 31, 32,
33, 34, 35, 36, 37, 38, 39, 40,
/*70*/ 41, 42, 43, 44, 45, 46, 47, 48,
49, 50, 51, INVAL, INVAL, INVAL, INVAL, INVAL,
/*80*/ INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
/*90*/ INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
/*A0*/ INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
/*B0*/ INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
/*C0*/ INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
/*D0*/ INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
/*E0*/ INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
/*F0*/ INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
};

View File

@@ -0,0 +1,24 @@
</$objtype/mkfile
TARG=scanmail\
testscan
OFILES= common.$O
HFILES= spam.h\
../common/sys.h\
LIB= ../common/libcommon.a$O\
BIN=/$objtype/bin/upas
UPDATE=\
mkfile\
$HFILES\
${OFILES:%.$O=%.c}\
${TARG:%=%.c}\
</sys/src/cmd/mkmany
CFLAGS=$CFLAGS -I../common
scanmail.$O: scanmail.c
$CC $CFLAGS -D'SPOOL="/mail"' scanmail.c

View File

@@ -0,0 +1,476 @@
#include "common.h"
#include "spam.h"
int cflag;
int debug;
int hflag;
int nflag;
int sflag;
int tflag;
int vflag;
Biobuf bin, bout, *cout;
/* file names */
char patfile[128];
char linefile[128];
char holdqueue[128];
char copydir[128];
char header[Hdrsize+2];
char cmd[1024];
char **qname;
char **qdir;
char *sender;
String *recips;
char* canon(Biobuf*, char*, char*, int*);
int matcher(char*, Pattern*, char*, Resub*);
int matchaction(int, char*, Resub*);
Biobuf *opencopy(char*);
Biobuf *opendump(char*);
char *qmail(char**, char*, int, Biobuf*);
void saveline(char*, char*, Resub*);
int optoutofspamfilter(char*);
void
usage(void)
{
fprint(2, "missing or bad arguments to qer\n");
exits("usage");
}
void
regerror(char *s)
{
fprint(2, "scanmail: %s\n", s);
}
void *
Malloc(long n)
{
void *p;
p = malloc(n);
if(p == 0)
exits("malloc");
return p;
}
void*
Realloc(void *p, ulong n)
{
p = realloc(p, n);
if(p == 0)
exits("realloc");
return p;
}
void
main(int argc, char *argv[])
{
int i, n, nolines, optout;
char **args, **a, *cp, *buf;
char body[Bodysize+2];
Resub match[1];
Biobuf *bp;
optout = 1;
a = args = Malloc((argc+1)*sizeof(char*));
sprint(patfile, "%s/patterns", UPASLIB);
sprint(linefile, "%s/lines", UPASLOG);
sprint(holdqueue, "%s/queue.hold", SPOOL);
sprint(copydir, "%s/copy", SPOOL);
*a++ = argv[0];
for(argc--, argv++; argv[0] && argv[0][0] == '-'; argc--, argv++){
switch(argv[0][1]){
case 'c': /* save copy of message */
cflag = 1;
break;
case 'd': /* debug */
debug++;
*a++ = argv[0];
break;
case 'h': /* queue held messages by sender domain */
hflag = 1; /* -q flag must be set also */
break;
case 'n': /* NOHOLD mode */
nflag = 1;
break;
case 'p': /* pattern file */
if(argv[0][2] || argv[1] == 0)
usage();
argc--;
argv++;
strecpy(patfile, patfile+sizeof patfile, *argv);
break;
case 'q': /* queue name */
if(argv[0][2] || argv[1] == 0)
usage();
*a++ = argv[0];
argc--;
argv++;
qname = a;
*a++ = argv[0];
break;
case 's': /* save copy of dumped message */
sflag = 1;
break;
case 't': /* test mode - don't log match
* and write message to /dev/null
*/
tflag = 1;
break;
case 'v': /* vebose - print matches */
vflag = 1;
break;
default:
*a++ = argv[0];
break;
}
}
if(argc < 3)
usage();
Binit(&bin, 0, OREAD);
bp = Bopen(patfile, OREAD);
if(bp){
parsepats(bp);
Bterm(bp);
}
qdir = a;
sender = argv[2];
/* copy the rest of argv, acummulating the recipients as we go */
for(i = 0; argv[i]; i++){
*a++ = argv[i];
if(i < 4) /* skip queue, 'mail', sender, dest sys */
continue;
/* recipients and smtp flags - skip the latter*/
if(strcmp(argv[i], "-g") == 0){
*a++ = argv[++i];
continue;
}
if(recips)
s_append(recips, ", ");
else
recips = s_new();
s_append(recips, argv[i]);
if(optout && !optoutofspamfilter(argv[i]))
optout = 0;
}
*a = 0;
/* construct a command string for matching */
snprint(cmd, sizeof(cmd)-1, "%s %s", sender, s_to_c(recips));
cmd[sizeof(cmd)-1] = 0;
for(cp = cmd; *cp; cp++)
*cp = tolower(*cp);
/* canonicalize a copy of the header and body.
* buf points to orginal message and n contains
* number of bytes of original message read during
* canonicalization.
*/
*body = 0;
*header = 0;
buf = canon(&bin, header+1, body+1, &n);
if (buf == 0)
exits("read");
/* if all users opt out, don't try matches */
if(optout){
if(cflag)
cout = opencopy(sender);
exits(qmail(args, buf, n, cout));
}
/* Turn off line logging, if command line matches */
nolines = matchaction(Lineoff, cmd, match);
for(i = 0; patterns[i].action; i++){
/* Lineoff patterns were already done above */
if(i == Lineoff)
continue;
/* don't apply "Line" patterns if excluded above */
if(nolines && i == SaveLine)
continue;
/* apply patterns to the sender/recips, header and body */
if(matchaction(i, cmd, match))
break;
if(matchaction(i, header+1, match))
break;
if(i == HoldHeader)
continue;
if(matchaction(i, body+1, match))
break;
}
if(cflag && patterns[i].action == 0) /* no match found - save msg */
cout = opencopy(sender);
exits(qmail(args, buf, n, cout));
}
char*
qmail(char **argv, char *buf, int n, Biobuf *cout)
{
Waitmsg *status;
int i, pid, pipefd[2];
char path[512];
Biobuf *bp;
pid = 0;
if(tflag == 0){
if(pipe(pipefd) < 0)
exits("pipe");
pid = fork();
if(pid == 0){
dup(pipefd[0], 0);
for(i = sysfiles(); i >= 3; i--)
close(i);
snprint(path, sizeof(path), "%s/qer", UPASBIN);
*argv=path;
exec(path, argv);
exits("exec");
}
Binit(&bout, pipefd[1], OWRITE);
bp = &bout;
} else
bp = Bopen("/dev/null", OWRITE);
while(n > 0){
Bwrite(bp, buf, n);
if(cout)
Bwrite(cout, buf, n);
n = Bread(&bin, buf, sizeof(buf)-1);
}
Bterm(bp);
if(cout)
Bterm(cout);
if(tflag)
return 0;
close(pipefd[1]);
close(pipefd[0]);
for(;;){
status = wait();
if(status == nil || status->pid == pid)
break;
free(status);
}
if(status == nil)
strcpy(buf, "wait failed");
else{
strcpy(buf, status->msg);
free(status);
}
return buf;
}
char*
canon(Biobuf *bp, char *header, char *body, int *n)
{
int hsize;
char *raw;
hsize = 0;
*header = 0;
*body = 0;
raw = readmsg(bp, &hsize, n);
if(raw){
if(convert(raw, raw+hsize, header, Hdrsize, 0))
conv64(raw+hsize, raw+*n, body, Bodysize); /* base64 */
else
convert(raw+hsize, raw+*n, body, Bodysize, 1); /* text */
}
return raw;
}
int
matchaction(int action, char *message, Resub *m)
{
char *name;
Pattern *p;
if(message == 0 || *message == 0)
return 0;
name = patterns[action].action;
p = patterns[action].strings;
if(p)
if(matcher(name, p, message, m))
return 1;
for(p = patterns[action].regexps; p; p = p->next)
if(matcher(name, p, message, m))
return 1;
return 0;
}
int
matcher(char *action, Pattern *p, char *message, Resub *m)
{
char *cp;
String *s;
for(cp = message; matchpat(p, cp, m); cp = m->ep){
switch(p->action){
case SaveLine:
if(vflag)
xprint(2, action, m);
saveline(linefile, sender, m);
break;
case HoldHeader:
case Hold:
if(nflag)
continue;
if(vflag)
xprint(2, action, m);
*qdir = holdqueue;
if(hflag && qname){
cp = strchr(sender, '!');
if(cp){
*cp = 0;
*qname = strdup(sender);
*cp = '!';
} else
*qname = strdup(sender);
}
return 1;
case Dump:
if(vflag)
xprint(2, action, m);
*(m->ep) = 0;
if(!tflag){
s = s_new();
s_append(s, sender);
s = unescapespecial(s);
syslog(0, "smtpd", "Dumped %s [%s] to %s", s_to_c(s), m->sp,
s_to_c(s_restart(recips)));
s_free(s);
}
tflag = 1;
if(sflag)
cout = opendump(sender);
return 1;
default:
break;
}
}
return 0;
}
void
saveline(char *file, char *sender, Resub *rp)
{
char *p, *q;
int i, c;
Biobuf *bp;
if(rp->sp == 0 || rp->ep == 0)
return;
/* back up approx 20 characters to whitespace */
for(p = rp->sp, i = 0; *p && i < 20; i++, p--)
;
while(*p && *p != ' ')
p--;
p++;
/* grab about 20 more chars beyond the end of the match */
for(q = rp->ep, i = 0; *q && i < 20; i++, q++)
;
while(*q && *q != ' ')
q++;
c = *q;
*q = 0;
bp = sysopen(file, "al", 0644);
if(bp){
Bprint(bp, "%s-> %s\n", sender, p);
Bterm(bp);
}
else if(debug)
fprint(2, "can't save line: (%s) %s\n", sender, p);
*q = c;
}
Biobuf*
opendump(char *sender)
{
int i;
ulong h;
char buf[512];
Biobuf *b;
char *cp;
cp = ctime(time(0));
cp[7] = 0;
cp[10] = 0;
if(cp[8] == ' ')
sprint(buf, "%s/queue.dump/%s%c", SPOOL, cp+4, cp[9]);
else
sprint(buf, "%s/queue.dump/%s%c%c", SPOOL, cp+4, cp[8], cp[9]);
cp = buf+strlen(buf);
if(access(buf, 0) < 0 && sysmkdir(buf, 0777) < 0){
syslog(0, "smtpd", "couldn't dump mail from %s: %r", sender);
return 0;
}
h = 0;
while(*sender)
h = h*257 + *sender++;
for(i = 0; i < 50; i++){
h += lrand();
sprint(cp, "/%lud", h);
b = sysopen(buf, "wlc", 0644);
if(b){
if(vflag)
fprint(2, "saving in %s\n", buf);
return b;
}
}
return 0;
}
Biobuf*
opencopy(char *sender)
{
int i;
ulong h;
char buf[512];
Biobuf *b;
h = 0;
while(*sender)
h = h*257 + *sender++;
for(i = 0; i < 50; i++){
h += lrand();
sprint(buf, "%s/%lud", copydir, h);
b = sysopen(buf, "wlc", 0600);
if(b)
return b;
}
return 0;
}
int
optoutofspamfilter(char *addr)
{
char *p, *f;
int rv;
p = strchr(addr, '!');
if(p)
p++;
else
p = addr;
rv = 0;
f = smprint("/mail/box/%s/nospamfiltering", p);
if(f != nil){
rv = access(f, 0)==0;
free(f);
}
return rv;
}

View File

@@ -0,0 +1,62 @@
enum{
Dump = 0, /* Actions must be in order of descending importance */
HoldHeader,
Hold,
SaveLine,
Lineoff, /* Lineoff must be the last action code */
Nactions,
Nhash = 128,
regexp = 1, /* types: literal string or regular expression */
string = 2,
MaxHtml = 256,
Hdrsize = 4096,
Bodysize = 8192,
Maxread = 64*1024,
};
typedef struct spat Spat;
typedef struct pattern Pattern;
typedef struct patterns Patterns;
struct spat
{
char* string;
int len;
int c1;
Spat* next;
Spat* alt;
};
struct pattern{
struct pattern *next;
int action;
int type;
Spat* alt;
union{
Reprog* pat;
Spat* spat[Nhash];
};
};
struct patterns {
char *action;
Pattern *strings;
Pattern *regexps;
};
extern int debug;
extern Patterns patterns[];
extern char header[];
extern char cmd[];
extern void conv64(char*, char*, char*, int);
extern int convert(char*, char*, char*, int, int);
extern void* Malloc(long n);
extern int matchpat(Pattern*, char*, Resub*);
extern char* readmsg(Biobuf*, int*, int*);
extern void parsepats(Biobuf*);
extern void* Realloc(void*, ulong);
extern void xprint(int, char*, Resub*);

View File

@@ -0,0 +1,212 @@
#include "sys.h"
#include "spam.h"
int debug;
Biobuf bin;
char patfile[128], header[Hdrsize+2];
char cmd[1024];
char* canon(Biobuf*, char*, char*, int*);
int matcher(char *, Pattern*, char*, Resub*);
int matchaction(Patterns*, char*);
void
usage(void)
{
fprint(2, "missing or bad arguments to qer\n");
exits("usage");
}
void *
Malloc(long n)
{
void *p;
p = malloc(n);
if(p == 0){
fprint(2, "malloc error");
exits("malloc");
}
return p;
}
void*
Realloc(void *p, ulong n)
{
p = realloc(p, n);
if(p == 0){
fprint(2, "realloc error");
exits("realloc");
}
return p;
}
void
dumppats(void)
{
int i, j;
Pattern *p;
Spat *s, *q;
for(i = 0; patterns[i].action; i++){
for(p = patterns[i].regexps; p; p = p->next){
print("%s <REGEXP>\n", patterns[i].action);
if(p->alt)
print("Alt:");
for(s = p->alt; s; s = s->next)
print("\t%s\n", s->string);
}
p = patterns[i].strings;
if(p == 0)
continue;
for(j = 0; j < Nhash; j++){
for(s = p->spat[j]; s; s = s->next){
print("%s %s\n", patterns[i].action, s->string);
if(s->alt)
print("Alt:");
for(q = s->alt; q; q = q->next)
print("\t%s\n", q->string);
}
}
}
}
void
main(int argc, char *argv[])
{
int i, fd, n, aflag, vflag;
char body[Bodysize+2], *raw, *ret;
Biobuf *bp;
sprint(patfile, "%s/patterns", UPASLIB);
aflag = -1;
vflag = 0;
ARGBEGIN {
case 'a':
aflag = 1;
break;
case 'v':
vflag = 1;
break;
case 'd':
debug++;
break;
case 'p':
strcpy(patfile,ARGF());
break;
} ARGEND
bp = Bopen(patfile, OREAD);
if(bp){
parsepats(bp);
Bterm(bp);
}
if(argc >= 1){
fd = open(*argv, OREAD);
if(fd < 0){
fprint(2, "can't open %s\n", *argv);
exits("open");
}
Binit(&bin, fd, OREAD);
} else
Binit(&bin, 0, OREAD);
*body = 0;
*header = 0;
ret = 0;
for(;;){
raw = canon(&bin, header+1, body+1, &n);
if(raw == 0)
break;
if(aflag == 0)
continue;
if(aflag < 0)
aflag = 0;
if(vflag){
if(header[1]) {
fprint(2, "\t**** Header ****\n\n");
write(2, header+1, strlen(header+1));
fprint(2, "\n");
}
fprint(2, "\t**** Body ****\n\n");
if(body[1])
write(2, body+1, strlen(body+1));
fprint(2, "\n");
}
for(i = 0; patterns[i].action; i++){
if(matchaction(&patterns[i], header+1))
ret = patterns[i].action;
if(i == HoldHeader)
continue;
if(matchaction(&patterns[i], body+1))
ret = patterns[i].action;
}
}
exits(ret);
}
char*
canon(Biobuf *bp, char *header, char *body, int *n)
{
int hsize, base64;
static char *raw;
hsize = 0;
base64 = 0;
*header = 0;
*body = 0;
if(raw == 0){
raw = readmsg(bp, &hsize, n);
if(raw)
base64 = convert(raw, raw+hsize, header, Hdrsize, 0);
} else {
free(raw);
raw = readmsg(bp, 0, n);
}
if(raw){
if(base64)
conv64(raw+hsize, raw+*n, body, Bodysize);
else
convert(raw+hsize, raw+*n, body, Bodysize, 1);
}
return raw;
}
int
matchaction(Patterns *pp, char *message)
{
char *name, *cp;
int ret;
Pattern *p;
Resub m[1];
if(message == 0 || *message == 0)
return 0;
name = pp->action;
p = pp->strings;
ret = 0;
if(p)
for(cp = message; matcher(name, p, cp, m); cp = m[0].ep)
ret++;
for(p = pp->regexps; p; p = p->next)
for(cp = message; matcher(name, p, cp, m); cp = m[0].ep)
ret++;
return ret;
}
int
matcher(char *action, Pattern *p, char *message, Resub *m)
{
if(matchpat(p, message, m)){
if(p->action != Lineoff)
xprint(1, action, m);
return 1;
}
return 0;
}

View File

@@ -0,0 +1,29 @@
#include "common.h"
#include "send.h"
/*
* Run a command to authorize or refuse entry. Return status 0 means
* authorize, -1 means refuse.
*/
void
authorize(dest *dp)
{
process *pp;
String *errstr;
dp->authorized = 1;
pp = proc_start(s_to_c(dp->repl1), (stream *)0, (stream *)0, outstream(), 1, 0);
if (pp == 0){
dp->status = d_noforward;
return;
}
errstr = s_new();
while(s_read_line(pp->std[2]->fp, errstr))
;
if ((dp->pstat = proc_wait(pp)) != 0) {
dp->repl2 = errstr;
dp->status = d_noforward;
} else
s_free(errstr);
proc_free(pp);
}

133
src/cmd/upas/send/bind.c Normal file
View File

@@ -0,0 +1,133 @@
#include "common.h"
#include "send.h"
static int forward_loop(char *, char *);
/* bind the destinations to the commands to be executed */
extern dest *
up_bind(dest *destp, message *mp, int checkforward)
{
dest *list[2]; /* lists of unbound destinations */
int li; /* index into list[2] */
dest *bound=0; /* bound destinations */
dest *dp;
int i;
list[0] = destp;
list[1] = 0;
/*
* loop once to check for:
* - forwarding rights
* - addressing loops
* - illegal characters
* - characters that need escaping
*/
for (dp = d_rm(&list[0]); dp != 0; dp = d_rm(&list[0])) {
if (!checkforward)
dp->authorized = 1;
dp->addr = escapespecial(dp->addr);
if (forward_loop(s_to_c(dp->addr), thissys)) {
dp->status = d_eloop;
d_same_insert(&bound, dp);
} else if(forward_loop(s_to_c(mp->sender), thissys)) {
dp->status = d_eloop;
d_same_insert(&bound, dp);
} else if(shellchars(s_to_c(dp->addr))) {
dp->status = d_syntax;
d_same_insert(&bound, dp);
} else
d_insert(&list[1], dp);
}
li = 1;
/* Loop until all addresses are bound or address loop detected */
for (i=0; list[li]!=0 && i<32; ++i, li ^= 1) {
/* Traverse the current list. Bound items are put on the
* `bound' list. Unbound items are put on the next list to
* traverse, `list[li^1]'.
*/
for (dp = d_rm(&list[li]); dp != 0; dp = d_rm(&list[li])){
dest *newlist;
rewrite(dp, mp);
if(debug)
fprint(2, "%s -> %s\n", s_to_c(dp->addr),
dp->repl1 ? s_to_c(dp->repl1):"");
switch (dp->status) {
case d_auth:
/* authorize address if not already authorized */
if(!dp->authorized){
authorize(dp);
if(dp->status==d_auth)
d_insert(&list[li^1], dp);
else
d_insert(&bound, dp);
}
break;
case d_cat:
/* address -> local */
newlist = expand_local(dp);
if (newlist == 0) {
/* append to mailbox (or error) */
d_same_insert(&bound, dp);
} else if (newlist->status == d_undefined) {
/* Forward to ... */
d_insert(&list[li^1], newlist);
} else {
/* Pipe to ... */
d_same_insert(&bound, newlist);
}
break;
case d_pipe:
/* address -> command */
d_same_insert(&bound, dp);
break;
case d_alias:
/* address -> rewritten address */
newlist = s_to_dest(dp->repl1, dp);
if(newlist != 0)
d_insert(&list[li^1], newlist);
else
d_same_insert(&bound, dp);
break;
case d_translate:
/* pipe to a translator */
newlist = translate(dp);
if (newlist != 0)
d_insert(&list[li^1], newlist);
else
d_same_insert(&bound, dp);
break;
default:
/* error */
d_same_insert(&bound, dp);
break;
}
}
}
/* mark remaining comands as "forwarding loops" */
for (dp = d_rm(&list[li]); dp != 0; dp = d_rm(&list[li])) {
dp->status = d_loop;
d_same_insert(&bound, dp);
}
return bound;
}
/* Return TRUE if a forwarding loop exists, i.e., the String `system'
* is found more than 4 times in the return address.
*/
static int
forward_loop(char *addr, char *system)
{
int len = strlen(system), found = 0;
while (addr = strchr(addr, '!'))
if (!strncmp(++addr, system, len)
&& addr[len] == '!' && ++found == 4)
return 1;
return 0;
}

View File

@@ -0,0 +1,60 @@
#include "common.h"
#include "send.h"
/* dispose of local addresses */
int
cat_mail(dest *dp, message *mp)
{
Biobuf *fp;
char *rcvr, *cp;
Mlock *l;
String *tmp, *s;
int i, n;
s = unescapespecial(s_clone(dp->repl1));
if (nflg) {
if(!xflg)
print("cat >> %s\n", s_to_c(s));
else
print("%s\n", s_to_c(dp->addr));
s_free(s);
return 0;
}
for(i = 0;; i++){
l = syslock(s_to_c(s));
if(l == 0)
return refuse(dp, mp, "can't lock mail file", 0, 0);
fp = sysopen(s_to_c(s), "al", MBOXMODE);
if(fp)
break;
tmp = s_append(0, s_to_c(s));
s_append(tmp, ".tmp");
fp = sysopen(s_to_c(tmp), "al", MBOXMODE);
if(fp){
syslog(0, "mail", "error: used %s", s_to_c(tmp));
s_free(tmp);
break;
}
s_free(tmp);
sysunlock(l);
if(i >= 5)
return refuse(dp, mp, "mail file cannot be opened", 0, 0);
sleep(1000);
}
s_free(s);
n = m_print(mp, fp, (char *)0, 1);
if (Bprint(fp, "\n") < 0 || Bflush(fp) < 0 || n < 0){
sysclose(fp);
sysunlock(l);
return refuse(dp, mp, "error writing mail file", 0, 0);
}
sysclose(fp);
sysunlock(l);
rcvr = s_to_c(dp->addr);
if(cp = strrchr(rcvr, '!'))
rcvr = cp+1;
logdelivery(dp, rcvr, mp);
return 0;
}

260
src/cmd/upas/send/dest.c Normal file
View File

@@ -0,0 +1,260 @@
#include "common.h"
#include "send.h"
static String* s_parseq(String*, String*);
/* exports */
dest *dlist;
extern dest*
d_new(String *addr)
{
dest *dp;
dp = (dest *)mallocz(sizeof(dest), 1);
if (dp == 0) {
perror("d_new");
exit(1);
}
dp->same = dp;
dp->nsame = 1;
dp->nchar = 0;
dp->next = dp;
dp->addr = escapespecial(addr);
dp->parent = 0;
dp->repl1 = dp->repl2 = 0;
dp->status = d_undefined;
return dp;
}
extern void
d_free(dest *dp)
{
if (dp != 0) {
s_free(dp->addr);
s_free(dp->repl1);
s_free(dp->repl2);
free((char *)dp);
}
}
/* The following routines manipulate an ordered list of items. Insertions
* are always to the end of the list. Deletions are from the beginning.
*
* The list are circular witht the `head' of the list being the last item
* added.
*/
/* Get first element from a circular list linked via 'next'. */
extern dest *
d_rm(dest **listp)
{
dest *dp;
if (*listp == 0)
return 0;
dp = (*listp)->next;
if (dp == *listp)
*listp = 0;
else
(*listp)->next = dp->next;
dp->next = dp;
return dp;
}
/* Insert a new entry at the end of the list linked via 'next'. */
extern void
d_insert(dest **listp, dest *new)
{
dest *head;
if (*listp == 0) {
*listp = new;
return;
}
if (new == 0)
return;
head = new->next;
new->next = (*listp)->next;
(*listp)->next = head;
*listp = new;
return;
}
/* Get first element from a circular list linked via 'same'. */
extern dest *
d_rm_same(dest **listp)
{
dest *dp;
if (*listp == 0)
return 0;
dp = (*listp)->same;
if (dp == *listp)
*listp = 0;
else
(*listp)->same = dp->same;
dp->same = dp;
return dp;
}
/* Look for a duplicate on the same list */
int
d_same_dup(dest *dp, dest *new)
{
dest *first = dp;
if(new->repl2 == 0)
return 1;
do {
if(strcmp(s_to_c(dp->repl2), s_to_c(new->repl2))==0)
return 1;
dp = dp->same;
} while(dp != first);
return 0;
}
/* Insert an entry into the corresponding list linked by 'same'. Note that
* the basic structure is a list of lists.
*/
extern void
d_same_insert(dest **listp, dest *new)
{
dest *dp;
int len;
if(new->status == d_pipe || new->status == d_cat) {
len = new->repl2 ? strlen(s_to_c(new->repl2)) : 0;
if(*listp != 0){
dp = (*listp)->next;
do {
if(dp->status == new->status
&& strcmp(s_to_c(dp->repl1), s_to_c(new->repl1))==0){
/* remove duplicates */
if(d_same_dup(dp, new))
return;
/* add to chain if chain small enough */
if(dp->nsame < MAXSAME
&& dp->nchar + len < MAXSAMECHAR){
new->same = dp->same;
dp->same = new;
dp->nchar += len + 1;
dp->nsame++;
return;
}
}
dp = dp->next;
} while (dp != (*listp)->next);
}
new->nchar = strlen(s_to_c(new->repl1)) + len + 1;
}
new->next = new;
d_insert(listp, new);
}
/*
* Form a To: if multiple destinations.
* The local! and !local! checks are artificial intelligence,
* there should be a better way.
*/
extern String*
d_to(dest *list)
{
dest *np, *sp;
String *s;
int i, n;
char *cp;
s = s_new();
s_append(s, "To: ");
np = list;
i = n = 0;
do {
np = np->next;
sp = np;
do {
sp = sp->same;
cp = s_to_c(sp->addr);
/* hack to get local! out of the names */
if(strncmp(cp, "local!", 6) == 0)
cp += 6;
if(n > 20){ /* 20 to appease mailers complaining about long lines */
s_append(s, "\n\t");
n = 0;
}
if(i != 0){
s_append(s, ", ");
n += 2;
}
s_append(s, cp);
n += strlen(cp);
i++;
} while(sp != np);
} while(np != list);
return unescapespecial(s);
}
/* expand a String of destinations into a linked list of destiniations */
extern dest *
s_to_dest(String *sp, dest *parent)
{
String *addr;
dest *list=0;
dest *new;
if (sp == 0)
return 0;
addr = s_new();
while (s_parseq(sp, addr)!=0) {
addr = escapespecial(addr);
if(shellchars(s_to_c(addr))){
while(new = d_rm(&list))
d_free(new);
break;
}
new = d_new(addr);
new->parent = parent;
new->authorized = parent->authorized;
d_insert(&list, new);
addr = s_new();
}
s_free(addr);
return list;
}
#undef isspace
#define isspace(c) ((c)==' ' || (c)=='\t' || (c)=='\n')
/* Get the next field from a String. The field is delimited by white space.
* Anything delimited by double quotes is included in the string.
*/
static String*
s_parseq(String *from, String *to)
{
int c;
if (*from->ptr == '\0')
return 0;
if (to == 0)
to = s_new();
for (c = *from->ptr;!isspace(c) && c != 0; c = *(++from->ptr)){
s_putc(to, c);
if(c == '"'){
for (c = *(++from->ptr); c && c != '"'; c = *(++from->ptr))
s_putc(to, *from->ptr);
s_putc(to, '"');
if(c == 0)
break;
}
}
s_terminate(to);
/* crunch trailing white */
while(isspace(*from->ptr))
from->ptr++;
return to;
}

128
src/cmd/upas/send/filter.c Normal file
View File

@@ -0,0 +1,128 @@
#include "common.h"
#include "send.h"
Biobuf bin;
int rmail, tflg;
char *subjectarg;
char *findbody(char*);
void
main(int argc, char *argv[])
{
message *mp;
dest *dp;
Reprog *p;
Resub match[10];
char file[MAXPATHLEN];
Biobuf *fp;
char *rcvr, *cp;
Mlock *l;
String *tmp;
int i;
int header, body;
header = body = 0;
ARGBEGIN {
case 'h':
header = 1;
break;
case 'b':
header = 1;
body = 1;
break;
} ARGEND
Binit(&bin, 0, OREAD);
if(argc < 2){
fprint(2, "usage: filter rcvr mailfile [regexp mailfile ...]\n");
exits("usage");
}
mp = m_read(&bin, 1, 0);
/* get rid of local system name */
cp = strchr(s_to_c(mp->sender), '!');
if(cp){
cp++;
mp->sender = s_copy(cp);
}
dp = d_new(s_copy(argv[0]));
strecpy(file, file+sizeof file, argv[1]);
cp = findbody(s_to_c(mp->body));
for(i = 2; i < argc; i += 2){
p = regcomp(argv[i]);
if(p == 0)
continue;
if(regexec(p, s_to_c(mp->sender), match, 10)){
regsub(argv[i+1], file, sizeof(file), match, 10);
break;
}
if(header == 0 && body == 0)
continue;
if(regexec(p, s_to_c(mp->body), match, 10)){
if(body == 0 && match[0].s.sp >= cp)
continue;
regsub(argv[i+1], file, sizeof(file), match, 10);
break;
}
}
/*
* always lock the normal mail file to avoid too many lock files
* lying about. This isn't right but it's what the majority prefers.
*/
l = syslock(argv[1]);
if(l == 0){
fprint(2, "can't lock mail file %s\n", argv[1]);
exit(1);
}
/*
* open the destination mail file
*/
fp = sysopen(file, "ca", MBOXMODE);
if (fp == 0){
tmp = s_append(0, file);
s_append(tmp, ".tmp");
fp = sysopen(s_to_c(tmp), "cal", MBOXMODE);
if(fp == 0){
sysunlock(l);
fprint(2, "can't open mail file %s\n", file);
exit(1);
}
syslog(0, "mail", "error: used %s", s_to_c(tmp));
s_free(tmp);
}
Bseek(fp, 0, 2);
if(m_print(mp, fp, (char *)0, 1) < 0
|| Bprint(fp, "\n") < 0
|| Bflush(fp) < 0){
sysclose(fp);
sysunlock(l);
fprint(2, "can't write mail file %s\n", file);
exit(1);
}
sysclose(fp);
sysunlock(l);
rcvr = argv[0];
if(cp = strrchr(rcvr, '!'))
rcvr = cp+1;
logdelivery(dp, rcvr, mp);
exit(0);
}
char*
findbody(char *p)
{
if(*p == '\n')
return p;
while(*p){
if(*p == '\n' && *(p+1) == '\n')
return p+1;
p++;
}
return p;
}

View File

@@ -0,0 +1,24 @@
#include "common.h"
#include "send.h"
#undef isspace
#define isspace(c) ((c)==' ' || (c)=='\t' || (c)=='\n')
/*
* Translate the last component of the sender address. If the translation
* yields the same address, replace the sender with its last component.
*/
extern void
gateway(message *mp)
{
char *base;
String *s;
/* first remove all systems equivalent to us */
base = skipequiv(s_to_c(mp->sender));
if(base != s_to_c(mp->sender)){
s = mp->sender;
mp->sender = s_copy(base);
s_free(s);
}
}

129
src/cmd/upas/send/local.c Normal file
View File

@@ -0,0 +1,129 @@
#include "common.h"
#include "send.h"
static void
mboxfile(dest *dp, String *user, String *path, char *file)
{
char *cp;
mboxpath(s_to_c(user), s_to_c(dp->addr), path, 0);
cp = strrchr(s_to_c(path), '/');
if(cp)
path->ptr = cp+1;
else
path->ptr = path->base;
s_append(path, file);
}
/*
* Check forwarding requests
*/
extern dest*
expand_local(dest *dp)
{
Biobuf *fp;
String *file, *line, *s;
dest *rv;
int forwardok;
char *user;
/* short circuit obvious security problems */
if(strstr(s_to_c(dp->addr), "/../")){
dp->status = d_unknown;
return 0;
}
/* isolate user's name if part of a path */
user = strrchr(s_to_c(dp->addr), '!');
if(user)
user++;
else
user = s_to_c(dp->addr);
/* if no replacement string, plug in user's name */
if(dp->repl1 == 0){
dp->repl1 = s_new();
mboxname(user, dp->repl1);
}
s = unescapespecial(s_clone(dp->repl1));
/*
* if this is the descendant of a `forward' file, don't
* look for a forward.
*/
forwardok = 1;
for(rv = dp->parent; rv; rv = rv->parent)
if(rv->status == d_cat){
forwardok = 0;
break;
}
file = s_new();
if(forwardok){
/*
* look for `forward' file for forwarding address(es)
*/
mboxfile(dp, s, file, "forward");
fp = sysopen(s_to_c(file), "r", 0);
if (fp != 0) {
line = s_new();
for(;;){
if(s_read_line(fp, line) == nil)
break;
if(*(line->ptr - 1) != '\n')
break;
if(*(line->ptr - 2) == '\\')
*(line->ptr-2) = ' ';
*(line->ptr-1) = ' ';
}
sysclose(fp);
if(debug)
fprint(2, "forward = %s\n", s_to_c(line));
rv = s_to_dest(s_restart(line), dp);
s_free(line);
if(rv){
s_free(file);
s_free(s);
return rv;
}
}
}
/*
* look for a 'pipe' file. This won't work if there are
* special characters in the account name since the file
* name passes through a shell. tdb.
*/
mboxfile(dp, dp->repl1, s_reset(file), "pipeto");
if(sysexist(s_to_c(file))){
if(debug)
fprint(2, "found a pipeto file\n");
dp->status = d_pipeto;
line = s_new();
s_append(line, "upasname='");
s_append(line, user);
s_append(line, "' ");
s_append(line, s_to_c(file));
s_append(line, " ");
s_append(line, s_to_c(dp->addr));
s_append(line, " ");
s_append(line, s_to_c(dp->repl1));
s_free(dp->repl1);
dp->repl1 = line;
s_free(file);
s_free(s);
return dp;
}
/*
* see if the mailbox directory exists
*/
mboxfile(dp, s, s_reset(file), ".");
if(sysexist(s_to_c(file)))
dp->status = d_cat;
else
dp->status = d_unknown;
s_free(file);
s_free(s);
return 0;
}

85
src/cmd/upas/send/log.c Normal file
View File

@@ -0,0 +1,85 @@
#include "common.h"
#include "send.h"
/* configuration */
#define LOGBiobuf "log/status"
/* log mail delivery */
extern void
logdelivery(dest *list, char *rcvr, message *mp)
{
dest *parent;
String *srcvr, *sender;
srcvr = unescapespecial(s_copy(rcvr));
sender = unescapespecial(s_clone(mp->sender));
for(parent=list; parent->parent!=0; parent=parent->parent)
;
if(parent!=list && strcmp(s_to_c(parent->addr), s_to_c(srcvr))!=0)
syslog(0, "mail", "delivered %s From %.256s %.256s (%.256s) %d",
rcvr,
s_to_c(sender), s_to_c(mp->date),
s_to_c(parent->addr), mp->size);
else
syslog(0, "mail", "delivered %s From %.256s %.256s %d", s_to_c(srcvr),
s_to_c(sender), s_to_c(mp->date), mp->size);
s_free(srcvr);
s_free(sender);
}
/* log mail forwarding */
extern void
loglist(dest *list, message *mp, char *tag)
{
dest *next;
dest *parent;
String *srcvr, *sender;
sender = unescapespecial(s_clone(mp->sender));
for(next=d_rm(&list); next != 0; next = d_rm(&list)) {
for(parent=next; parent->parent!=0; parent=parent->parent)
;
srcvr = unescapespecial(s_clone(next->addr));
if(parent!=next)
syslog(0, "mail", "%s %.256s From %.256s %.256s (%.256s) %d",
tag,
s_to_c(srcvr), s_to_c(sender),
s_to_c(mp->date), s_to_c(parent->addr), mp->size);
else
syslog(0, "mail", "%s %.256s From %.256s %.256s %d", tag,
s_to_c(srcvr), s_to_c(sender),
s_to_c(mp->date), mp->size);
s_free(srcvr);
}
s_free(sender);
}
/* log a mail refusal */
extern void
logrefusal(dest *dp, message *mp, char *msg)
{
char buf[2048];
char *cp, *ep;
String *sender, *srcvr;
srcvr = unescapespecial(s_clone(dp->addr));
sender = unescapespecial(s_clone(mp->sender));
sprint(buf, "error %.256s From %.256s %.256s\nerror+ ", s_to_c(srcvr),
s_to_c(sender), s_to_c(mp->date));
s_free(srcvr);
s_free(sender);
cp = buf + strlen(buf);
ep = buf + sizeof(buf) - sizeof("error + ");
while(*msg && cp<ep) {
*cp++ = *msg;
if (*msg++ == '\n') {
strcpy(cp, "error+ ");
cp += sizeof("error+ ") - 1;
}
}
*cp = 0;
syslog(0, "mail", "%s", buf);
}

575
src/cmd/upas/send/main.c Normal file
View File

@@ -0,0 +1,575 @@
#include "common.h"
#include "send.h"
/* globals to all files */
int rmail;
char *thissys, *altthissys;
int nflg;
int xflg;
int debug;
int rflg;
int iflg = 1;
int nosummary;
/* global to this file */
static String *errstring;
static message *mp;
static int interrupt;
static int savemail;
static Biobuf in;
static int forked;
static int add822headers = 1;
static String *arglist;
/* predeclared */
static int send(dest *, message *, int);
static void lesstedious(void);
static void save_mail(message *);
static int complain_mail(dest *, message *);
static int pipe_mail(dest *, message *);
static void appaddr(String *, dest *);
static void mkerrstring(String *, message *, dest *, dest *, char *, int);
static int replymsg(String *, message *, dest *);
static int catchint(void*, char*);
void
usage(void)
{
fprint(2, "usage: mail [-birtx] list-of-addresses\n");
exits("usage");
}
void
main(int argc, char *argv[])
{
dest *dp=0;
int checkforward;
char *base;
int rv;
/* process args */
ARGBEGIN{
case '#':
nflg = 1;
break;
case 'b':
add822headers = 0;
break;
case 'x':
nflg = 1;
xflg = 1;
break;
case 'd':
debug = 1;
break;
case 'i':
iflg = 0;
break;
case 'r':
rflg = 1;
break;
default:
usage();
}ARGEND
while(*argv){
if(shellchars(*argv)){
fprint(2, "illegal characters in destination\n");
exits("syntax");
}
d_insert(&dp, d_new(s_copy(*argv++)));
}
if (dp == 0)
usage();
arglist = d_to(dp);
/*
* get context:
* - whether we're rmail or mail
*/
base = basename(argv0);
checkforward = rmail = (strcmp(base, "rmail")==0) | rflg;
thissys = sysname_read();
altthissys = alt_sysname_read();
if(rmail)
add822headers = 0;
/*
* read the mail. If an interrupt occurs while reading, save in
* dead.letter
*/
if (!nflg) {
Binit(&in, 0, OREAD);
if(!rmail)
atnotify(catchint, 1);
mp = m_read(&in, rmail, !iflg);
if (mp == 0)
exit(0);
if (interrupt != 0) {
save_mail(mp);
exit(1);
}
} else {
mp = m_new();
if(default_from(mp) < 0){
fprint(2, "%s: can't determine login name\n", argv0);
exit(1);
}
}
errstring = s_new();
getrules();
/*
* If this is a gateway, translate the sender address into a local
* address. This only happens if mail to the local address is
* forwarded to the sender.
*/
gateway(mp);
/*
* Protect against shell characters in the sender name for
* security reasons.
*/
mp->sender = escapespecial(mp->sender);
if (shellchars(s_to_c(mp->sender)))
mp->replyaddr = s_copy("postmaster");
else
mp->replyaddr = s_clone(mp->sender);
/*
* reject messages that have been looping for too long
*/
if(mp->received > 32)
exit(refuse(dp, mp, "possible forward loop", 0, 0));
/*
* reject messages that are too long. We don't do it earlier
* in m_read since we haven't set up enough things yet.
*/
if(mp->size < 0)
exit(refuse(dp, mp, "message too long", 0, 0));
rv = send(dp, mp, checkforward);
if(savemail)
save_mail(mp);
if(mp)
m_free(mp);
exit(rv);
}
/* send a message to a list of sites */
static int
send(dest *destp, message *mp, int checkforward)
{
dest *dp; /* destination being acted upon */
dest *bound; /* bound destinations */
int errors=0;
/* bind the destinations to actions */
bound = up_bind(destp, mp, checkforward);
if(add822headers && mp->haveto == 0){
if(nosummary)
mp->to = d_to(bound);
else
mp->to = arglist;
}
/* loop through and execute commands */
for (dp = d_rm(&bound); dp != 0; dp = d_rm(&bound)) {
switch (dp->status) {
case d_cat:
errors += cat_mail(dp, mp);
break;
case d_pipeto:
case d_pipe:
if (!rmail && !nflg && !forked) {
forked = 1;
lesstedious();
}
errors += pipe_mail(dp, mp);
break;
default:
errors += complain_mail(dp, mp);
break;
}
}
return errors;
}
/* avoid user tedium (as Mike Lesk said in a previous version) */
static void
lesstedious(void)
{
int i;
if(debug)
return;
switch(fork()){
case -1:
break;
case 0:
sysdetach();
for(i=0; i<3; i++)
close(i);
savemail = 0;
break;
default:
exit(0);
}
}
/* save the mail */
static void
save_mail(message *mp)
{
Biobuf *fp;
String *file;
file = s_new();
deadletter(file);
fp = sysopen(s_to_c(file), "cAt", 0660);
if (fp == 0)
return;
m_bprint(mp, fp);
sysclose(fp);
fprint(2, "saved in %s\n", s_to_c(file));
s_free(file);
}
/* remember the interrupt happened */
static int
catchint(void *a, char *msg)
{
USED(a);
if(strstr(msg, "interrupt") || strstr(msg, "hangup")) {
interrupt = 1;
return 1;
}
return 0;
}
/* dispose of incorrect addresses */
static int
complain_mail(dest *dp, message *mp)
{
char *msg;
switch (dp->status) {
case d_undefined:
msg = "Invalid address"; /* a little different, for debugging */
break;
case d_syntax:
msg = "invalid address";
break;
case d_unknown:
msg = "unknown user";
break;
case d_eloop:
case d_loop:
msg = "forwarding loop";
break;
case d_noforward:
if(dp->pstat && *s_to_c(dp->repl2))
return refuse(dp, mp, s_to_c(dp->repl2), dp->pstat, 0);
else
msg = "destination unknown or forwarding disallowed";
break;
case d_pipe:
msg = "broken pipe";
break;
case d_cat:
msg = "broken cat";
break;
case d_translate:
if(dp->pstat && *s_to_c(dp->repl2))
return refuse(dp, mp, s_to_c(dp->repl2), dp->pstat, 0);
else
msg = "name translation failed";
break;
case d_alias:
msg = "broken alias";
break;
case d_badmbox:
msg = "corrupted mailbox";
break;
case d_resource:
return refuse(dp, mp, "out of some resource. Try again later.", 0, 1);
default:
msg = "unknown d_";
break;
}
if (nflg) {
print("%s: %s\n", msg, s_to_c(dp->addr));
return 0;
}
return refuse(dp, mp, msg, 0, 0);
}
/* dispose of remote addresses */
static int
pipe_mail(dest *dp, message *mp)
{
dest *next, *list=0;
String *cmd;
process *pp;
int status;
char *none;
String *errstring=s_new();
if (dp->status == d_pipeto)
none = "none";
else
none = 0;
/*
* collect the arguments
*/
next = d_rm_same(&dp);
if(xflg)
cmd = s_new();
else
cmd = s_clone(next->repl1);
for(; next != 0; next = d_rm_same(&dp)){
if(xflg){
s_append(cmd, s_to_c(next->addr));
s_append(cmd, "\n");
} else {
if (next->repl2 != 0) {
s_append(cmd, " ");
s_append(cmd, s_to_c(next->repl2));
}
}
d_insert(&list, next);
}
if (nflg) {
if(xflg)
print("%s", s_to_c(cmd));
else
print("%s\n", s_to_c(cmd));
s_free(cmd);
return 0;
}
/*
* run the process
*/
pp = proc_start(s_to_c(cmd), instream(), 0, outstream(), 1, none);
if(pp==0 || pp->std[0]==0 || pp->std[2]==0)
return refuse(list, mp, "out of processes, pipes, or memory", 0, 1);
pipesig(0);
m_print(mp, pp->std[0]->fp, thissys, 0);
pipesigoff();
stream_free(pp->std[0]);
pp->std[0] = 0;
while(s_read_line(pp->std[2]->fp, errstring))
;
status = proc_wait(pp);
proc_free(pp);
s_free(cmd);
/*
* return status
*/
if (status != 0)
return refuse(list, mp, s_to_c(errstring), status, 0);
loglist(list, mp, "remote");
return 0;
}
static void
appaddr(String *sp, dest *dp)
{
dest *parent;
String *s;
if (dp->parent != 0) {
for(parent=dp->parent; parent->parent!=0; parent=parent->parent)
;
s = unescapespecial(s_clone(parent->addr));
s_append(sp, s_to_c(s));
s_free(s);
s_append(sp, "' alias `");
}
s = unescapespecial(s_clone(dp->addr));
s_append(sp, s_to_c(s));
s_free(s);
}
/*
* reject delivery
*
* returns 0 - if mail has been disposed of
* other - if mail has not been disposed
*/
int
refuse(dest *list, message *mp, char *cp, int status, int outofresources)
{
String *errstring=s_new();
dest *dp;
int rv;
dp = d_rm(&list);
mkerrstring(errstring, mp, dp, list, cp, status);
/*
* log first in case we get into trouble
*/
logrefusal(dp, mp, s_to_c(errstring));
/*
* bulk mail is never replied to, if we're out of resources,
* let the sender try again
*/
if(rmail){
/* accept it or request a retry */
if(outofresources){
fprint(2, "Mail %s\n", s_to_c(errstring));
rv = 1; /* try again later */
} else if(mp->bulk)
rv = 0; /* silently discard bulk */
else
rv = replymsg(errstring, mp, dp); /* try later if we can't reply */
} else {
/* aysnchronous delivery only happens if !rmail */
if(forked){
/*
* if spun off for asynchronous delivery, we own the mail now.
* return it or dump it on the floor. rv really doesn't matter.
*/
rv = 0;
if(!outofresources && !mp->bulk)
replymsg(errstring, mp, dp);
} else {
fprint(2, "Mail %s\n", s_to_c(errstring));
savemail = 1;
rv = 1;
}
}
s_free(errstring);
return rv;
}
/* make the error message */
static void
mkerrstring(String *errstring, message *mp, dest *dp, dest *list, char *cp, int status)
{
dest *next;
char smsg[64];
String *sender;
sender = unescapespecial(s_clone(mp->sender));
/* list all aliases */
s_append(errstring, " from '");
s_append(errstring, s_to_c(sender));
s_append(errstring, "'\nto '");
appaddr(errstring, dp);
for(next = d_rm(&list); next != 0; next = d_rm(&list)) {
s_append(errstring, "'\nand '");
appaddr(errstring, next);
d_insert(&dp, next);
}
s_append(errstring, "'\nfailed with error '");
s_append(errstring, cp);
s_append(errstring, "'.\n");
/* >> and | deserve different flavored messages */
switch(dp->status) {
case d_pipe:
s_append(errstring, "The mailer `");
s_append(errstring, s_to_c(dp->repl1));
sprint(smsg, "' returned error status %x.\n\n", status);
s_append(errstring, smsg);
break;
}
s_free(sender);
}
/*
* create a new boundary
*/
static String*
mkboundary(void)
{
char buf[32];
int i;
static int already;
if(already == 0){
srand((time(0)<<16)|getpid());
already = 1;
}
strcpy(buf, "upas-");
for(i = 5; i < sizeof(buf)-1; i++)
buf[i] = 'a' + nrand(26);
buf[i] = 0;
return s_copy(buf);
}
/*
* reply with up to 1024 characters of the
* original message
*/
static int
replymsg(String *errstring, message *mp, dest *dp)
{
message *refp = m_new();
dest *ndp;
char *rcvr;
int rv;
String *boundary;
boundary = mkboundary();
refp->bulk = 1;
refp->rfc822headers = 1;
rcvr = dp->status==d_eloop ? "postmaster" : s_to_c(mp->replyaddr);
ndp = d_new(s_copy(rcvr));
s_append(refp->sender, "postmaster");
s_append(refp->replyaddr, "/dev/null");
s_append(refp->date, thedate());
refp->haveto = 1;
s_append(refp->body, "To: ");
s_append(refp->body, rcvr);
s_append(refp->body, "\n");
s_append(refp->body, "Subject: bounced mail\n");
s_append(refp->body, "MIME-Version: 1.0\n");
s_append(refp->body, "Content-Type: multipart/mixed;\n");
s_append(refp->body, "\tboundary=\"");
s_append(refp->body, s_to_c(boundary));
s_append(refp->body, "\"\n");
s_append(refp->body, "Content-Disposition: inline\n");
s_append(refp->body, "\n");
s_append(refp->body, "This is a multi-part message in MIME format.\n");
s_append(refp->body, "--");
s_append(refp->body, s_to_c(boundary));
s_append(refp->body, "\n");
s_append(refp->body, "Content-Disposition: inline\n");
s_append(refp->body, "Content-Type: text/plain; charset=\"US-ASCII\"\n");
s_append(refp->body, "Content-Transfer-Encoding: 7bit\n");
s_append(refp->body, "\n");
s_append(refp->body, "The attached mail");
s_append(refp->body, s_to_c(errstring));
s_append(refp->body, "--");
s_append(refp->body, s_to_c(boundary));
s_append(refp->body, "\n");
s_append(refp->body, "Content-Type: message/rfc822\n");
s_append(refp->body, "Content-Disposition: inline\n\n");
s_append(refp->body, s_to_c(mp->body));
s_append(refp->body, "--");
s_append(refp->body, s_to_c(boundary));
s_append(refp->body, "--\n");
refp->size = s_len(refp->body);
rv = send(ndp, refp, 0);
m_free(refp);
d_free(ndp);
return rv;
}

View File

@@ -0,0 +1,46 @@
SSRC= message.c main.c bind.c rewrite.c local.c dest.c process.c translate.c\
log.c chkfwd.c notify.c gateway.c authorize.o ../common/*.c
SOBJ= message.o main.o bind.o rewrite.o local.o dest.o process.o translate.o\
log.o chkfwd.o notify.o gateway.o authorize.o\
../config/config.o ../common/common.a ../libc/libc.a
SINC= ../common/mail.h ../common/string.h ../common/aux.h
CFLAGS=${UNIX} -g -I. -I../libc -I../common -I/usr/include ${SCFLAGS}
LFLAGS=-g
.c.o: ; $(CC) -c $(CFLAGS) $*.c
LIB=/usr/lib/upas
all: send
send: $(SOBJ)
$(CC) $(SOBJ) $(LFLAGS) -o send
chkfwd.o: $(SINC) message.h dest.h
dest.o: $(SINC) dest.h
local.o: $(SINC) dest.h process.h
log.o: $(SINC) message.h
main.o: $(SINC) message.h dest.h process.h
bind.o: $(SINC) dest.h message.h
process.o: $(SINC) process.h
rewrite.o: $(SINC) dest.h
translate.o: $(SINC) dest.h process.h
message.o: $(SINC) message.h
notify.o: $(SINC) message.h
gateway.o: $(SINC) dest.h message.h
prcan:
prcan $(SSRC)
clean:
-rm -f send *.[oO] a.out core *.sL rmail
cyntax:
cyntax $(CFLAGS) $(SSRC)
install: send
rm -f $(LIB)/send /bin/rmail
cp send $(LIB)/send
cp send /bin/rmail
strip /bin/rmail
strip $(LIB)/send
chown root $(LIB)/send /bin/rmail
chmod 4755 $(LIB)/send /bin/rmail

573
src/cmd/upas/send/message.c Normal file
View File

@@ -0,0 +1,573 @@
#include "common.h"
#include "send.h"
#include "../smtp/smtp.h"
#include "../smtp/y.tab.h"
/* global to this file */
static Reprog *rfprog;
static Reprog *fprog;
#define VMLIMIT (64*1024)
#define MSGLIMIT (128*1024*1024)
int received; /* from rfc822.y */
static String* getstring(Node *p);
static String* getaddr(Node *p);
extern int
default_from(message *mp)
{
char *cp, *lp;
cp = getenv("upasname");
lp = getlog();
if(lp == nil)
return -1;
if(cp && *cp)
s_append(mp->sender, cp);
else
s_append(mp->sender, lp);
s_append(mp->date, thedate());
return 0;
}
extern message *
m_new(void)
{
message *mp;
mp = (message *)mallocz(sizeof(message), 1);
if (mp == 0) {
perror("message:");
exit(1);
}
mp->sender = s_new();
mp->replyaddr = s_new();
mp->date = s_new();
mp->body = s_new();
mp->size = 0;
mp->fd = -1;
return mp;
}
extern void
m_free(message *mp)
{
if(mp->fd >= 0){
close(mp->fd);
sysremove(s_to_c(mp->tmp));
s_free(mp->tmp);
}
s_free(mp->sender);
s_free(mp->date);
s_free(mp->body);
s_free(mp->havefrom);
s_free(mp->havesender);
s_free(mp->havereplyto);
s_free(mp->havesubject);
free((char *)mp);
}
/* read a message into a temp file , return an open fd to it */
static int
m_read_to_file(Biobuf *fp, message *mp)
{
int fd;
int n;
String *file;
char buf[4*1024];
file = s_new();
/*
* create temp file to be remove on close
*/
abspath("mtXXXXXX", UPASTMP, file);
mktemp(s_to_c(file));
if((fd = syscreate(s_to_c(file), ORDWR|ORCLOSE, 0600))<0){
s_free(file);
return -1;
}
mp->tmp = file;
/*
* read the rest into the temp file
*/
while((n = Bread(fp, buf, sizeof(buf))) > 0){
if(write(fd, buf, n) != n){
close(fd);
return -1;
}
mp->size += n;
if(mp->size > MSGLIMIT){
mp->size = -1;
break;
}
}
mp->fd = fd;
return 0;
}
/* get the first address from a node */
static String*
getaddr(Node *p)
{
for(; p; p = p->next)
if(p->s && p->addr)
return s_copy(s_to_c(p->s));
}
/* get the text of a header line minus the field name */
static String*
getstring(Node *p)
{
String *s;
s = s_new();
if(p == nil)
return s;
for(p = p->next; p; p = p->next){
if(p->s){
s_append(s, s_to_c(p->s));
}else{
s_putc(s, p->c);
s_terminate(s);
}
if(p->white)
s_append(s, s_to_c(p->white));
}
return s;
}
#if 0 /* jpc */
static char *fieldname[] =
{
[WORD-WORD] "WORD",
[DATE-WORD] "DATE",
[RESENT_DATE-WORD] "RESENT_DATE",
[RETURN_PATH-WORD] "RETURN_PATH",
[FROM-WORD] "FROM",
[SENDER-WORD] "SENDER",
[REPLY_TO-WORD] "REPLY_TO",
[RESENT_FROM-WORD] "RESENT_FROM",
[RESENT_SENDER-WORD] "RESENT_SENDER",
[RESENT_REPLY_TO-WORD] "RESENT_REPLY_TO",
[SUBJECT-WORD] "SUBJECT",
[TO-WORD] "TO",
[CC-WORD] "CC",
[BCC-WORD] "BCC",
[RESENT_TO-WORD] "RESENT_TO",
[RESENT_CC-WORD] "RESENT_CC",
[RESENT_BCC-WORD] "RESENT_BCC",
[REMOTE-WORD] "REMOTE",
[PRECEDENCE-WORD] "PRECEDENCE",
[MIMEVERSION-WORD] "MIMEVERSION",
[CONTENTTYPE-WORD] "CONTENTTYPE",
[MESSAGEID-WORD] "MESSAGEID",
[RECEIVED-WORD] "RECEIVED",
[MAILER-WORD] "MAILER",
[BADTOKEN-WORD] "BADTOKEN",
};
#endif /* jpc */
/* fix 822 addresses */
static void
rfc822cruft(message *mp)
{
Field *f;
Node *p;
String *body, *s;
char *cp;
/*
* parse headers in in-core part
*/
yyinit(s_to_c(mp->body), s_len(mp->body));
mp->rfc822headers = 0;
yyparse();
mp->rfc822headers = 1;
mp->received = received;
/*
* remove equivalent systems in all addresses
*/
body = s_new();
cp = s_to_c(mp->body);
for(f = firstfield; f; f = f->next){
if(f->node->c == MIMEVERSION)
mp->havemime = 1;
if(f->node->c == FROM)
mp->havefrom = getaddr(f->node);
if(f->node->c == SENDER)
mp->havesender = getaddr(f->node);
if(f->node->c == REPLY_TO)
mp->havereplyto = getaddr(f->node);
if(f->node->c == TO)
mp->haveto = 1;
if(f->node->c == DATE)
mp->havedate = 1;
if(f->node->c == SUBJECT)
mp->havesubject = getstring(f->node);
if(f->node->c == PRECEDENCE && f->node->next && f->node->next->next){
s = f->node->next->next->s;
if(s && (strcmp(s_to_c(s), "bulk") == 0
|| strcmp(s_to_c(s), "Bulk") == 0))
mp->bulk = 1;
}
for(p = f->node; p; p = p->next){
if(p->s){
if(p->addr){
cp = skipequiv(s_to_c(p->s));
s_append(body, cp);
} else
s_append(body, s_to_c(p->s));
}else{
s_putc(body, p->c);
s_terminate(body);
}
if(p->white)
s_append(body, s_to_c(p->white));
cp = p->end+1;
}
s_append(body, "\n");
}
if(*s_to_c(body) == 0){
s_free(body);
return;
}
if(*cp != '\n')
s_append(body, "\n");
s_memappend(body, cp, s_len(mp->body) - (cp - s_to_c(mp->body)));
s_terminate(body);
firstfield = 0;
mp->size += s_len(body) - s_len(mp->body);
s_free(mp->body);
mp->body = body;
}
/* read in a message, interpret the 'From' header */
extern message *
m_read(Biobuf *fp, int rmail, int interactive)
{
message *mp;
Resub subexp[10];
char *line;
int first;
int n;
mp = m_new();
/* parse From lines if remote */
if (rmail) {
/* get remote address */
String *sender=s_new();
if (rfprog == 0)
rfprog = regcomp(REMFROMRE);
first = 1;
while(s_read_line(fp, s_restart(mp->body)) != 0) {
memset(subexp, 0, sizeof(subexp));
if (regexec(rfprog, s_to_c(mp->body), subexp, 10) == 0){
if(first == 0)
break;
if (fprog == 0)
fprog = regcomp(FROMRE);
memset(subexp, 0, sizeof(subexp));
if(regexec(fprog, s_to_c(mp->body), subexp,10) == 0)
break;
s_restart(mp->body);
append_match(subexp, s_restart(sender), SENDERMATCH);
append_match(subexp, s_restart(mp->date), DATEMATCH);
break;
}
append_match(subexp, s_restart(sender), REMSENDERMATCH);
append_match(subexp, s_restart(mp->date), REMDATEMATCH);
if(subexp[REMSYSMATCH].s.sp!=subexp[REMSYSMATCH].e.ep){
append_match(subexp, mp->sender, REMSYSMATCH);
s_append(mp->sender, "!");
}
first = 0;
}
s_append(mp->sender, s_to_c(sender));
s_free(sender);
}
if(*s_to_c(mp->sender)=='\0')
default_from(mp);
/* if sender address is unreturnable, treat message as bulk mail */
if(!returnable(s_to_c(mp->sender)))
mp->bulk = 1;
/* get body */
if(interactive && !rmail){
/* user typing on terminal: terminator == '.' or EOF */
for(;;) {
line = s_read_line(fp, mp->body);
if (line == 0)
break;
if (strcmp(".\n", line)==0) {
mp->body->ptr -= 2;
*mp->body->ptr = '\0';
break;
}
}
mp->size = mp->body->ptr - mp->body->base;
} else {
/*
* read up to VMLIMIT bytes (more or less) into main memory.
* if message is longer put the rest in a tmp file.
*/
mp->size = mp->body->ptr - mp->body->base;
n = s_read(fp, mp->body, VMLIMIT);
if(n < 0){
perror("m_read");
exit(1);
}
mp->size += n;
if(n == VMLIMIT){
if(m_read_to_file(fp, mp) < 0){
perror("m_read");
exit(1);
}
}
}
/*
* ignore 0 length messages from a terminal
*/
if (!rmail && mp->size == 0)
return 0;
rfc822cruft(mp);
return mp;
}
/* return a piece of message starting at `offset' */
extern int
m_get(message *mp, long offset, char **pp)
{
static char buf[4*1024];
/*
* are we past eof?
*/
if(offset >= mp->size)
return 0;
/*
* are we in the virtual memory portion?
*/
if(offset < s_len(mp->body)){
*pp = mp->body->base + offset;
return mp->body->ptr - mp->body->base - offset;
}
/*
* read it from the temp file
*/
offset -= s_len(mp->body);
if(mp->fd < 0)
return -1;
if(seek(mp->fd, offset, 0)<0)
return -1;
*pp = buf;
return read(mp->fd, buf, sizeof buf);
}
/* output the message body without ^From escapes */
static int
m_noescape(message *mp, Biobuf *fp)
{
long offset;
int n;
char *p;
for(offset = 0; offset < mp->size; offset += n){
n = m_get(mp, offset, &p);
if(n <= 0){
Bflush(fp);
return -1;
}
if(Bwrite(fp, p, n) < 0)
return -1;
}
return Bflush(fp);
}
/*
* Output the message body with '^From ' escapes.
* Ensures that any line starting with a 'From ' gets a ' ' stuck
* in front of it.
*/
static int
m_escape(message *mp, Biobuf *fp)
{
char *p, *np;
char *end;
long offset;
int m, n;
char *start;
for(offset = 0; offset < mp->size; offset += n){
n = m_get(mp, offset, &start);
if(n < 0){
Bflush(fp);
return -1;
}
p = start;
for(end = p+n; p < end; p += m){
np = memchr(p, '\n', end-p);
if(np == 0){
Bwrite(fp, p, end-p);
break;
}
m = np - p + 1;
if(m > 5 && strncmp(p, "From ", 5) == 0)
Bputc(fp, ' ');
Bwrite(fp, p, m);
}
}
Bflush(fp);
return 0;
}
static int
printfrom(message *mp, Biobuf *fp)
{
String *s;
int rv;
if(!returnable(s_to_c(mp->sender)))
return Bprint(fp, "From: Postmaster\n");
s = username(mp->sender);
if(s) {
s_append(s, " <");
s_append(s, s_to_c(mp->sender));
s_append(s, ">");
} else {
s = s_copy(s_to_c(mp->sender));
}
s = unescapespecial(s);
rv = Bprint(fp, "From: %s\n", s_to_c(s));
s_free(s);
return rv;
}
static char *
rewritezone(char *z)
{
int mindiff;
char s;
Tm *tm;
static char x[7];
tm = localtime(time(0));
mindiff = tm->tzoff/60;
/* if not in my timezone, don't change anything */
if(strcmp(tm->zone, z) != 0)
return z;
if(mindiff < 0){
s = '-';
mindiff = -mindiff;
} else
s = '+';
sprint(x, "%c%.2d%.2d", s, mindiff/60, mindiff%60);
return x;
}
int
isutf8(String *s)
{
char *p;
for(p = s_to_c(s); *p; p++)
if(*p&0x80)
return 1;
return 0;
}
void
printutf8mime(Biobuf *b)
{
Bprint(b, "MIME-Version: 1.0\n");
Bprint(b, "Content-Type: text/plain; charset=\"UTF-8\"\n");
Bprint(b, "Content-Transfer-Encoding: 8bit\n");
}
/* output a message */
extern int
m_print(message *mp, Biobuf *fp, char *remote, int mbox)
{
String *date, *sender;
char *f[6];
int n;
sender = unescapespecial(s_clone(mp->sender));
if (remote != 0){
if(print_remote_header(fp,s_to_c(sender),s_to_c(mp->date),remote) < 0){
s_free(sender);
return -1;
}
} else {
if(print_header(fp, s_to_c(sender), s_to_c(mp->date)) < 0){
s_free(sender);
return -1;
}
}
s_free(sender);
if(!rmail && !mp->havedate){
/* add a date: line Date: Sun, 19 Apr 1998 12:27:52 -0400 */
date = s_copy(s_to_c(mp->date));
n = getfields(s_to_c(date), f, 6, 1, " \t");
if(n == 6)
Bprint(fp, "Date: %s, %s %s %s %s %s\n", f[0], f[2], f[1],
f[5], f[3], rewritezone(f[4]));
}
if(!rmail && !mp->havemime && isutf8(mp->body))
printutf8mime(fp);
if(mp->to){
/* add the to: line */
if (Bprint(fp, "%s\n", s_to_c(mp->to)) < 0)
return -1;
/* add the from: line */
if (!mp->havefrom && printfrom(mp, fp) < 0)
return -1;
if(!mp->rfc822headers && *s_to_c(mp->body) != '\n')
if (Bprint(fp, "\n") < 0)
return -1;
} else if(!rmail){
/* add the from: line */
if (!mp->havefrom && printfrom(mp, fp) < 0)
return -1;
if(!mp->rfc822headers && *s_to_c(mp->body) != '\n')
if (Bprint(fp, "\n") < 0)
return -1;
}
if (!mbox)
return m_noescape(mp, fp);
return m_escape(mp, fp);
}
/* print just the message body */
extern int
m_bprint(message *mp, Biobuf *fp)
{
return m_noescape(mp, fp);
}

52
src/cmd/upas/send/mkfile Normal file
View File

@@ -0,0 +1,52 @@
<$PLAN9/src/mkhdr
TARG=send\
filter
UOFILES=message.$O\
dest.$O\
log.$O\
skipequiv.$O\
OFILES=\
$UOFILES\
../smtp/rfc822.tab.$O\
SMOBJ=main.$O\
bind.$O\
rewrite.$O\
local.$O\
translate.$O\
authorize.$O\
gateway.$O\
cat_mail.$O\
LIB=../common/libcommon.av\
HFILES=send.h\
../common/common.h\
../common/sys.h\
LIB=../common/libcommon.a\
BIN=$PLAN9/bin/upas
UPDATE=\
mkfile\
$HFILES\
${UOFILES:%.$O=%.c}\
${SMOBJ:%.$O=%.c}\
${TARG:%=%.c}\
<$PLAN9/src/mkmany
CFLAGS=$CFLAGS -I../common
$O.send: $SMOBJ $OFILES
$LD $LDFLAGS -o $target $prereq $LIB
message.$O: ../smtp/y.tab.h
../smtp/y.tab.h ../smtp/rfc822.tab.$O: ../smtp/rfc822.y
# @{
cd ../smtp
mk rfc822.tab.$O
# }

View File

@@ -0,0 +1,36 @@
#include <u.h>
#include <libc.h>
#include <regexp.h>
#include <bio.h>
main(void)
{
char *re;
char *line;
Reprog *prog;
char *cp;
Biobuf in;
Binit(&in, 0, OREAD);
print("re> ");
while(re = Brdline(&in, '\n')){
re[Blinelen(&in)-1] = 0;
if(*re == 0)
break;
prog = regcomp(re);
print("> ");
while(line = Brdline(&in, '\n')){
line[Blinelen(&in)-1] = 0;
if(cp = strchr(line, '\n'))
*cp = 0;
if(*line == 0)
break;
if(regexec(prog, line, 0))
print("yes\n");
else
print("no\n");
print("> ");
}
print("re> ");
}
}

315
src/cmd/upas/send/rewrite.c Normal file
View File

@@ -0,0 +1,315 @@
#include "common.h"
#include "send.h"
extern int debug;
/*
* Routines for dealing with the rewrite rules.
*/
/* globals */
typedef struct rule rule;
#define NSUBEXP 10
struct rule {
String *matchre; /* address match */
String *repl1; /* first replacement String */
String *repl2; /* second replacement String */
d_status type; /* type of rule */
Reprog *program;
Resub subexp[NSUBEXP];
rule *next;
};
static rule *rulep;
static rule *rlastp;
/* predeclared */
static String *substitute(String *, Resub *, message *);
static rule *findrule(String *, int);
/*
* Get the next token from `line'. The symbol `\l' is replaced by
* the name of the local system.
*/
extern String *
rule_parse(String *line, char *system, int *backl)
{
String *token;
String *expanded;
char *cp;
token = s_parse(line, 0);
if(token == 0)
return(token);
if(strchr(s_to_c(token), '\\')==0)
return(token);
expanded = s_new();
for(cp = s_to_c(token); *cp; cp++) {
if(*cp == '\\') switch(*++cp) {
case 'l':
s_append(expanded, system);
*backl = 1;
break;
case '\\':
s_putc(expanded, '\\');
break;
default:
s_putc(expanded, '\\');
s_putc(expanded, *cp);
break;
} else
s_putc(expanded, *cp);
}
s_free(token);
s_terminate(expanded);
return(expanded);
}
static int
getrule(String *line, String *type, char *system)
{
rule *rp;
String *re;
int backl;
backl = 0;
/* get a rule */
re = rule_parse(s_restart(line), system, &backl);
if(re == 0)
return 0;
rp = (rule *)malloc(sizeof(rule));
if(rp == 0) {
perror("getrules:");
exit(1);
}
rp->next = 0;
s_tolower(re);
rp->matchre = s_new();
s_append(rp->matchre, s_to_c(re));
s_restart(rp->matchre);
s_free(re);
s_parse(line, s_restart(type));
rp->repl1 = rule_parse(line, system, &backl);
rp->repl2 = rule_parse(line, system, &backl);
rp->program = 0;
if(strcmp(s_to_c(type), "|") == 0)
rp->type = d_pipe;
else if(strcmp(s_to_c(type), ">>") == 0)
rp->type = d_cat;
else if(strcmp(s_to_c(type), "alias") == 0)
rp->type = d_alias;
else if(strcmp(s_to_c(type), "translate") == 0)
rp->type = d_translate;
else if(strcmp(s_to_c(type), "auth") == 0)
rp->type = d_auth;
else {
s_free(rp->matchre);
s_free(rp->repl1);
s_free(rp->repl2);
free((char *)rp);
fprint(2,"illegal rewrite rule: %s\n", s_to_c(line));
return 0;
}
if(rulep == 0)
rulep = rlastp = rp;
else
rlastp = rlastp->next = rp;
return backl;
}
/*
* rules are of the form:
* <reg exp> <String> <repl exp> [<repl exp>]
*/
extern int
getrules(void)
{
Biobuf *rfp;
String *line;
String *type;
String *file;
file = abspath("rewrite", unsharp(UPASLIB), (String *)0);
rfp = sysopen(s_to_c(file), "r", 0);
if(rfp == 0) {
rulep = 0;
return -1;
}
rlastp = 0;
line = s_new();
type = s_new();
while(s_getline(rfp, s_restart(line)))
if(getrule(line, type, thissys) && altthissys)
getrule(s_restart(line), type, altthissys);
s_free(type);
s_free(line);
s_free(file);
sysclose(rfp);
return 0;
}
/* look up a matching rule */
static rule *
findrule(String *addrp, int authorized)
{
rule *rp;
static rule defaultrule;
if(rulep == 0)
return &defaultrule;
for (rp = rulep; rp != 0; rp = rp->next) {
if(rp->type==d_auth && authorized)
continue;
if(rp->program == 0)
rp->program = regcomp(rp->matchre->base);
if(rp->program == 0)
continue;
memset(rp->subexp, 0, sizeof(rp->subexp));
if(debug)
print("matching %s aginst %s\n", s_to_c(addrp), rp->matchre->base);
if(regexec(rp->program, s_to_c(addrp), rp->subexp, NSUBEXP))
if(s_to_c(addrp) == rp->subexp[0].s.sp)
if((s_to_c(addrp) + strlen(s_to_c(addrp))) == rp->subexp[0].e.ep)
return rp;
}
return 0;
}
/* Transforms the address into a command.
* Returns: -1 ifaddress not matched by reules
* 0 ifaddress matched and ok to forward
* 1 ifaddress matched and not ok to forward
*/
extern int
rewrite(dest *dp, message *mp)
{
rule *rp; /* rewriting rule */
String *lower; /* lower case version of destination */
/*
* Rewrite the address. Matching is case insensitive.
*/
lower = s_clone(dp->addr);
s_tolower(s_restart(lower));
rp = findrule(lower, dp->authorized);
if(rp == 0){
s_free(lower);
return -1;
}
strcpy(s_to_c(lower), s_to_c(dp->addr));
dp->repl1 = substitute(rp->repl1, rp->subexp, mp);
dp->repl2 = substitute(rp->repl2, rp->subexp, mp);
dp->status = rp->type;
if(debug){
print("\t->");
if(dp->repl1)
print("%s", s_to_c(dp->repl1));
if(dp->repl2)
print("%s", s_to_c(dp->repl2));
print("\n");
}
s_free(lower);
return 0;
}
static String *
substitute(String *source, Resub *subexp, message *mp)
{
int i;
char *s;
char *sp;
String *stp;
if(source == 0)
return 0;
sp = s_to_c(source);
/* someplace to put it */
stp = s_new();
/* do the substitution */
while (*sp != '\0') {
if(*sp == '\\') {
switch (*++sp) {
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
i = *sp-'0';
if(subexp[i].s.sp != 0)
for (s = subexp[i].s.sp;
s < subexp[i].e.ep;
s++)
s_putc(stp, *s);
break;
case '\\':
s_putc(stp, '\\');
break;
case '\0':
sp--;
break;
case 's':
for(s = s_to_c(mp->replyaddr); *s; s++)
s_putc(stp, *s);
break;
case 'p':
if(mp->bulk)
s = "bulk";
else
s = "normal";
for(;*s; s++)
s_putc(stp, *s);
break;
default:
s_putc(stp, *sp);
break;
}
} else if(*sp == '&') {
if(subexp[0].s.sp != 0)
for (s = subexp[0].s.sp;
s < subexp[0].e.ep; s++)
s_putc(stp, *s);
} else
s_putc(stp, *sp);
sp++;
}
s_terminate(stp);
return s_restart(stp);
}
extern void
regerror(char* s)
{
fprint(2, "rewrite: %s\n", s);
}
extern void
dumprules(void)
{
rule *rp;
for (rp = rulep; rp != 0; rp = rp->next) {
fprint(2, "'%s'", rp->matchre->base);
switch (rp->type) {
case d_pipe:
fprint(2, " |");
break;
case d_cat:
fprint(2, " >>");
break;
case d_alias:
fprint(2, " alias");
break;
case d_translate:
fprint(2, " translate");
break;
default:
fprint(2, " UNKNOWN");
break;
}
fprint(2, " '%s'", rp->repl1 ? rp->repl1->base:"...");
fprint(2, " '%s'\n", rp->repl2 ? rp->repl2->base:"...");
}
}

108
src/cmd/upas/send/send.h Normal file
View File

@@ -0,0 +1,108 @@
#define MAXSAME 16
#define MAXSAMECHAR 1024
/* status of a destination*/
typedef enum {
d_undefined, /* address has not been matched*/
d_pipe, /* repl1|repl2 == delivery command, rep*/
d_cat, /* repl1 == mail file */
d_translate, /* repl1 == translation command*/
d_alias, /* repl1 == translation*/
d_auth, /* repl1 == command to authorize*/
d_syntax, /* addr contains illegal characters*/
d_unknown, /* addr does not match a rewrite rule*/
d_loop, /* addressing loop*/
d_eloop, /* external addressing loop*/
d_noforward, /* forwarding not allowed*/
d_badmbox, /* mailbox badly formatted*/
d_resource, /* ran out of something we needed*/
d_pipeto, /* pipe to from a mailbox*/
} d_status;
/* a destination*/
typedef struct dest dest;
struct dest {
dest *next; /* for chaining*/
dest *same; /* dests with same cmd*/
dest *parent; /* destination we're a translation of*/
String *addr; /* destination address*/
String *repl1; /* substitution field 1*/
String *repl2; /* substitution field 2*/
int pstat; /* process status*/
d_status status; /* delivery status*/
int authorized; /* non-zero if we have been authorized*/
int nsame; /* number of same dests chained to this entry*/
int nchar; /* number of characters in the command*/
};
typedef struct message message;
struct message {
String *sender;
String *replyaddr;
String *date;
String *body;
String *tmp; /* name of temp file */
String *to;
int size;
int fd; /* if >= 0, the file the message is stored in*/
char haveto;
String *havefrom;
String *havesender;
String *havereplyto;
char havedate;
char havemime;
String *havesubject;
char bulk; /* if Precedence: Bulk in header */
char rfc822headers;
int received; /* number of received lines */
char *boundary; /* bondary marker for attachments */
};
/*
* exported variables
*/
extern int rmail;
extern int onatty;
extern char *thissys, *altthissys;
extern int xflg;
extern int nflg;
extern int tflg;
extern int debug;
extern int nosummary;
/*
* exported procedures
*/
extern void authorize(dest*);
extern int cat_mail(dest*, message*);
extern dest *up_bind(dest*, message*, int);
extern int ok_to_forward(char*);
extern int lookup(char*, char*, Biobuf**, char*, Biobuf**);
extern dest *d_new(String*);
extern void d_free(dest*);
extern dest *d_rm(dest**);
extern void d_insert(dest**, dest*);
extern dest *d_rm_same(dest**);
extern void d_same_insert(dest**, dest*);
extern String *d_to(dest*);
extern dest *s_to_dest(String*, dest*);
extern void gateway(message*);
extern dest *expand_local(dest*);
extern void logdelivery(dest*, char*, message*);
extern void loglist(dest*, message*, char*);
extern void logrefusal(dest*, message*, char*);
extern int default_from(message*);
extern message *m_new(void);
extern void m_free(message*);
extern message *m_read(Biobuf*, int, int);
extern int m_get(message*, long, char**);
extern int m_print(message*, Biobuf*, char*, int);
extern int m_bprint(message*, Biobuf*);
extern String *rule_parse(String*, char*, int*);
extern int getrules(void);
extern int rewrite(dest*, message*);
extern void dumprules(void);
extern void regerror(char*);
extern dest *translate(dest*);
extern char* skipequiv(char*);
extern int refuse(dest*, message*, char*, int, int);

View File

@@ -0,0 +1,93 @@
#include "common.h"
#include "send.h"
#undef isspace
#define isspace(c) ((c)==' ' || (c)=='\t' || (c)=='\n')
/*
* skip past all systems in equivlist
*/
extern char*
skipequiv(char *base)
{
char *sp;
static Biobuf *fp;
while(*base){
sp = strchr(base, '!');
if(sp==0)
break;
*sp = '\0';
if(lookup(base, "equivlist", &fp, 0, 0)==1){
/* found or us, forget this system */
*sp='!';
base=sp+1;
} else {
/* no files or system is not found, and not us */
*sp='!';
break;
}
}
return base;
}
static int
okfile(char *cp, Biobuf *fp)
{
char *buf;
int len;
char *bp, *ep;
int c;
len = strlen(cp);
Bseek(fp, 0, 0);
/* one iteration per system name in the file */
while(buf = Brdline(fp, '\n')) {
ep = &buf[Blinelen(fp)];
for(bp=buf; bp < ep;){
while(isspace(*bp) || *bp==',')
bp++;
if(strncmp(bp, cp, len) == 0) {
c = *(bp+len);
if(isspace(c) || c==',')
return 1;
}
while(bp < ep && (!isspace(*bp)) && *bp!=',')
bp++;
}
}
/* didn't find it, prohibit forwarding */
return 0;
}
/* return 1 if name found in one of the files
* 0 if name not found in one of the files
* -1 if neither file exists
*/
extern int
lookup(char *cp, char *local, Biobuf **lfpp, char *global, Biobuf **gfpp)
{
static String *file = 0;
if (local) {
if (file == 0)
file = s_new();
abspath(local, UPASLIB, s_restart(file));
if (*lfpp != 0 || (*lfpp = sysopen(s_to_c(file), "r", 0)) != 0) {
if (okfile(cp, *lfpp))
return 1;
} else
local = 0;
}
if (global) {
abspath(global, UPASLIB, s_restart(file));
if (*gfpp != 0 || (*gfpp = sysopen(s_to_c(file), "r", 0)) != 0) {
if (okfile(cp, *gfpp))
return 1;
} else
global = 0;
}
return (local || global)? 0 : -1;
}

View File

@@ -0,0 +1,43 @@
#include "common.h"
#include "send.h"
/* pipe an address through a command to translate it */
extern dest *
translate(dest *dp)
{
process *pp;
String *line;
dest *rv;
char *cp;
int n;
pp = proc_start(s_to_c(dp->repl1), (stream *)0, outstream(), outstream(), 1, 0);
if (pp == 0) {
dp->status = d_resource;
return 0;
}
line = s_new();
for(;;) {
cp = Brdline(pp->std[1]->fp, '\n');
if(cp == 0)
break;
if(strncmp(cp, "_nosummary_", 11) == 0){
nosummary = 1;
continue;
}
n = Blinelen(pp->std[1]->fp);
cp[n-1] = ' ';
s_nappend(line, cp, n);
}
rv = s_to_dest(s_restart(line), dp);
s_restart(line);
while(s_read_line(pp->std[2]->fp, line))
;
if ((dp->pstat = proc_wait(pp)) != 0) {
dp->repl2 = line;
rv = 0;
} else
s_free(line);
proc_free(pp);
return rv;
}

29
src/cmd/upas/send/tryit Normal file
View File

@@ -0,0 +1,29 @@
#!/bin/sh
set -x
> /usr/spool/mail/test.local
echo "Forward to test.local" > /usr/spool/mail/test.forward
echo "Pipe to cat > /tmp/test.mail" > /usr/spool/mail/test.pipe
chmod 644 /usr/spool/mail/test.pipe
mail test.local <<EOF
mailed to test.local
EOF
mail test.forward <<EOF
mailed to test.forward
EOF
mail test.pipe <<EOF
mailed to test.pipe
EOF
mail dutoit!bowell!test.local <<EOF
mailed to dutoit!bowell!test.local
EOF
sleep 60
ls -l /usr/spool/mail/test.*
ls -l /tmp/test.mail
echo ">>>test.local<<<"
cat /usr/spool/mail/test.local
echo ">>>test.mail<<<"
cat /tmp/test.mail

View File

@@ -0,0 +1,274 @@
#include "common.h"
#include "smtpd.h"
#include "smtp.h"
#include <ctype.h>
#include <ip.h>
#include <ndb.h>
typedef struct {
int existed; /* these two are distinct to cope with errors */
int created;
int noperm;
long mtime; /* mod time, iff it already existed */
} Greysts;
/*
* There's a bit of a problem with yahoo; they apparently have a vast
* pool of machines that all run the same queue(s), so a 451 retry can
* come from a different IP address for many, many retries, and it can
* take ~5 hours for the same IP to call us back. Various other goofballs,
* notably the IEEE, try to send mail just before 9 AM, then refuse to try
* again until after 5 PM. Doh!
*/
enum {
Nonspammax = 14*60*60, /* must call back within this time if real */
};
static char whitelist[] = "/mail/lib/whitelist";
/*
* matches ip addresses or subnets in whitelist against nci->rsys.
* ignores comments and blank lines in /mail/lib/whitelist.
*/
static int
onwhitelist(void)
{
int lnlen;
char *line, *parse;
char input[128];
uchar ip[IPaddrlen], ipmasked[IPaddrlen];
uchar mask4[IPaddrlen], addr4[IPaddrlen];
uchar mask[IPaddrlen], addr[IPaddrlen], addrmasked[IPaddrlen];
Biobuf *wl;
static int beenhere;
static allzero[IPaddrlen];
if (!beenhere) {
beenhere = 1;
fmtinstall('I', eipfmt);
}
parseip(ip, nci->rsys);
wl = Bopen(whitelist, OREAD);
if (wl == nil)
return 1;
while ((line = Brdline(wl, '\n')) != nil) {
if (line[0] == '#' || line[0] == '\n')
continue;
lnlen = Blinelen(wl);
line[lnlen-1] = '\0'; /* clobber newline */
/* default mask is /32 (v4) or /128 (v6) for bare IP */
parse = line;
if (strchr(line, '/') == nil) {
strncpy(input, line, sizeof input - 5);
if (strchr(line, '.') != nil)
strcat(input, "/32");
else
strcat(input, "/128");
parse = input;
}
/* sorry, dave; where's parsecidr for v4 or v6? */
v4parsecidr(addr4, mask4, parse);
v4tov6(addr, addr4);
v4tov6(mask, mask4);
maskip(addr, mask, addrmasked);
maskip(ip, mask, ipmasked);
if (memcmp(ipmasked, addrmasked, IPaddrlen) == 0)
break;
}
Bterm(wl);
return line != nil;
}
static int mkdirs(char *);
/*
* if any directories leading up to path don't exist, create them.
* modifies but restores path.
*/
static int
mkpdirs(char *path)
{
int rv = 0;
char *sl = strrchr(path, '/');
if (sl != nil) {
*sl = '\0';
rv = mkdirs(path);
*sl = '/';
}
return rv;
}
/*
* if path or any directories leading up to it don't exist, create them.
* modifies but restores path.
*/
static int
mkdirs(char *path)
{
int fd;
if (access(path, AEXIST) >= 0)
return 0;
/* make presumed-missing intermediate directories */
if (mkpdirs(path) < 0)
return -1;
/* make final directory */
fd = create(path, OREAD, 0777|DMDIR);
if (fd < 0)
/*
* we may have lost a race; if the directory now exists,
* it's okay.
*/
return access(path, AEXIST) < 0? -1: 0;
close(fd);
return 0;
}
static long
getmtime(char *file)
{
long mtime = -1;
Dir *ds = dirstat(file);
if (ds != nil) {
mtime = ds->mtime;
free(ds);
}
return mtime;
}
static void
tryaddgrey(char *file, Greysts *gsp)
{
int fd = create(file, OWRITE|OEXCL, 0444|DMEXCL);
gsp->created = (fd >= 0);
if (fd >= 0) {
close(fd);
gsp->existed = 0; /* just created; couldn't have existed */
} else {
/*
* why couldn't we create file? it must have existed
* (or we were denied perm on parent dir.).
* if it existed, fill in gsp->mtime; otherwise
* make presumed-missing intermediate directories.
*/
gsp->existed = access(file, AEXIST) >= 0;
if (gsp->existed)
gsp->mtime = getmtime(file);
else if (mkpdirs(file) < 0)
gsp->noperm = 1;
}
}
static void
addgreylist(char *file, Greysts *gsp)
{
tryaddgrey(file, gsp);
if (!gsp->created && !gsp->existed && !gsp->noperm)
/* retry the greylist entry with parent dirs created */
tryaddgrey(file, gsp);
}
static int
recentcall(Greysts *gsp)
{
long delay = time(0) - gsp->mtime;
if (!gsp->existed)
return 0;
/* reject immediate call-back; spammers are doing that now */
return delay >= 30 && delay <= Nonspammax;
}
/*
* policy: if (caller-IP, my-IP, rcpt) is not on the greylist,
* reject this message as "451 temporary failure". if the caller is real,
* he'll retry soon, otherwise he's a spammer.
* at the first rejection, create a greylist entry for (my-ip, caller-ip,
* rcpt, time), where time is the file's mtime. if they call back and there's
* already a greylist entry, and it's within the allowed interval,
* add their IP to the append-only whitelist.
*
* greylist files can be removed at will; at worst they'll cause a few
* extra retries.
*/
static int
isrcptrecent(char *rcpt)
{
char *user;
char file[256];
Greysts gs;
Greysts *gsp = &gs;
if (rcpt[0] == '\0' || strchr(rcpt, '/') != nil ||
strcmp(rcpt, ".") == 0 || strcmp(rcpt, "..") == 0)
return 0;
/* shorten names to fit pre-fossil or pre-9p2000 file servers */
user = strrchr(rcpt, '!');
if (user == nil)
user = rcpt;
else
user++;
/* check & try to update the grey list entry */
snprint(file, sizeof file, "/mail/grey/%s/%s/%s",
nci->lsys, nci->rsys, user);
memset(gsp, 0, sizeof *gsp);
addgreylist(file, gsp);
/* if on greylist already and prior call was recent, add to whitelist */
if (gsp->existed && recentcall(gsp)) {
syslog(0, "smtpd",
"%s/%s was grey; adding IP to white", nci->rsys, rcpt);
return 1;
} else if (gsp->existed)
syslog(0, "smtpd", "call for %s/%s was seconds ago or long ago",
nci->rsys, rcpt);
else
syslog(0, "smtpd", "no call registered for %s/%s; registering",
nci->rsys, rcpt);
return 0;
}
void
vfysenderhostok(void)
{
char *fqdn;
int recent = 0;
Link *l;
if (onwhitelist())
return;
for (l = rcvers.first; l; l = l->next)
if (isrcptrecent(s_to_c(l->p)))
recent = 1;
/* if on greylist already and prior call was recent, add to whitelist */
if (recent) {
int fd = create(whitelist, OWRITE, 0666|DMAPPEND);
if (fd >= 0) {
seek(fd, 0, 2); /* paranoia */
if ((fqdn = csgetvalue(nil, "ip", nci->rsys, "dom", nil)) != nil)
fprint(fd, "# %s\n%s\n\n", fqdn, nci->rsys);
else
fprint(fd, "# unknown\n%s\n\n", nci->rsys);
close(fd);
}
} else {
syslog(0, "smtpd",
"no recent call from %s for a rcpt; rejecting with temporary failure",
nci->rsys);
reply("451 please try again soon from the same IP.\r\n");
exits("no recent call for a rcpt");
}
}

54
src/cmd/upas/smtp/mkfile Normal file
View File

@@ -0,0 +1,54 @@
<$PLAN9/src/mkhdr
TARG = # smtpd\
smtp\
OFILES=
LIB=../common/libcommon.a\
$PLAN9/lib/libthread.a # why do i have to explicitly put this?
HFILES=../common/common.h\
../common/sys.h\
smtpd.h\
smtp.h\
BIN=$PLAN9/bin/upas
UPDATE=\
greylist.c\
mkfile\
mxdial.c\
rfc822.y\
rmtdns.c\
smtpd.y\
spam.c\
$HFILES\
${OFILES:%.$O=%.c}\
${TARG:%=%.c}\
<$PLAN9/src/mkmany
CFLAGS=$CFLAGS -I../common -D'SPOOL="/mail"'
$O.smtpd: smtpd.tab.$O rmtdns.$O spam.$O rfc822.tab.$O greylist.$O
$O.smtp: rfc822.tab.$O mxdial.$O
smtpd.$O: smtpd.h
smtp.$O to.$O: smtp.h
smtpd.tab.c: smtpd.y smtpd.h
yacc -o xxx smtpd.y
sed 's/yy/zz/g' < xxx > $target
rm xxx
rfc822.tab.c: rfc822.y smtp.h
9 yacc -d -o $target rfc822.y
clean:V:
rm -f *.[$OS] [$OS].$TARG smtpd.tab.c rfc822.tab.c y.tab.? y.debug $TARG
../common/libcommon.a$O:
@{
cd ../common
mk
}

333
src/cmd/upas/smtp/mxdial.c Normal file
View File

@@ -0,0 +1,333 @@
#include "common.h"
#include <ndb.h>
#include "smtp.h" /* to publish dial_string_parse */
enum
{
Nmx= 16,
Maxstring= 256,
};
typedef struct Mx Mx;
struct Mx
{
char host[256];
char ip[24];
int pref;
};
static Mx mx[Nmx];
Ndb *db;
extern int debug;
static int mxlookup(DS*, char*);
static int mxlookup1(DS*, char*);
static int compar(void*, void*);
static int callmx(DS*, char*, char*);
static void expand_meta(DS *ds);
extern int cistrcmp(char*, char*);
int
mxdial(char *addr, char *ddomain, char *gdomain)
{
int fd;
DS ds;
char err[Errlen];
addr = netmkaddr(addr, 0, "smtp");
dial_string_parse(addr, &ds);
/* try connecting to destination or any of it's mail routers */
fd = callmx(&ds, addr, ddomain);
/* try our mail gateway */
rerrstr(err, sizeof(err));
if(fd < 0 && gdomain && strstr(err, "can't translate") != 0) {
fprint(2,"dialing %s\n",gdomain);
fd = dial(netmkaddr(gdomain, 0, "smtp"), 0, 0, 0);
}
return fd;
}
/*
* take an address and return all the mx entries for it,
* most preferred first
*/
static int
callmx(DS *ds, char *dest, char *domain)
{
int fd, i, nmx;
char addr[Maxstring];
/* get a list of mx entries */
nmx = mxlookup(ds, domain);
if(nmx < 0){
/* dns isn't working, don't just dial */
return -1;
}
if(nmx == 0){
if(debug)
fprint(2, "mxlookup returns nothing\n");
return dial(dest, 0, 0, 0);
}
/* refuse to honor loopback addresses given by dns */
for(i = 0; i < nmx; i++){
if(strcmp(mx[i].ip, "127.0.0.1") == 0){
if(debug)
fprint(2, "mxlookup returns loopback\n");
werrstr("illegal: domain lists 127.0.0.1 as mail server");
return -1;
}
}
/* sort by preference */
if(nmx > 1)
qsort(mx, nmx, sizeof(Mx), compar);
/* dial each one in turn */
for(i = 0; i < nmx; i++){
snprint(addr, sizeof(addr), "%s/%s!%s!%s", ds->netdir, ds->proto,
mx[i].host, ds->service);
if(debug)
fprint(2, "mxdial trying %s\n", addr);
fd = dial(addr, 0, 0, 0);
if(fd >= 0)
return fd;
}
return -1;
}
/*
* call the dns process and have it try to resolve the mx request
*
* this routine knows about the firewall and tries inside and outside
* dns's seperately.
*/
static int
mxlookup(DS *ds, char *domain)
{
int n;
/* just in case we find no domain name */
strcpy(domain, ds->host);
if(ds->netdir){
n = mxlookup1(ds, domain);
} else {
ds->netdir = "/net";
n = mxlookup1(ds, domain);
if(n == 0) {
ds->netdir = "/net.alt";
n = mxlookup1(ds, domain);
}
}
return n;
}
static int
mxlookup1(DS *ds, char *domain)
{
char buf[1024];
char dnsname[Maxstring];
char *fields[4];
int i, n, fd, nmx;
snprint(dnsname, sizeof dnsname, "%s/dns", ds->netdir);
fd = open(dnsname, ORDWR);
if(fd < 0)
return 0;
nmx = 0;
snprint(buf, sizeof(buf), "%s mx", ds->host);
if(debug)
fprint(2, "sending %s '%s'\n", dnsname, buf);
n = write(fd, buf, strlen(buf));
if(n < 0){
rerrstr(buf, sizeof buf);
if(debug)
fprint(2, "dns: %s\n", buf);
if(strstr(buf, "dns failure")){
/* if dns fails for the mx lookup, we have to stop */
close(fd);
return -1;
}
} else {
/*
* get any mx entries
*/
seek(fd, 0, 0);
while(nmx < Nmx && (n = read(fd, buf, sizeof(buf)-1)) > 0){
buf[n] = 0;
if(debug)
fprint(2, "dns mx: %s\n", buf);
n = getfields(buf, fields, 4, 1, " \t");
if(n < 4)
continue;
if(strchr(domain, '.') == 0)
strcpy(domain, fields[0]);
strncpy(mx[nmx].host, fields[3], sizeof(mx[n].host)-1);
mx[nmx].pref = atoi(fields[2]);
nmx++;
}
if(debug)
fprint(2, "dns mx; got %d entries\n", nmx);
}
/*
* no mx record? try name itself.
*/
/*
* BUG? If domain has no dots, then we used to look up ds->host
* but return domain instead of ds->host in the list. Now we return
* ds->host. What will this break?
*/
if(nmx == 0){
mx[0].pref = 1;
strncpy(mx[0].host, ds->host, sizeof(mx[0].host));
nmx++;
}
/*
* look up all ip addresses
*/
for(i = 0; i < nmx; i++){
seek(fd, 0, 0);
snprint(buf, sizeof buf, "%s ip", mx[i].host);
mx[i].ip[0] = 0;
if(write(fd, buf, strlen(buf)) < 0)
goto no;
seek(fd, 0, 0);
if((n = read(fd, buf, sizeof buf-1)) < 0)
goto no;
buf[n] = 0;
if(getfields(buf, fields, 4, 1, " \t") < 3)
goto no;
strncpy(mx[i].ip, fields[2], sizeof(mx[i].ip)-1);
continue;
no:
/* remove mx[i] and go around again */
nmx--;
mx[i] = mx[nmx];
i--;
}
return nmx;
}
static int
compar(void *a, void *b)
{
return ((Mx*)a)->pref - ((Mx*)b)->pref;
}
/* break up an address to its component parts */
void
dial_string_parse(char *str, DS *ds)
{
char *p, *p2;
strncpy(ds->buf, str, sizeof(ds->buf));
ds->buf[sizeof(ds->buf)-1] = 0;
p = strchr(ds->buf, '!');
if(p == 0) {
ds->netdir = 0;
ds->proto = "net";
ds->host = ds->buf;
} else {
if(*ds->buf != '/'){
ds->netdir = 0;
ds->proto = ds->buf;
} else {
for(p2 = p; *p2 != '/'; p2--)
;
*p2++ = 0;
ds->netdir = ds->buf;
ds->proto = p2;
}
*p = 0;
ds->host = p + 1;
}
ds->service = strchr(ds->host, '!');
if(ds->service)
*ds->service++ = 0;
if(*ds->host == '$')
expand_meta(ds);
}
#if 0 /* jpc */
static void
expand_meta(DS *ds)
{
char buf[128], cs[128], *net, *p;
int fd, n;
net = ds->netdir;
if(!net)
net = "/net";
if(debug)
fprint(2, "expanding %s!%s\n", net, ds->host);
snprint(cs, sizeof(cs), "%s/cs", net);
if((fd = open(cs, ORDWR)) == -1){
if(debug)
fprint(2, "open %s: %r\n", cs);
syslog(0, "smtp", "cannot open %s: %r", cs);
return;
}
snprint(buf, sizeof(buf), "!ipinfo %s", ds->host+1); // +1 to skip $
if(write(fd, buf, strlen(buf)) <= 0){
if(debug)
fprint(2, "write %s: %r\n", cs);
syslog(0, "smtp", "%s to %s - write failed: %r", buf, cs);
close(fd);
return;
}
seek(fd, 0, 0);
if((n = read(fd, ds->expand, sizeof(ds->expand)-1)) < 0){
if(debug)
fprint(2, "read %s: %r\n", cs);
syslog(0, "smtp", "%s - read failed: %r", cs);
close(fd);
return;
}
close(fd);
ds->expand[n] = 0;
if((p = strchr(ds->expand, '=')) == nil){
if(debug)
fprint(2, "response %s: %s\n", cs, ds->expand);
syslog(0, "smtp", "%q from %s - bad response: %r", ds->expand, cs);
return;
}
ds->host = p+1;
/* take only first one returned (quasi-bug) */
if((p = strchr(ds->host, ' ')) != nil)
*p = 0;
}
#endif /* jpc */
static void
expand_meta(DS *ds)
{
Ndb *db;
Ndbs s;
char *sys, *smtpserver;
sys = sysname();
db = ndbopen(unsharp("#9/ndb/local"));
fprint(2,"%s",ds->host);
smtpserver = ndbgetvalue(db, &s, "sys", sys, "smtp", nil);
snprint(ds->host,128,"%s",smtpserver);
fprint(2," exanded to %s\n",ds->host);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,98 @@
/* A Bison parser, made by GNU Bison 2.0. */
/* Skeleton parser for Yacc-like parsing with Bison,
Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA. */
/* As a special exception, when this file is copied by Bison into a
Bison output file, you may use that output file without restriction.
This special exception was added by the Free Software Foundation
in version 1.24 of Bison. */
/* Tokens. */
#ifndef YYTOKENTYPE
# define YYTOKENTYPE
/* Put the tokens into the symbol table, so that GDB and other debuggers
know about them. */
enum yytokentype {
WORD = 258,
DATE = 259,
RESENT_DATE = 260,
RETURN_PATH = 261,
FROM = 262,
SENDER = 263,
REPLY_TO = 264,
RESENT_FROM = 265,
RESENT_SENDER = 266,
RESENT_REPLY_TO = 267,
SUBJECT = 268,
TO = 269,
CC = 270,
BCC = 271,
RESENT_TO = 272,
RESENT_CC = 273,
RESENT_BCC = 274,
REMOTE = 275,
PRECEDENCE = 276,
MIMEVERSION = 277,
CONTENTTYPE = 278,
MESSAGEID = 279,
RECEIVED = 280,
MAILER = 281,
BADTOKEN = 282
};
#endif
#define WORD 258
#define DATE 259
#define RESENT_DATE 260
#define RETURN_PATH 261
#define FROM 262
#define SENDER 263
#define REPLY_TO 264
#define RESENT_FROM 265
#define RESENT_SENDER 266
#define RESENT_REPLY_TO 267
#define SUBJECT 268
#define TO 269
#define CC 270
#define BCC 271
#define RESENT_TO 272
#define RESENT_CC 273
#define RESENT_BCC 274
#define REMOTE 275
#define PRECEDENCE 276
#define MIMEVERSION 277
#define CONTENTTYPE 278
#define MESSAGEID 279
#define RECEIVED 280
#define MAILER 281
#define BADTOKEN 282
#if ! defined (YYSTYPE) && ! defined (YYSTYPE_IS_DECLARED)
typedef int YYSTYPE;
# define yystype YYSTYPE /* obsolescent; will be withdrawn */
# define YYSTYPE_IS_DECLARED 1
# define YYSTYPE_IS_TRIVIAL 1
#endif
extern YYSTYPE yylval;

778
src/cmd/upas/smtp/rfc822.y Normal file
View File

@@ -0,0 +1,778 @@
%{
#include "common.h"
#include "smtp.h"
#include <ctype.h>
char *yylp; /* next character to be lex'd */
int yydone; /* tell yylex to give up */
char *yybuffer; /* first parsed character */
char *yyend; /* end of buffer to be parsed */
Node *root;
Field *firstfield;
Field *lastfield;
Node *usender;
Node *usys;
Node *udate;
char *startfield, *endfield;
int originator;
int destination;
int date;
int received;
int messageid;
%}
%term WORD
%term DATE
%term RESENT_DATE
%term RETURN_PATH
%term FROM
%term SENDER
%term REPLY_TO
%term RESENT_FROM
%term RESENT_SENDER
%term RESENT_REPLY_TO
%term SUBJECT
%term TO
%term CC
%term BCC
%term RESENT_TO
%term RESENT_CC
%term RESENT_BCC
%term REMOTE
%term PRECEDENCE
%term MIMEVERSION
%term CONTENTTYPE
%term MESSAGEID
%term RECEIVED
%term MAILER
%term BADTOKEN
%start msg
%%
msg : fields
| unixfrom '\n' fields
;
fields : '\n'
{ yydone = 1; }
| field '\n'
| field '\n' fields
;
field : dates
{ date = 1; }
| originator
{ originator = 1; }
| destination
{ destination = 1; }
| subject
| optional
| ignored
| received
| precedence
| error '\n' field
;
unixfrom : FROM route_addr unix_date_time REMOTE FROM word
{ freenode($1); freenode($4); freenode($5);
usender = $2; udate = $3; usys = $6;
}
;
originator : REPLY_TO ':' address_list
{ newfield(link3($1, $2, $3), 1); }
| RETURN_PATH ':' route_addr
{ newfield(link3($1, $2, $3), 1); }
| FROM ':' mailbox_list
{ newfield(link3($1, $2, $3), 1); }
| SENDER ':' mailbox
{ newfield(link3($1, $2, $3), 1); }
| RESENT_REPLY_TO ':' address_list
{ newfield(link3($1, $2, $3), 1); }
| RESENT_SENDER ':' mailbox
{ newfield(link3($1, $2, $3), 1); }
| RESENT_FROM ':' mailbox
{ newfield(link3($1, $2, $3), 1); }
;
dates : DATE ':' date_time
{ newfield(link3($1, $2, $3), 0); }
| RESENT_DATE ':' date_time
{ newfield(link3($1, $2, $3), 0); }
;
destination : TO ':'
{ newfield(link2($1, $2), 0); }
| TO ':' address_list
{ newfield(link3($1, $2, $3), 0); }
| RESENT_TO ':'
{ newfield(link2($1, $2), 0); }
| RESENT_TO ':' address_list
{ newfield(link3($1, $2, $3), 0); }
| CC ':'
{ newfield(link2($1, $2), 0); }
| CC ':' address_list
{ newfield(link3($1, $2, $3), 0); }
| RESENT_CC ':'
{ newfield(link2($1, $2), 0); }
| RESENT_CC ':' address_list
{ newfield(link3($1, $2, $3), 0); }
| BCC ':'
{ newfield(link2($1, $2), 0); }
| BCC ':' address_list
{ newfield(link3($1, $2, $3), 0); }
| RESENT_BCC ':'
{ newfield(link2($1, $2), 0); }
| RESENT_BCC ':' address_list
{ newfield(link3($1, $2, $3), 0); }
;
subject : SUBJECT ':' things
{ newfield(link3($1, $2, $3), 0); }
| SUBJECT ':'
{ newfield(link2($1, $2), 0); }
;
received : RECEIVED ':' things
{ newfield(link3($1, $2, $3), 0); received++; }
| RECEIVED ':'
{ newfield(link2($1, $2), 0); received++; }
;
precedence : PRECEDENCE ':' things
{ newfield(link3($1, $2, $3), 0); }
| PRECEDENCE ':'
{ newfield(link2($1, $2), 0); }
;
ignored : ignoredhdr ':' things
{ newfield(link3($1, $2, $3), 0); }
| ignoredhdr ':'
{ newfield(link2($1, $2), 0); }
;
ignoredhdr : MIMEVERSION | CONTENTTYPE | MESSAGEID { messageid = 1; } | MAILER
;
optional : fieldwords ':' things
{ /* hack to allow same lex for field names and the rest */
if(badfieldname($1)){
freenode($1);
freenode($2);
freenode($3);
return 1;
}
newfield(link3($1, $2, $3), 0);
}
| fieldwords ':'
{ /* hack to allow same lex for field names and the rest */
if(badfieldname($1)){
freenode($1);
freenode($2);
return 1;
}
newfield(link2($1, $2), 0);
}
;
address_list : address
| address_list ',' address
{ $$ = link3($1, $2, $3); }
;
address : mailbox
| group
;
group : phrase ':' address_list ';'
{ $$ = link2($1, link3($2, $3, $4)); }
| phrase ':' ';'
{ $$ = link3($1, $2, $3); }
;
mailbox_list : mailbox
| mailbox_list ',' mailbox
{ $$ = link3($1, $2, $3); }
;
mailbox : route_addr
| phrase brak_addr
{ $$ = link2($1, $2); }
| brak_addr
;
brak_addr : '<' route_addr '>'
{ $$ = link3($1, $2, $3); }
| '<' '>'
{ $$ = nobody($2); freenode($1); }
;
route_addr : route ':' at_addr
{ $$ = address(concat($1, concat($2, $3))); }
| addr_spec
;
route : '@' domain
{ $$ = concat($1, $2); }
| route ',' '@' domain
{ $$ = concat($1, concat($2, concat($3, $4))); }
;
addr_spec : local_part
{ $$ = address($1); }
| at_addr
;
at_addr : local_part '@' domain
{ $$ = address(concat($1, concat($2, $3)));}
| at_addr '@' domain
{ $$ = address(concat($1, concat($2, $3)));}
;
local_part : word
;
domain : word
;
phrase : word
| phrase word
{ $$ = link2($1, $2); }
;
things : thing
| things thing
{ $$ = link2($1, $2); }
;
thing : word | '<' | '>' | '@' | ':' | ';' | ','
;
date_time : things
;
unix_date_time : word word word unix_time word word
{ $$ = link3($1, $3, link3($2, $6, link2($4, $5))); }
;
unix_time : word
| unix_time ':' word
{ $$ = link3($1, $2, $3); }
;
word : WORD | DATE | RESENT_DATE | RETURN_PATH | FROM | SENDER
| REPLY_TO | RESENT_FROM | RESENT_SENDER | RESENT_REPLY_TO
| TO | CC | BCC | RESENT_TO | RESENT_CC | RESENT_BCC | REMOTE | SUBJECT
| PRECEDENCE | MIMEVERSION | CONTENTTYPE | MESSAGEID | RECEIVED | MAILER
;
fieldwords : fieldword
| WORD
| fieldwords fieldword
{ $$ = link2($1, $2); }
| fieldwords word
{ $$ = link2($1, $2); }
;
fieldword : '<' | '>' | '@' | ';' | ','
;
%%
/*
* Initialize the parsing. Done once for each header field.
*/
void
yyinit(char *p, int len)
{
yybuffer = p;
yylp = p;
yyend = p + len;
firstfield = lastfield = 0;
received = 0;
}
/*
* keywords identifying header fields we care about
*/
typedef struct Keyword Keyword;
struct Keyword {
char *rep;
int val;
};
/* field names that we need to recognize */
Keyword key[] = {
{ "date", DATE },
{ "resent-date", RESENT_DATE },
{ "return_path", RETURN_PATH },
{ "from", FROM },
{ "sender", SENDER },
{ "reply-to", REPLY_TO },
{ "resent-from", RESENT_FROM },
{ "resent-sender", RESENT_SENDER },
{ "resent-reply-to", RESENT_REPLY_TO },
{ "to", TO },
{ "cc", CC },
{ "bcc", BCC },
{ "resent-to", RESENT_TO },
{ "resent-cc", RESENT_CC },
{ "resent-bcc", RESENT_BCC },
{ "remote", REMOTE },
{ "subject", SUBJECT },
{ "precedence", PRECEDENCE },
{ "mime-version", MIMEVERSION },
{ "content-type", CONTENTTYPE },
{ "message-id", MESSAGEID },
{ "received", RECEIVED },
{ "mailer", MAILER },
{ "who-the-hell-cares", WORD }
};
/*
* Lexical analysis for an rfc822 header field. Continuation lines
* are handled in yywhite() when skipping over white space.
*
*/
int
yylex(void)
{
String *t;
int quoting;
int escaping;
char *start;
Keyword *kp;
int c, d;
/* print("lexing\n"); /**/
if(yylp >= yyend)
return 0;
if(yydone)
return 0;
quoting = escaping = 0;
start = yylp;
yylval = malloc(sizeof(Node));
yylval->white = yylval->s = 0;
yylval->next = 0;
yylval->addr = 0;
yylval->start = yylp;
for(t = 0; yylp < yyend; yylp++){
c = *yylp & 0xff;
/* dump nulls, they can't be in header */
if(c == 0)
continue;
if(escaping) {
escaping = 0;
} else if(quoting) {
switch(c){
case '\\':
escaping = 1;
break;
case '\n':
d = (*(yylp+1))&0xff;
if(d != ' ' && d != '\t'){
quoting = 0;
yylp--;
continue;
}
break;
case '"':
quoting = 0;
break;
}
} else {
switch(c){
case '\\':
escaping = 1;
break;
case '(':
case ' ':
case '\t':
case '\r':
goto out;
case '\n':
if(yylp == start){
yylp++;
/* print("lex(c %c)\n", c); /**/
yylval->end = yylp;
return yylval->c = c;
}
goto out;
case '@':
case '>':
case '<':
case ':':
case ',':
case ';':
if(yylp == start){
yylp++;
yylval->white = yywhite();
/* print("lex(c %c)\n", c); /**/
yylval->end = yylp;
return yylval->c = c;
}
goto out;
case '"':
quoting = 1;
break;
default:
break;
}
}
if(t == 0)
t = s_new();
s_putc(t, c);
}
out:
yylval->white = yywhite();
if(t) {
s_terminate(t);
} else /* message begins with white-space! */
return yylval->c = '\n';
yylval->s = t;
for(kp = key; kp->val != WORD; kp++)
if(cistrcmp(s_to_c(t), kp->rep)==0)
break;
/* print("lex(%d) %s\n", kp->val-WORD, s_to_c(t)); /**/
yylval->end = yylp;
return yylval->c = kp->val;
}
void
yyerror(char *x)
{
USED(x);
/*fprint(2, "parse err: %s\n", x);/**/
}
/*
* parse white space and comments
*/
String *
yywhite(void)
{
String *w;
int clevel;
int c;
int escaping;
escaping = clevel = 0;
for(w = 0; yylp < yyend; yylp++){
c = *yylp & 0xff;
/* dump nulls, they can't be in header */
if(c == 0)
continue;
if(escaping){
escaping = 0;
} else if(clevel) {
switch(c){
case '\n':
/*
* look for multiline fields
*/
if(*(yylp+1)==' ' || *(yylp+1)=='\t')
break;
else
goto out;
case '\\':
escaping = 1;
break;
case '(':
clevel++;
break;
case ')':
clevel--;
break;
}
} else {
switch(c){
case '\\':
escaping = 1;
break;
case '(':
clevel++;
break;
case ' ':
case '\t':
case '\r':
break;
case '\n':
/*
* look for multiline fields
*/
if(*(yylp+1)==' ' || *(yylp+1)=='\t')
break;
else
goto out;
default:
goto out;
}
}
if(w == 0)
w = s_new();
s_putc(w, c);
}
out:
if(w)
s_terminate(w);
return w;
}
/*
* link two parsed entries together
*/
Node*
link2(Node *p1, Node *p2)
{
Node *p;
for(p = p1; p->next; p = p->next)
;
p->next = p2;
return p1;
}
/*
* link three parsed entries together
*/
Node*
link3(Node *p1, Node *p2, Node *p3)
{
Node *p;
for(p = p2; p->next; p = p->next)
;
p->next = p3;
for(p = p1; p->next; p = p->next)
;
p->next = p2;
return p1;
}
/*
* make a:b, move all white space after both
*/
Node*
colon(Node *p1, Node *p2)
{
if(p1->white){
if(p2->white)
s_append(p1->white, s_to_c(p2->white));
} else {
p1->white = p2->white;
p2->white = 0;
}
s_append(p1->s, ":");
if(p2->s)
s_append(p1->s, s_to_c(p2->s));
if(p1->end < p2->end)
p1->end = p2->end;
freenode(p2);
return p1;
}
/*
* concatenate two fields, move all white space after both
*/
Node*
concat(Node *p1, Node *p2)
{
char buf[2];
if(p1->white){
if(p2->white)
s_append(p1->white, s_to_c(p2->white));
} else {
p1->white = p2->white;
p2->white = 0;
}
if(p1->s == nil){
buf[0] = p1->c;
buf[1] = 0;
p1->s = s_new();
s_append(p1->s, buf);
}
if(p2->s)
s_append(p1->s, s_to_c(p2->s));
else {
buf[0] = p2->c;
buf[1] = 0;
s_append(p1->s, buf);
}
if(p1->end < p2->end)
p1->end = p2->end;
freenode(p2);
return p1;
}
/*
* look for disallowed chars in the field name
*/
int
badfieldname(Node *p)
{
for(; p; p = p->next){
/* field name can't contain white space */
if(p->white && p->next)
return 1;
}
return 0;
}
/*
* mark as an address
*/
Node *
address(Node *p)
{
p->addr = 1;
return p;
}
/*
* case independent string compare
*/
int
cistrcmp(char *s1, char *s2)
{
int c1, c2;
for(; *s1; s1++, s2++){
c1 = isupper(*s1) ? tolower(*s1) : *s1;
c2 = isupper(*s2) ? tolower(*s2) : *s2;
if (c1 != c2)
return -1;
}
return *s2;
}
/*
* free a node
*/
void
freenode(Node *p)
{
Node *tp;
while(p){
tp = p->next;
if(p->s)
s_free(p->s);
if(p->white)
s_free(p->white);
free(p);
p = tp;
}
}
/*
* an anonymous user
*/
Node*
nobody(Node *p)
{
if(p->s)
s_free(p->s);
p->s = s_copy("pOsTmAsTeR");
p->addr = 1;
return p;
}
/*
* add anything that was dropped because of a parse error
*/
void
missing(Node *p)
{
Node *np;
char *start, *end;
Field *f;
String *s;
start = yybuffer;
if(lastfield != nil){
for(np = lastfield->node; np; np = np->next)
start = np->end+1;
}
end = p->start-1;
if(end <= start)
return;
if(strncmp(start, "From ", 5) == 0)
return;
np = malloc(sizeof(Node));
np->start = start;
np->end = end;
np->white = nil;
s = s_copy("BadHeader: ");
np->s = s_nappend(s, start, end-start);
np->next = nil;
f = malloc(sizeof(Field));
f->next = 0;
f->node = np;
f->source = 0;
if(firstfield)
lastfield->next = f;
else
firstfield = f;
lastfield = f;
}
/*
* create a new field
*/
void
newfield(Node *p, int source)
{
Field *f;
missing(p);
f = malloc(sizeof(Field));
f->next = 0;
f->node = p;
f->source = source;
if(firstfield)
lastfield->next = f;
else
firstfield = f;
lastfield = f;
endfield = startfield;
startfield = yylp;
}
/*
* fee a list of fields
*/
void
freefield(Field *f)
{
Field *tf;
while(f){
tf = f->next;
freenode(f->node);
free(f);
f = tf;
}
}
/*
* add some white space to a node
*/
Node*
whiten(Node *p)
{
Node *tp;
for(tp = p; tp->next; tp = tp->next)
;
if(tp->white == 0)
tp->white = s_copy(" ");
return p;
}
void
yycleanup(void)
{
Field *f, *fnext;
Node *np, *next;
for(f = firstfield; f; f = fnext){
for(np = f->node; np; np = next){
if(np->s)
s_free(np->s);
if(np->white)
s_free(np->white);
next = np->next;
free(np);
}
fnext = f->next;
free(f);
}
firstfield = lastfield = 0;
}

View File

@@ -0,0 +1,58 @@
#include "common.h"
#include <ndb.h>
int
rmtdns(char *net, char *path)
{
int fd, n, r;
char *domain, *cp, buf[1024];
if(net == 0 || path == 0)
return 0;
domain = strdup(path);
cp = strchr(domain, '!');
if(cp){
*cp = 0;
n = cp-domain;
} else
n = strlen(domain);
if(*domain == '[' && domain[n-1] == ']'){ /* accept [nnn.nnn.nnn.nnn] */
domain[n-1] = 0;
r = strcmp(ipattr(domain+1), "ip");
domain[n-1] = ']';
} else
r = strcmp(ipattr(domain), "ip"); /* accept nnn.nnn.nnn.nnn */
if(r == 0){
free(domain);
return 0;
}
snprint(buf, sizeof(buf), "%s/dns", net);
fd = open(buf, ORDWR); /* look up all others */
if(fd < 0){ /* dns screw up - can't check */
free(domain);
return 0;
}
n = snprint(buf, sizeof(buf), "%s all", domain);
free(domain);
seek(fd, 0, 0);
n = write(fd, buf, n);
close(fd);
if(n < 0){
rerrstr(buf, sizeof(buf));
if (strcmp(buf, "dns: name does not exist") == 0)
return -1;
}
return 0;
}
/*
void main(int, char *argv[]){ print("return = %d\n", rmtdns("/net.alt/tcp/109", argv[1]));}
*/

1122
src/cmd/upas/smtp/smtp.c Normal file

File diff suppressed because it is too large Load Diff

61
src/cmd/upas/smtp/smtp.h Normal file
View File

@@ -0,0 +1,61 @@
typedef struct Node Node;
typedef struct Field Field;
typedef Node *Nodeptr;
#define YYSTYPE Nodeptr
struct Node {
Node *next;
int c; /* token type */
char addr; /* true if this is an address */
String *s; /* string representing token */
String *white; /* white space following token */
char *start; /* first byte for this token */
char *end; /* next byte in input */
};
struct Field {
Field *next;
Node *node;
int source;
};
typedef struct DS DS;
struct DS {
/* dist string */
char buf[128];
char expand[128];
char *netdir;
char *proto;
char *host;
char *service;
};
extern Field *firstfield;
extern Field *lastfield;
extern Node *usender;
extern Node *usys;
extern Node *udate;
extern int originator;
extern int destination;
extern int date;
extern int messageid;
Node* anonymous(Node*);
Node* address(Node*);
int badfieldname(Node*);
Node* bang(Node*, Node*);
Node* colon(Node*, Node*);
int cistrcmp(char*, char*);
Node* link2(Node*, Node*);
Node* link3(Node*, Node*, Node*);
void freenode(Node*);
void newfield(Node*, int);
void freefield(Field*);
void yyinit(char*, int);
int yyparse(void);
int yylex(void);
String* yywhite(void);
Node* whiten(Node*);
void yycleanup(void);
int mxdial(char*, char*, char*);
void dial_string_parse(char*, DS*);

1494
src/cmd/upas/smtp/smtpd.c Normal file

File diff suppressed because it is too large Load Diff

68
src/cmd/upas/smtp/smtpd.h Normal file
View File

@@ -0,0 +1,68 @@
enum {
ACCEPT = 0,
REFUSED,
DENIED,
DIALUP,
BLOCKED,
DELAY,
TRUSTED,
NONE,
MAXREJECTS = 100,
};
typedef struct Link Link;
typedef struct List List;
struct Link {
Link *next;
String *p;
};
struct List {
Link *first;
Link *last;
};
extern int fflag;
extern int rflag;
extern int sflag;
extern int debug;
extern NetConnInfo *nci;
extern char *dom;
extern char* me;
extern int trusted;
extern List senders;
extern List rcvers;
void addbadguy(char*);
void auth(String *, String *);
int blocked(String*);
void data(void);
char* dumpfile(char*);
int forwarding(String*);
void getconf(void);
void hello(String*, int extended);
void help(String *);
int isbadguy(void);
void listadd(List*, String*);
void listfree(List*);
int masquerade(String*, char*);
void noop(void);
int optoutofspamfilter(char*);
void quit(void);
void parseinit(void);
void receiver(String*);
int recipok(char*);
int reply(char*, ...);
void reset(void);
int rmtdns(char*, char*);
void sayhi(void);
void sender(String*);
void starttls(void);
void turn(void);
void verify(String*);
void vfysenderhostok(void);
int zzparse(void);

317
src/cmd/upas/smtp/smtpd.y Normal file
View File

@@ -0,0 +1,317 @@
%{
#include "common.h"
#include <ctype.h>
#include "smtpd.h"
#define YYSTYPE yystype
typedef struct quux yystype;
struct quux {
String *s;
int c;
};
Biobuf *yyfp;
YYSTYPE *bang;
extern Biobuf bin;
extern int debug;
YYSTYPE cat(YYSTYPE*, YYSTYPE*, YYSTYPE*, YYSTYPE*, YYSTYPE*, YYSTYPE*, YYSTYPE*);
int yyparse(void);
int yylex(void);
YYSTYPE anonymous(void);
%}
%term SPACE
%term CNTRL
%term CRLF
%start conversation
%%
conversation : cmd
| conversation cmd
;
cmd : error
| 'h' 'e' 'l' 'o' spaces sdomain CRLF
{ hello($6.s, 0); }
| 'e' 'h' 'l' 'o' spaces sdomain CRLF
{ hello($6.s, 1); }
| 'm' 'a' 'i' 'l' spaces 'f' 'r' 'o' 'm' ':' spath CRLF
{ sender($11.s); }
| 'm' 'a' 'i' 'l' spaces 'f' 'r' 'o' 'm' ':' spath spaces 'a' 'u' 't' 'h' '=' sauth CRLF
{ sender($11.s); }
| 'r' 'c' 'p' 't' spaces 't' 'o' ':' spath CRLF
{ receiver($9.s); }
| 'd' 'a' 't' 'a' CRLF
{ data(); }
| 'r' 's' 'e' 't' CRLF
{ reset(); }
| 's' 'e' 'n' 'd' spaces 'f' 'r' 'o' 'm' ':' spath CRLF
{ sender($11.s); }
| 's' 'o' 'm' 'l' spaces 'f' 'r' 'o' 'm' ':' spath CRLF
{ sender($11.s); }
| 's' 'a' 'm' 'l' spaces 'f' 'r' 'o' 'm' ':' spath CRLF
{ sender($11.s); }
| 'v' 'r' 'f' 'y' spaces string CRLF
{ verify($6.s); }
| 'e' 'x' 'p' 'n' spaces string CRLF
{ verify($6.s); }
| 'h' 'e' 'l' 'p' CRLF
{ help(0); }
| 'h' 'e' 'l' 'p' spaces string CRLF
{ help($6.s); }
| 'n' 'o' 'o' 'p' CRLF
{ noop(); }
| 'q' 'u' 'i' 't' CRLF
{ quit(); }
| 't' 'u' 'r' 'n' CRLF
{ turn(); }
| 's' 't' 'a' 'r' 't' 't' 'l' 's' CRLF
{ starttls(); }
| 'a' 'u' 't' 'h' spaces name spaces string CRLF
{ auth($6.s, $8.s); }
| 'a' 'u' 't' 'h' spaces name CRLF
{ auth($6.s, nil); }
| CRLF
{ reply("501 illegal command or bad syntax\r\n"); }
;
path : '<' '>' ={ $$ = anonymous(); }
| '<' mailbox '>' ={ $$ = $2; }
| '<' a_d_l ':' mailbox '>' ={ $$ = cat(&$2, bang, &$4, 0, 0 ,0, 0); }
;
spath : path ={ $$ = $1; }
| spaces path ={ $$ = $2; }
;
auth : path ={ $$ = $1; }
| mailbox ={ $$ = $1; }
;
sauth : auth ={ $$ = $1; }
| spaces auth ={ $$ = $2; }
;
;
a_d_l : at_domain ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
| at_domain ',' a_d_l ={ $$ = cat(&$1, bang, &$3, 0, 0, 0, 0); }
;
at_domain : '@' domain ={ $$ = cat(&$2, 0, 0, 0, 0 ,0, 0); }
;
sdomain : domain ={ $$ = $1; }
| domain spaces ={ $$ = $1; }
;
domain : element ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
| element '.' ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
| element '.' domain ={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); }
;
element : name ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
| '#' number ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
| '[' ']' ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
| '[' dotnum ']' ={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); }
;
mailbox : local_part ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
| local_part '@' domain ={ $$ = cat(&$3, bang, &$1, 0, 0 ,0, 0); }
;
local_part : dot_string ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
| quoted_string ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
;
name : let_dig ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
| let_dig ld_str ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
| let_dig ldh_str ld_str ={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); }
;
ld_str : let_dig
| let_dig ld_str ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
;
ldh_str : hunder
| ld_str hunder ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
| ldh_str ld_str hunder ={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); }
;
let_dig : a
| d
;
dot_string : string ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
| string '.' dot_string ={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); }
;
string : char ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
| string char ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
;
quoted_string : '"' qtext '"' ={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); }
;
qtext : '\\' x ={ $$ = cat(&$2, 0, 0, 0, 0 ,0, 0); }
| qtext '\\' x ={ $$ = cat(&$1, &$3, 0, 0, 0 ,0, 0); }
| q
| qtext q ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
;
char : c
| '\\' x ={ $$ = $2; }
;
dotnum : snum '.' snum '.' snum '.' snum ={ $$ = cat(&$1, &$2, &$3, &$4, &$5, &$6, &$7); }
;
number : d ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
| number d ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
;
snum : number ={ if(atoi(s_to_c($1.s)) > 255) print("bad snum\n"); }
;
spaces : SPACE ={ $$ = $1; }
| SPACE spaces ={ $$ = $1; }
;
hunder : '-' | '_'
;
special1 : CNTRL
| '(' | ')' | ',' | '.'
| ':' | ';' | '<' | '>' | '@'
;
special : special1 | '\\' | '"'
;
notspecial : '!' | '#' | '$' | '%' | '&' | '\''
| '*' | '+' | '-' | '/'
| '=' | '?'
| '[' | ']' | '^' | '_' | '`' | '{' | '|' | '}' | '~'
;
a : 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i'
| 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r'
| 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z'
;
d : '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
;
c : a | d | notspecial
;
q : a | d | special1 | notspecial | SPACE
;
x : a | d | special | notspecial | SPACE
;
%%
void
parseinit(void)
{
bang = (YYSTYPE*)malloc(sizeof(YYSTYPE));
bang->c = '!';
bang->s = 0;
yyfp = &bin;
}
yylex(void)
{
int c;
for(;;){
c = Bgetc(yyfp);
if(c == -1)
return 0;
if(debug)
fprint(2, "%c", c);
yylval.c = c = c & 0x7F;
if(c == '\n'){
return CRLF;
}
if(c == '\r'){
c = Bgetc(yyfp);
if(c != '\n'){
Bungetc(yyfp);
c = '\r';
} else {
if(debug)
fprint(2, "%c", c);
return CRLF;
}
}
if(isalpha(c))
return tolower(c);
if(isspace(c))
return SPACE;
if(iscntrl(c))
return CNTRL;
return c;
}
}
YYSTYPE
cat(YYSTYPE *y1, YYSTYPE *y2, YYSTYPE *y3, YYSTYPE *y4, YYSTYPE *y5, YYSTYPE *y6, YYSTYPE *y7)
{
YYSTYPE rv;
if(y1->s)
rv.s = y1->s;
else {
rv.s = s_new();
s_putc(rv.s, y1->c);
s_terminate(rv.s);
}
if(y2){
if(y2->s){
s_append(rv.s, s_to_c(y2->s));
s_free(y2->s);
} else {
s_putc(rv.s, y2->c);
s_terminate(rv.s);
}
} else
return rv;
if(y3){
if(y3->s){
s_append(rv.s, s_to_c(y3->s));
s_free(y3->s);
} else {
s_putc(rv.s, y3->c);
s_terminate(rv.s);
}
} else
return rv;
if(y4){
if(y4->s){
s_append(rv.s, s_to_c(y4->s));
s_free(y4->s);
} else {
s_putc(rv.s, y4->c);
s_terminate(rv.s);
}
} else
return rv;
if(y5){
if(y5->s){
s_append(rv.s, s_to_c(y5->s));
s_free(y5->s);
} else {
s_putc(rv.s, y5->c);
s_terminate(rv.s);
}
} else
return rv;
if(y6){
if(y6->s){
s_append(rv.s, s_to_c(y6->s));
s_free(y6->s);
} else {
s_putc(rv.s, y6->c);
s_terminate(rv.s);
}
} else
return rv;
if(y7){
if(y7->s){
s_append(rv.s, s_to_c(y7->s));
s_free(y7->s);
} else {
s_putc(rv.s, y7->c);
s_terminate(rv.s);
}
} else
return rv;
}
void
yyerror(char *x)
{
USED(x);
}
/*
* an anonymous user
*/
YYSTYPE
anonymous(void)
{
YYSTYPE rv;
rv.s = s_copy("/dev/null");
return rv;
}

591
src/cmd/upas/smtp/spam.c Normal file
View File

@@ -0,0 +1,591 @@
#include "common.h"
#include "smtpd.h"
#include <ip.h>
enum {
NORELAY = 0,
DNSVERIFY,
SAVEBLOCK,
DOMNAME,
OURNETS,
OURDOMS,
IP = 0,
STRING,
};
typedef struct Keyword Keyword;
struct Keyword {
char *name;
int code;
};
static Keyword options[] = {
"norelay", NORELAY,
"verifysenderdom", DNSVERIFY,
"saveblockedmsg", SAVEBLOCK,
"defaultdomain", DOMNAME,
"ournets", OURNETS,
"ourdomains", OURDOMS,
0, NONE,
};
static Keyword actions[] = {
"allow", ACCEPT,
"block", BLOCKED,
"deny", DENIED,
"dial", DIALUP,
"delay", DELAY,
0, NONE,
};
static int hisaction;
static List ourdoms;
static List badguys;
static ulong v4peerip;
static char* getline(Biobuf*);
static int cidrcheck(char*);
static int
findkey(char *val, Keyword *p)
{
for(; p->name; p++)
if(strcmp(val, p->name) == 0)
break;
return p->code;
}
char*
actstr(int a)
{
char buf[32];
Keyword *p;
for(p=actions; p->name; p++)
if(p->code == a)
return p->name;
if(a==NONE)
return "none";
sprint(buf, "%d", a);
return buf;
}
int
getaction(char *s, char *type)
{
char buf[1024];
Keyword *k;
if(s == nil || *s == 0)
return ACCEPT;
for(k = actions; k->name != 0; k++){
snprint(buf, sizeof buf, "/mail/ratify/%s/%s/%s", k->name, type, s);
if(access(buf,0) >= 0)
return k->code;
}
return ACCEPT;
}
int
istrusted(char *s)
{
char buf[1024];
if(s == nil || *s == 0)
return 0;
snprint(buf, sizeof buf, "/mail/ratify/trusted/%s", s);
return access(buf,0) >= 0;
}
void
getconf(void)
{
Biobuf *bp;
char *cp, *p;
String *s;
char buf[512];
uchar addr[4];
v4parseip(addr, nci->rsys);
v4peerip = nhgetl(addr);
trusted = istrusted(nci->rsys);
hisaction = getaction(nci->rsys, "ip");
if(debug){
fprint(2, "istrusted(%s)=%d\n", nci->rsys, trusted);
fprint(2, "getaction(%s, ip)=%s\n", nci->rsys, actstr(hisaction));
}
snprint(buf, sizeof(buf), "%s/smtpd.conf", UPASLIB);
bp = sysopen(buf, "r", 0);
if(bp == 0)
return;
for(;;){
cp = getline(bp);
if(cp == 0)
break;
p = cp+strlen(cp)+1;
switch(findkey(cp, options)){
case NORELAY:
if(fflag == 0 && strcmp(p, "on") == 0)
fflag++;
break;
case DNSVERIFY:
if(rflag == 0 && strcmp(p, "on") == 0)
rflag++;
break;
case SAVEBLOCK:
if(sflag == 0 && strcmp(p, "on") == 0)
sflag++;
break;
case DOMNAME:
if(dom == 0)
dom = strdup(p);
break;
case OURNETS:
if (trusted == 0)
trusted = cidrcheck(p);
break;
case OURDOMS:
while(*p){
s = s_new();
s_append(s, p);
listadd(&ourdoms, s);
p += strlen(p)+1;
}
break;
default:
break;
}
}
sysclose(bp);
}
/*
* match a user name. the only meta-char is '*' which matches all
* characters. we only allow it as "*", which matches anything or
* an * at the end of the name (e.g., "username*") which matches
* trailing characters.
*/
static int
usermatch(char *pathuser, char *specuser)
{
int n;
n = strlen(specuser)-1;
if(specuser[n] == '*'){
if(n == 0) /* match everything */
return 0;
return strncmp(pathuser, specuser, n);
}
return strcmp(pathuser, specuser);
}
static int
dommatch(char *pathdom, char *specdom)
{
int n;
if (*specdom == '*'){
if (specdom[1] == '.' && specdom[2]){
specdom += 2;
n = strlen(pathdom)-strlen(specdom);
if(n == 0 || (n > 0 && pathdom[n-1] == '.'))
return strcmp(pathdom+n, specdom);
return n;
}
}
return strcmp(pathdom, specdom);
}
/*
* figure out action for this sender
*/
int
blocked(String *path)
{
String *lpath;
int action;
if(debug)
fprint(2, "blocked(%s)\n", s_to_c(path));
/* if the sender's IP address is blessed, ignore sender email address */
if(trusted){
if(debug)
fprint(2, "\ttrusted => trusted\n");
return TRUSTED;
}
/* if sender's IP address is blocked, ignore sender email address */
if(hisaction != ACCEPT){
if(debug)
fprint(2, "\thisaction=%s => %s\n", actstr(hisaction), actstr(hisaction));
return hisaction;
}
/* convert to lower case */
lpath = s_copy(s_to_c(path));
s_tolower(lpath);
/* classify */
action = getaction(s_to_c(lpath), "account");
if(debug)
fprint(2, "\tgetaction account %s => %s\n", s_to_c(lpath), actstr(action));
s_free(lpath);
return action;
}
/*
* get a canonicalized line: a string of null-terminated lower-case
* tokens with a two null bytes at the end.
*/
static char*
getline(Biobuf *bp)
{
char c, *cp, *p, *q;
int n;
static char *buf;
static int bufsize;
for(;;){
cp = Brdline(bp, '\n');
if(cp == 0)
return 0;
n = Blinelen(bp);
cp[n-1] = 0;
if(buf == 0 || bufsize < n+1){
bufsize += 512;
if(bufsize < n+1)
bufsize = n+1;
buf = realloc(buf, bufsize);
if(buf == 0)
break;
}
q = buf;
for (p = cp; *p; p++){
c = *p;
if(c == '\\' && p[1]) /* we don't allow \<newline> */
c = *++p;
else
if(c == '#')
break;
else
if(c == ' ' || c == '\t' || c == ',')
if(q == buf || q[-1] == 0)
continue;
else
c = 0;
*q++ = tolower(c);
}
if(q != buf){
if(q[-1])
*q++ = 0;
*q = 0;
break;
}
}
return buf;
}
static int
isourdom(char *s)
{
Link *l;
if(strchr(s, '.') == nil)
return 1;
for(l = ourdoms.first; l; l = l->next){
if(dommatch(s, s_to_c(l->p)) == 0)
return 1;
}
return 0;
}
int
forwarding(String *path)
{
char *cp, *s;
String *lpath;
if(debug)
fprint(2, "forwarding(%s)\n", s_to_c(path));
/* first check if they want loopback */
lpath = s_copy(s_to_c(s_restart(path)));
if(nci->rsys && *nci->rsys){
cp = s_to_c(lpath);
if(strncmp(cp, "[]!", 3) == 0){
found:
s_append(path, "[");
s_append(path, nci->rsys);
s_append(path, "]!");
s_append(path, cp+3);
s_terminate(path);
s_free(lpath);
return 0;
}
cp = strchr(cp,'!'); /* skip our domain and check next */
if(cp++ && strncmp(cp, "[]!", 3) == 0)
goto found;
}
/* if mail is from a trusted IP addr, allow it to forward */
if(trusted) {
s_free(lpath);
return 0;
}
/* sender is untrusted; ensure receiver is in one of our domains */
for(cp = s_to_c(lpath); *cp; cp++) /* convert receiver lc */
*cp = tolower(*cp);
for(s = s_to_c(lpath); cp = strchr(s, '!'); s = cp+1){
*cp = 0;
if(!isourdom(s)){
s_free(lpath);
return 1;
}
}
s_free(lpath);
return 0;
}
int
masquerade(String *path, char *him)
{
char *cp, *s;
String *lpath;
int rv = 0;
if(debug)
fprint(2, "masquerade(%s)\n", s_to_c(path));
if(trusted)
return 0;
if(path == nil)
return 0;
lpath = s_copy(s_to_c(path));
/* sender is untrusted; ensure receiver is in one of our domains */
for(cp = s_to_c(lpath); *cp; cp++) /* convert receiver lc */
*cp = tolower(*cp);
s = s_to_c(lpath);
/* scan first element of ! or last element of @ paths */
if((cp = strchr(s, '!')) != nil){
*cp = 0;
if(isourdom(s))
rv = 1;
} else if((cp = strrchr(s, '@')) != nil){
if(isourdom(cp+1))
rv = 1;
} else {
if(isourdom(him))
rv = 1;
}
s_free(lpath);
return rv;
}
/* this is a v4 only check */
static int
cidrcheck(char *cp)
{
char *p;
ulong a, m;
uchar addr[IPv4addrlen];
uchar mask[IPv4addrlen];
if(v4peerip == 0)
return 0;
/* parse a list of CIDR addresses comparing each to the peer IP addr */
while(cp && *cp){
v4parsecidr(addr, mask, cp);
a = nhgetl(addr);
m = nhgetl(mask);
/*
* if a mask isn't specified, we build a minimal mask
* instead of using the default mask for that net. in this
* case we never allow a class A mask (0xff000000).
*/
if(strchr(cp, '/') == 0){
m = 0xff000000;
p = cp;
for(p = strchr(p, '.'); p && p[1]; p = strchr(p+1, '.'))
m = (m>>8)|0xff000000;
/* force at least a class B */
m |= 0xffff0000;
}
if((v4peerip&m) == a)
return 1;
cp += strlen(cp)+1;
}
return 0;
}
int
isbadguy(void)
{
Link *l;
/* check if this IP address is banned */
for(l = badguys.first; l; l = l->next)
if(cidrcheck(s_to_c(l->p)))
return 1;
return 0;
}
void
addbadguy(char *p)
{
listadd(&badguys, s_copy(p));
};
char*
dumpfile(char *sender)
{
int i, fd;
ulong h;
static char buf[512];
char *cp;
if (sflag == 1){
cp = ctime(time(0));
cp[7] = 0;
if(cp[8] == ' ')
sprint(buf, "%s/queue.dump/%s%c", SPOOL, cp+4, cp[9]);
else
sprint(buf, "%s/queue.dump/%s%c%c", SPOOL, cp+4, cp[8], cp[9]);
cp = buf+strlen(buf);
if(access(buf, 0) < 0 && sysmkdir(buf, 0777) < 0)
return "/dev/null";
h = 0;
while(*sender)
h = h*257 + *sender++;
for(i = 0; i < 50; i++){
h += lrand();
sprint(cp, "/%lud", h);
if(access(buf, 0) >= 0)
continue;
fd = syscreate(buf, ORDWR, 0666);
if(fd >= 0){
if(debug)
fprint(2, "saving in %s\n", buf);
close(fd);
return buf;
}
}
}
return "/dev/null";
}
char *validator = "/mail/lib/validateaddress";
int
recipok(char *user)
{
char *cp, *p, c;
char buf[512];
int n;
Biobuf *bp;
int pid;
Waitmsg *w;
if(shellchars(user)){
syslog(0, "smtpd", "shellchars in user name");
return 0;
}
if(access(validator, AEXEC) == 0)
switch(pid = fork()) {
case -1:
break;
case 0:
execl(validator, "validateaddress", user, nil);
exits(0);
default:
while(w = wait()) {
if(w->pid != pid)
continue;
if(w->msg[0] != 0){
/*
syslog(0, "smtpd", "validateaddress %s: %s", user, w->msg);
*/
return 0;
}
break;
}
}
snprint(buf, sizeof(buf), "%s/names.blocked", UPASLIB);
bp = sysopen(buf, "r", 0);
if(bp == 0)
return 1;
for(;;){
cp = Brdline(bp, '\n');
if(cp == 0)
break;
n = Blinelen(bp);
cp[n-1] = 0;
while(*cp == ' ' || *cp == '\t')
cp++;
for(p = cp; c = *p; p++){
if(c == '#')
break;
if(c == ' ' || c == '\t')
break;
}
if(p > cp){
*p = 0;
if(cistrcmp(user, cp) == 0){
syslog(0, "smtpd", "names.blocked blocks %s", user);
Bterm(bp);
return 0;
}
}
}
Bterm(bp);
return 1;
}
/*
* a user can opt out of spam filtering by creating
* a file in his mail directory named 'nospamfiltering'.
*/
int
optoutofspamfilter(char *addr)
{
char *p, *f;
int rv;
p = strchr(addr, '!');
if(p)
p++;
else
p = addr;
rv = 0;
f = smprint("/mail/box/%s/nospamfiltering", p);
if(f != nil){
rv = access(f, 0)==0;
free(f);
}
return rv;
}

25
src/cmd/upas/smtp/y.tab.h Normal file
View File

@@ -0,0 +1,25 @@
#define WORD 57346
#define DATE 57347
#define RESENT_DATE 57348
#define RETURN_PATH 57349
#define FROM 57350
#define SENDER 57351
#define REPLY_TO 57352
#define RESENT_FROM 57353
#define RESENT_SENDER 57354
#define RESENT_REPLY_TO 57355
#define SUBJECT 57356
#define TO 57357
#define CC 57358
#define BCC 57359
#define RESENT_TO 57360
#define RESENT_CC 57361
#define RESENT_BCC 57362
#define REMOTE 57363
#define PRECEDENCE 57364
#define MIMEVERSION 57365
#define CONTENTTYPE 57366
#define MESSAGEID 57367
#define RECEIVED 57368
#define MAILER 57369
#define BADTOKEN 57370

17
src/cmd/upas/unesc/mkfile Normal file
View File

@@ -0,0 +1,17 @@
</$objtype/mkfile
TARG=unesc
OFILES=unesc.$O\
BIN=/$objtype/bin/upas
CC=pcc -c
CFLAGS=-B
UPDATE=\
mkfile\
$HFILES\
${OFILES:%.$O=%.c}\
</sys/src/cmd/mkone

View File

@@ -0,0 +1,48 @@
/*
* upas/unesc - interpret =?foo?bar?=char?= escapes
*/
#include <stdio.h>
#include <stdlib.h>
int
hex(int c)
{
if('0' <= c && c <= '9')
return c - '0';
if('A' <= c && c <= 'F')
return c - 'A' + 10;
if('a' <= c && c <= 'f')
return c - 'a' + 10;
return 0;
}
void
main(int argc, char **argv)
{
int c;
while((c=getchar()) != EOF){
if(c == '='){
if((c=getchar()) == '?'){
while((c=getchar()) != EOF && c != '?')
continue;
while((c=getchar()) != EOF && c != '?')
continue;
while((c=getchar()) != EOF && c != '?'){
if(c == '='){
c = hex(getchar()) << 4;
c |= hex(getchar());
}
putchar(c);
}
(void) getchar(); /* consume '=' */
}else{
putchar('=');
putchar(c);
}
}else
putchar(c);
}
exit(0);
}

20
src/cmd/upas/vf/mkfile Normal file
View File

@@ -0,0 +1,20 @@
<$PLAN9/src/mkhdr
TARG=vf
OFILES=vf.$O\
LIB=../common/libcommon.a\
HFILES=../common/common.h\
../common/sys.h\
BIN=$PLAN9/bin/upas
UPDATE=\
mkfile\
$HFILES\
${OFILES:%.$O=%.c}\
<$PLAN9/src/mkone
CFLAGS=$CFLAGS -I../common

1110
src/cmd/upas/vf/vf.c Normal file

File diff suppressed because it is too large Load Diff