commit 5cdb17983ae6e6367ad7a940cb219eab247a9304 from: rsc date: Sat Oct 29 16:26:44 2005 UTC Thanks to John Cummings. commit - cd3745196389579fb78b9b01ef1daefb5a57aa71 commit + 5cdb17983ae6e6367ad7a940cb219eab247a9304 blob - /dev/null blob + 67f367981d81015260bf8a7bce01b9ff594d41b8 (mode 644) --- /dev/null +++ src/cmd/upas/common/libsys.c @@ -0,0 +1,1001 @@ +#include "common.h" +#include +#include + +/* + * number of predefined fd's + */ +int nsysfile=3; + +static char err[Errlen]; + +/* + * return the date + */ +extern char * +thedate(void) +{ + static char now[64]; + char *cp; + + strcpy(now, ctime(time(0))); + cp = strchr(now, '\n'); + if(cp) + *cp = 0; + return now; +} + +/* + * return the user id of the current user + */ +extern char * +getlog(void) +{ + return getuser(); +} +#if 0 /* jpc */ +extern char * +getlog(void) +{ + static char user[64]; + int fd; + int n; + + fd = open("/dev/user", 0); + if(fd < 0) + return nil; + if((n=read(fd, user, sizeof(user)-1)) <= 0) + return nil; + close(fd); + user[n] = 0; + return user; +} +#endif /* jpc */ +/* + * return the lock name (we use one lock per directory) + */ +static String * +lockname(char *path) +{ + String *lp; + char *cp; + + /* + * get the name of the lock file + */ + lp = s_new(); + cp = strrchr(path, '/'); + if(cp) + s_nappend(lp, path, cp - path + 1); + s_append(lp, "L.mbox"); + + return lp; +} + +int +syscreatelocked(char *path, int mode, int perm) +{ + return create(path, mode, DMEXCL|perm); +} + +int +sysopenlocked(char *path, int mode) +{ +/* return open(path, OEXCL|mode);/**/ + return open(path, mode); /* until system call is fixed */ +} + +int +sysunlockfile(int fd) +{ + return close(fd); +} + +/* + * try opening a lock file. If it doesn't exist try creating it. + */ +static int +openlockfile(Mlock *l) +{ + int fd; + Dir *d; + Dir nd; + char *p; + + fd = open(s_to_c(l->name), OREAD); + if(fd >= 0){ + l->fd = fd; + return 0; + } + + d = dirstat(s_to_c(l->name)); + if(d == nil){ + /* file doesn't exist */ + /* try creating it */ + fd = create(s_to_c(l->name), OREAD, DMEXCL|0666); + if(fd >= 0){ + nulldir(&nd); + nd.mode = DMEXCL|0666; + if(dirfwstat(fd, &nd) < 0){ + /* if we can't chmod, don't bother */ + /* live without the lock but log it */ + syslog(0, "mail", "lock error: %s: %r", s_to_c(l->name)); + remove(s_to_c(l->name)); + } + l->fd = fd; + return 0; + } + + /* couldn't create */ + /* do we have write access to the directory? */ + p = strrchr(s_to_c(l->name), '/'); + if(p != 0){ + *p = 0; + fd = access(s_to_c(l->name), 2); + *p = '/'; + if(fd < 0){ + /* live without the lock but log it */ + syslog(0, "mail", "lock error: %s: %r", s_to_c(l->name)); + return 0; + } + } else { + fd = access(".", 2); + if(fd < 0){ + /* live without the lock but log it */ + syslog(0, "mail", "lock error: %s: %r", s_to_c(l->name)); + return 0; + } + } + } else + free(d); + + return 1; /* try again later */ +} + +#define LSECS 5*60 + +/* + * Set a lock for a particular file. The lock is a file in the same directory + * and has L. prepended to the name of the last element of the file name. + */ +extern Mlock * +syslock(char *path) +{ + Mlock *l; + int tries; + + l = mallocz(sizeof(Mlock), 1); + if(l == 0) + return nil; + + l->name = lockname(path); + + /* + * wait LSECS seconds for it to unlock + */ + for(tries = 0; tries < LSECS*2; tries++){ + switch(openlockfile(l)){ + case 0: + return l; + case 1: + sleep(500); + break; + default: + goto noway; + } + } + +noway: + s_free(l->name); + free(l); + return nil; +} + +/* + * like lock except don't wait + */ +extern Mlock * +trylock(char *path) +{ + Mlock *l; + char buf[1]; + int fd; + + l = malloc(sizeof(Mlock)); + if(l == 0) + return 0; + + l->name = lockname(path); + if(openlockfile(l) != 0){ + s_free(l->name); + free(l); + return 0; + } + + /* fork process to keep lock alive */ + 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; +} + +extern void +syslockrefresh(Mlock *l) +{ + char buf[1]; + + pread(l->fd, buf, 1, 0); +} + +extern void +sysunlock(Mlock *l) +{ + if(l == 0) + return; + if(l->name){ + s_free(l->name); + } + if(l->fd >= 0) + close(l->fd); + if(l->pid > 0) + postnote(PNPROC, l->pid, "time to die"); + free(l); +} + +/* + * Open a file. The modes are: + * + * l - locked + * a - set append permissions + * r - readable + * w - writable + * A - append only (doesn't exist in Bio) + */ +extern Biobuf * +sysopen(char *path, char *mode, ulong perm) +{ + int sysperm; + int sysmode; + int fd; + int docreate; + int append; + int truncate; + Dir *d, nd; + Biobuf *bp; + + /* + * decode the request + */ + sysperm = 0; + sysmode = -1; + docreate = 0; + append = 0; + truncate = 0; + for(; mode && *mode; mode++) + switch(*mode){ + case 'A': + sysmode = OWRITE; + append = 1; + break; + case 'c': + docreate = 1; + break; + case 'l': + sysperm |= DMEXCL; + break; + case 'a': + sysperm |= DMAPPEND; + break; + case 'w': + if(sysmode == -1) + sysmode = OWRITE; + else + sysmode = ORDWR; + break; + case 'r': + if(sysmode == -1) + sysmode = OREAD; + else + sysmode = ORDWR; + break; + case 't': + truncate = 1; + break; + default: + break; + } + switch(sysmode){ + case OREAD: + case OWRITE: + case ORDWR: + break; + default: + if(sysperm&DMAPPEND) + sysmode = OWRITE; + else + sysmode = OREAD; + break; + } + + /* + * create file if we need to + */ + if(truncate) + sysmode |= OTRUNC; + fd = open(path, sysmode); + if(fd < 0){ + d = dirstat(path); + if(d == nil){ + if(docreate == 0) + return 0; + + fd = create(path, sysmode, sysperm|perm); + if(fd < 0) + return 0; + nulldir(&nd); + nd.mode = sysperm|perm; + dirfwstat(fd, &nd); + } else { + free(d); + return 0; + } + } + + bp = (Biobuf*)malloc(sizeof(Biobuf)); + if(bp == 0){ + close(fd); + return 0; + } + memset(bp, 0, sizeof(Biobuf)); + Binit(bp, fd, sysmode&~OTRUNC); + + if(append) + Bseek(bp, 0, 2); + return bp; +} + +/* + * close the file, etc. + */ +int +sysclose(Biobuf *bp) +{ + int rv; + + rv = Bterm(bp); + close(Bfildes(bp)); + free(bp); + return rv; +} + +/* + * create a file + */ +int +syscreate(char *file, int mode, ulong perm) +{ + return create(file, mode, perm); +} + +/* + * make a directory + */ +int +sysmkdir(char *file, ulong perm) +{ + int fd; + + if((fd = create(file, OREAD, DMDIR|perm)) < 0) + return -1; + close(fd); + return 0; +} + +/* + * change the group of a file + */ +int +syschgrp(char *file, char *group) +{ + Dir nd; + + if(group == 0) + return -1; + nulldir(&nd); + nd.gid = group; + return dirwstat(file, &nd); +} + +extern int +sysdirreadall(int fd, Dir **d) +{ + return dirreadall(fd, d); +} + +/* + * read in the system name + */ +extern char * +sysname_read(void) +{ + static char name[128]; + char *cp; + + cp = getenv("site"); + if(cp == 0 || *cp == 0) + cp = alt_sysname_read(); + if(cp == 0 || *cp == 0) + cp = "kremvax"; + strecpy(name, name+sizeof name, cp); + return name; +} +extern char * +alt_sysname_read(void) +{ + static char name[128]; + int n, fd; + + fd = open("/dev/sysname", OREAD); + if(fd < 0) + return 0; + n = read(fd, name, sizeof(name)-1); + close(fd); + if(n <= 0) + return 0; + name[n] = 0; + return name; +} + +/* + * get all names + */ +extern char** +sysnames_read(void) +{ + static char **namev; + Ndbtuple *t, *nt; + Ndb* db; + Ndbs s; + int n; + char *cp; + + if(namev) + return namev; + + /* free(csgetvalue(0, "sys", alt_sysname_read(), "dom", &t)); jpc */ + db = ndbopen(unsharp("#9/ndb/local")); + free(ndbgetvalue(db, &s, "sys", sysname(),"dom", &t)); + /* t = nil; /* jpc */ + /* fprint(2,"csgetvalue called: fixme"); /* jpc */ + + n = 0; + for(nt = t; nt; nt = nt->entry) + if(strcmp(nt->attr, "dom") == 0) + n++; + + namev = (char**)malloc(sizeof(char *)*(n+3)); + + if(namev){ + n = 0; + namev[n++] = strdup(sysname_read()); + cp = alt_sysname_read(); + if(cp) + namev[n++] = strdup(cp); + for(nt = t; nt; nt = nt->entry) + if(strcmp(nt->attr, "dom") == 0) + namev[n++] = strdup(nt->val); + namev[n] = 0; + } + if(t) + ndbfree(t); + + return namev; +} + +/* + * read in the domain name + */ +extern char * +domainname_read(void) +{ + char **namev; + + for(namev = sysnames_read(); *namev; namev++) + if(strchr(*namev, '.')) + return *namev; + return 0; +} + +/* + * return true if the last error message meant file + * did not exist. + */ +extern int +e_nonexistent(void) +{ + rerrstr(err, sizeof(err)); + return strcmp(err, "file does not exist") == 0; +} + +/* + * return true if the last error message meant file + * was locked. + */ +extern int +e_locked(void) +{ + rerrstr(err, sizeof(err)); + return strcmp(err, "open/create -- file is locked") == 0; +} + +/* + * return the length of a file + */ +extern long +sysfilelen(Biobuf *fp) +{ + Dir *d; + long rv; + + d = dirfstat(Bfildes(fp)); + if(d == nil) + return -1; + rv = d->length; + free(d); + return rv; +} + +/* + * remove a file + */ +extern int +sysremove(char *path) +{ + return remove(path); +} + +/* + * rename a file, fails unless both are in the same directory + */ +extern int +sysrename(char *old, char *new) +{ + Dir d; + char *obase; + char *nbase; + + obase = strrchr(old, '/'); + nbase = strrchr(new, '/'); + if(obase){ + if(nbase == 0) + return -1; + if(strncmp(old, new, obase-old) != 0) + return -1; + nbase++; + } else { + if(nbase) + return -1; + nbase = new; + } + nulldir(&d); + d.name = nbase; + return dirwstat(old, &d); +} + +/* + * see if a file exists + */ +extern int +sysexist(char *file) +{ + Dir *d; + + d = dirstat(file); + if(d == nil) + return 0; + free(d); + return 1; +} + +/* + * return nonzero if file is a directory + */ +extern int +sysisdir(char *file) +{ + Dir *d; + int rv; + + d = dirstat(file); + if(d == nil) + return 0; + rv = d->mode & DMDIR; + free(d); + return rv; +} + +/* + * kill a process or process group + */ + +static int +stomp(int pid, char *file) +{ + char name[64]; + int fd; + + snprint(name, sizeof(name), "/proc/%d/%s", pid, file); + fd = open(name, 1); + if(fd < 0) + return -1; + if(write(fd, "die: yankee pig dog\n", sizeof("die: yankee pig dog\n") - 1) <= 0){ + close(fd); + return -1; + } + close(fd); + return 0; + +} + +/* + * kill a process + */ +extern int +syskill(int pid) +{ + return stomp(pid, "note"); + +} + +/* + * kill a process group + */ +extern int +syskillpg(int pid) +{ + return stomp(pid, "notepg"); +} + +extern int +sysdetach(void) +{ + if(rfork(RFENVG|RFNAMEG|RFNOTEG) < 0) { + werrstr("rfork failed"); + return -1; + } + return 0; +} + +/* + * catch a write on a closed pipe + */ +static int *closedflag; +static int +catchpipe(void *a, char *msg) +{ + static char *foo = "sys: write on closed pipe"; + + USED(a); + if(strncmp(msg, foo, strlen(foo)) == 0){ + if(closedflag) + *closedflag = 1; + return 1; + } + return 0; +} +void +pipesig(int *flagp) +{ + closedflag = flagp; + atnotify(catchpipe, 1); +} +void +pipesigoff(void) +{ + atnotify(catchpipe, 0); +} + +void +exit9(int i) +{ + char buf[32]; + + if(i == 0) + exits(0); + snprint(buf, sizeof(buf), "%d", i); + exits(buf); +} + +static int +islikeatty(int fd) +{ + Dir *d; + int rv; + + d = dirfstat(fd); + if(d == nil) + return 0; + rv = strcmp(d->name, "cons") == 0; + free(d); + return rv; +} + +#if 0 +/* jpc */ +static int +islikeatty(int fd) +{ + char buf[64]; + + if(fd2path(fd, buf, sizeof buf) != 0) + return 0; + + /* might be /mnt/term/dev/cons */ + return strlen(buf) >= 9 && strcmp(buf+strlen(buf)-9, "/dev/cons") == 0; +} +#endif + +extern int +holdon(void) +{ + int fd; + + if(!islikeatty(0)) + return -1; + + fd = open("/dev/consctl", OWRITE); + write(fd, "holdon", 6); + + return fd; +} + +extern int +sysopentty(void) +{ + return open("/dev/cons", ORDWR); +} + +extern void +holdoff(int fd) +{ + write(fd, "holdoff", 7); + close(fd); +} + +extern int +sysfiles(void) +{ + return 128; +} + +/* + * expand a path relative to the user's mailbox directory + * + * if the path starts with / or ./, don't change it + * + */ +extern String * +mboxpath(char *path, char *user, String *to, int dot) +{ + if (dot || *path=='/' || strncmp(path, "./", 2) == 0 + || strncmp(path, "../", 3) == 0) { + to = s_append(to, path); + } else { + to = s_append(to, unsharp(MAILROOT)); + to = s_append(to, "/box/"); + to = s_append(to, user); + to = s_append(to, "/"); + to = s_append(to, path); + } + return to; +} + +extern String * +mboxname(char *user, String *to) +{ + return mboxpath("mbox", user, to, 0); +} + +extern String * +deadletter(String *to) /* pass in sender??? */ +{ + char *cp; + + cp = getlog(); + if(cp == 0) + return 0; + return mboxpath("dead.letter", cp, to, 0); +} + +char * +homedir(char *user) +{ + USED(user); + return getenv("home"); +} + +String * +readlock(String *file) +{ + char *cp; + + cp = getlog(); + if(cp == 0) + return 0; + return mboxpath("reading", cp, file, 0); +} + +String * +username(String *from) +{ + int n; + Biobuf *bp; + char *p, *q; + String *s; + + bp = Bopen("/adm/keys.who", OREAD); + if(bp == 0) + bp = Bopen("/adm/netkeys.who", OREAD); + if(bp == 0) + return 0; + + s = 0; + n = strlen(s_to_c(from)); + for(;;) { + p = Brdline(bp, '\n'); + if(p == 0) + break; + p[Blinelen(bp)-1] = 0; + if(strncmp(p, s_to_c(from), n)) + continue; + p += n; + if(*p != ' ' && *p != '\t') /* must be full match */ + continue; + while(*p && (*p == ' ' || *p == '\t')) + p++; + if(*p == 0) + continue; + for(q = p; *q; q++) + if(('0' <= *q && *q <= '9') || *q == '<') + break; + while(q > p && q[-1] != ' ' && q[-1] != '\t') + q--; + while(q > p && (q[-1] == ' ' || q[-1] == '\t')) + q--; + *q = 0; + s = s_new(); + s_append(s, "\""); + s_append(s, p); + s_append(s, "\""); + break; + } + Bterm(bp); + return s; +} + +char * +remoteaddr(int fd, char *dir) +{ + char buf[128], *p; + int n; + + if(dir == 0){ + fprint(2,"remoteaddr: called fd2path: fixme\n"); /* jpc + if(fd2path(fd, buf, sizeof(buf)) != 0) + return ""; */ + + /* parse something of the form /net/tcp/nnnn/data */ + p = strrchr(buf, '/'); + if(p == 0) + return ""; + strncpy(p+1, "remote", sizeof(buf)-(p-buf)-2); + } else + snprint(buf, sizeof buf, "%s/remote", dir); + buf[sizeof(buf)-1] = 0; + + fd = open(buf, OREAD); + if(fd < 0) + return ""; + n = read(fd, buf, sizeof(buf)-1); + close(fd); + if(n > 0){ + buf[n] = 0; + p = strchr(buf, '!'); + if(p) + *p = 0; + return strdup(buf); + } + return ""; +} + +// create a file and +// 1) ensure the modes we asked for +// 2) make gid == uid +static int +docreate(char *file, int perm) +{ + int fd; + Dir ndir; + Dir *d; + + // create the mbox + fd = create(file, OREAD, perm); + if(fd < 0){ + fprint(2, "couldn't create %s\n", file); + return -1; + } + d = dirfstat(fd); + if(d == nil){ + fprint(2, "couldn't stat %s\n", file); + return -1; + } + nulldir(&ndir); + ndir.mode = perm; + ndir.gid = d->uid; + if(dirfwstat(fd, &ndir) < 0) + fprint(2, "couldn't chmod %s: %r\n", file); + close(fd); + return 0; +} + +// create a mailbox +int +creatembox(char *user, char *folder) +{ + char *p; + String *mailfile; + char buf[512]; + Mlock *ml; + + mailfile = s_new(); + if(folder == 0) + mboxname(user, mailfile); + else { + snprint(buf, sizeof(buf), "%s/mbox", folder); + mboxpath(buf, user, mailfile, 0); + } + + // don't destroy existing mailbox + if(access(s_to_c(mailfile), 0) == 0){ + fprint(2, "mailbox already exists\n"); + return -1; + } + fprint(2, "creating new mbox: %s\n", s_to_c(mailfile)); + + // make sure preceding levels exist + for(p = s_to_c(mailfile); p; p++) { + if(*p == '/') /* skip leading or consecutive slashes */ + continue; + p = strchr(p, '/'); + if(p == 0) + break; + *p = 0; + if(access(s_to_c(mailfile), 0) != 0){ + if(docreate(s_to_c(mailfile), DMDIR|0711) < 0) + return -1; + } + *p = '/'; + } + + // create the mbox + if(docreate(s_to_c(mailfile), 0622|DMAPPEND|DMEXCL) < 0) + return -1; + + /* + * create the lock file if it doesn't exist + */ + ml = trylock(s_to_c(mailfile)); + if(ml != nil) + sysunlock(ml); + + return 0; +} blob - /dev/null blob + 5347c6550c2b586e599962a0069ca3179f28e055 (mode 644) --- /dev/null +++ src/cmd/upas/common/mail.c @@ -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; +} blob - /dev/null blob + c7beae817b5468dc7ffd4a74f9f5073dd9fe9b9e (mode 644) --- /dev/null +++ src/cmd/upas/common/makefile @@ -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 + blob - /dev/null blob + 8cfaf267e8c51405770c3eb38b1ff6acd6f2f856 (mode 644) --- /dev/null +++ src/cmd/upas/common/mkfile @@ -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 blob - /dev/null blob + 16b21aefe4ad3db08a1f982ed6183e9cb2b64ed0 (mode 644) --- /dev/null +++ src/cmd/upas/common/process.c @@ -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); +} blob - /dev/null blob + 960aebb37327c9dc5b510a4a6e78d021d32f01f4 (mode 644) --- /dev/null +++ src/cmd/upas/common/sys.h @@ -0,0 +1,85 @@ +/* + * System dependent header files for research + */ + +#include +#include +#include +#include +#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 blob - /dev/null blob + dd4572db84ee193ac151c461338dad82c970fe2f (mode 644) --- /dev/null +++ src/cmd/upas/filterkit/dat.h @@ -0,0 +1,8 @@ +typedef struct Addr Addr; +struct Addr +{ + Addr *next; + char *val; +}; + +extern Addr* readaddrs(char*, Addr*); blob - /dev/null blob + 33903708a4f6d40e97464c6cce657ef590de8c46 (mode 644) --- /dev/null +++ src/cmd/upas/filterkit/deliver.c @@ -0,0 +1,60 @@ +#include "dat.h" +#include "common.h" + +void +usage(void) +{ + fprint(2, "usage: %s recipient fromaddr-file mbox\n", argv0); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + int fd; + char now[30]; + Addr *a; + char *deliveredto; + Mlock *l; + int bytes; + + ARGBEGIN{ + }ARGEND; + + if(argc != 3) + usage(); + + deliveredto = strrchr(argv[0], '!'); + if(deliveredto == nil) + deliveredto = argv[0]; + else + deliveredto++; + a = readaddrs(argv[1], nil); + if(a == nil) + sysfatal("missing from address"); + + l = syslock(argv[2]); + + /* append to mbox */ + fd = open(argv[2], OWRITE); + if(fd < 0) + sysfatal("opening mailbox: %r"); + seek(fd, 0, 2); + strncpy(now, ctime(time(0)), sizeof(now)); + now[28] = 0; + if(fprint(fd, "From %s %s\n", a->val, now) < 0) + sysfatal("writing mailbox: %r"); + + /* copy message handles escapes and any needed new lines */ + bytes = appendfiletombox(0, fd); + if(bytes < 0) + sysfatal("writing mailbox: %r"); + + close(fd); + sysunlock(l); + + /* log it */ + syslog(0, "mail", "delivered %s From %s %s (%s) %d", deliveredto, + a->val, now, argv[0], bytes); + exits(0); +} blob - /dev/null blob + 6c23dcc19c19a6f1a427af2a9e345506e4f93c2d (mode 644) --- /dev/null +++ src/cmd/upas/filterkit/list.c @@ -0,0 +1,315 @@ +#include +#include +#include +#include +#include +#include +#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(); +} blob - /dev/null blob + c077d56411bd1392db7b4aee235e33aea2b9cd61 (mode 644) --- /dev/null +++ src/cmd/upas/filterkit/mkfile @@ -0,0 +1,21 @@ + $TMP +upas/list add /mail/box/$user/_pattern $TMP >[2] /dev/null +rm $TMP + +# send mail +upas/send $options $dests blob - /dev/null blob + 82ef9d1fcd03a221728f6c6d88a842ad66080b8d (mode 644) --- /dev/null +++ src/cmd/upas/filterkit/pipeto.sample @@ -0,0 +1,73 @@ +#!/bin/rc + +# create a /tmp for here documents +rfork en +bind -c /mail/tmp /tmp + +KEY=whocares +USER=ken + +RECIP=$1 +MBOX=$2 +PF=/mail/box/$USER/_pattern +TMP=/mail/tmp/mine.$pid +BIN=/bin/upas +D=/mail/fs/mbox/1 + +# save and parse the mail file +{sed '/^$/,$ s/^From / From /'; echo} > $TMP +upas/fs -f $TMP + +# if we like the source +# or if the subject contains a valid token +# then deliver the mail and allow all the addresses +if( $BIN/list check $PF $D/from $D/sender $D/replyto ) +{ + $BIN/deliver $RECIP $D/from $MBOX < $D/raw + $BIN/list add $PF $D/from $D/to $D/cc $D/sender + rm $TMP + exit 0 +} +switch($status){ +case *!match* + echo `{date} dropped $RECIP From `{cat $D/replyto} >> /mail/box/$USER/_bounced >[2] /dev/null + rm $TMP + exit 0 +} +if ( $BIN/token $KEY $D/subject ) +{ + $BIN/deliver $RECIP $D/from $MBOX < $D/raw + $BIN/list add $PF $D/from $D/to $D/cc $D/sender + rm $TMP + echo `{date} added $RECIP From `{cat $D/replyto} \ + >> /mail/box/$USER/_bounced >[2] /dev/null + exit 0 +} + +# don't recognize the sender so +# return the message with instructions +TOKEN=`{upas/token $KEY} +upasname=/dev/null +{{cat; cat $D/raw} | upas/send `{cat $D/replyto}}<> /mail/box/$USER/_bounced >[2] /dev/null + +rv=$status +rm $TMP +exit $status blob - /dev/null blob + 5794f7322a00bf717bae0794d0cec6420ccf39af (mode 644) --- /dev/null +++ src/cmd/upas/filterkit/pipeto.sample-hold @@ -0,0 +1,43 @@ +#!/bin/rc + +# create a /tmp for here documents +rfork en +bind -c /mail/tmp /tmp + +KEY=whocares +USER=ken + +RECIP=$1 +MBOX=$2 +PF=/mail/box/$USER/_pattern +TMP=/mail/tmp/mine.$pid +BIN=/bin/upas +D=/mail/fs/mbox/1 + +# save and parse the mail file +{sed '/^$/,$ s/^From / From /'; echo} > $TMP +upas/fs -f $TMP + +# if we like the source +# or if the subject contains a valid token +# then deliver the mail and allow all the addresses +if( $BIN/list check $PF $D/from $D/sender $D/replyto ) +{ + $BIN/deliver $RECIP $D/from $MBOX < $D/raw + $BIN/list add $PF $D/from $D/to $D/cc $D/sender + rm $TMP + exit 0 +} +switch($status){ +case *!match* + echo `{date} dropped $RECIP From `{cat $D/replyto} >> /mail/box/$USER/_bounced >[2] /dev/null + rm $TMP + exit 0 +} + +# don't recognize the sender so hold the message +$BIN/deliver $RECIP $D/from /mail/box/$USER/_held < $D/raw + +rv=$status +rm $TMP +exit $status blob - /dev/null blob + 9aadc1ab188ecd8a280acc8fbbc1a7f59e682bae (mode 644) --- /dev/null +++ src/cmd/upas/filterkit/readaddrs.c @@ -0,0 +1,98 @@ +#include +#include +#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; +} blob - /dev/null blob + 8fbcda668d5b6a865c5b7d870b1a9d19e4cde22a (mode 644) --- /dev/null +++ src/cmd/upas/filterkit/token.c @@ -0,0 +1,89 @@ +#include +#include +#include +#include +#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); +} blob - /dev/null blob + ffcbf5b3caabb66243e11f1749e4d965b98815a5 (mode 644) --- /dev/null +++ src/cmd/upas/fs/dat.h @@ -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; + blob - /dev/null blob + b68b8d2e3ef111262257f9a41dc05bef16bb1f0c (mode 644) --- /dev/null +++ src/cmd/upas/fs/fs.c @@ -0,0 +1,1704 @@ +#include "common.h" +#include +#include +#include +#include <9pclient.h> /* jpc */ +#include /* jpc */ +#include "dat.h" + +enum +{ + OPERM = 0x3, // mask of all permission types in open mode +}; + +typedef struct Fid Fid; + +struct Fid +{ + Qid qid; + short busy; + short open; + int fid; + Fid *next; + Mailbox *mb; + Message *m; + Message *mtop; // top level message + + //finger pointers to speed up reads of large directories + long foff; // offset/DIRLEN of finger + Message *fptr; // pointer to message at off + int fvers; // mailbox version when finger was saved +}; + +ulong path; // incremented for each new file +Fid *fids; +int mfd[2]; +char user[Elemlen]; +int messagesize = 4*1024*IOHDRSZ; +uchar mdata[8*1024*IOHDRSZ]; +uchar mbuf[8*1024*IOHDRSZ]; +Fcall thdr; +Fcall rhdr; +int fflg; +char *mntpt; +int biffing; +int plumbing = 1; + +QLock mbllock; +Mailbox *mbl; + +Fid *newfid(int); +void error(char*); +void io(void); +void *erealloc(void*, ulong); +void *emalloc(ulong); +void usage(void); +void run_io(void*); +void reader(void*); +int readheader(Message*, char*, int, int); +int cistrncmp(char*, char*, int); +int tokenconvert(String*, char*, int); +String* stringconvert(String*, char*, int); +void post(char*, char*, int); + +char *rflush(Fid*), *rauth(Fid*), + *rattach(Fid*), *rwalk(Fid*), + *ropen(Fid*), *rcreate(Fid*), + *rread(Fid*), *rwrite(Fid*), *rclunk(Fid*), + *rremove(Fid*), *rstat(Fid*), *rwstat(Fid*), + *rversion(Fid*); + +char *(*fcalls[])(Fid*) = { + [Tflush] rflush, + [Tversion] rversion, + [Tauth] rauth, + [Tattach] rattach, + [Twalk] rwalk, + [Topen] ropen, + [Tcreate] rcreate, + [Tread] rread, + [Twrite] rwrite, + [Tclunk] rclunk, + [Tremove] rremove, + [Tstat] rstat, + [Twstat] rwstat, +}; + +char Eperm[] = "permission denied"; +char Enotdir[] = "not a directory"; +char Enoauth[] = "upas/fs: authentication not required"; +char Enotexist[] = "file does not exist"; +char Einuse[] = "file in use"; +char Eexist[] = "file exists"; +char Enotowner[] = "not owner"; +char Eisopen[] = "file already open for I/O"; +char Excl[] = "exclusive use file already open"; +char Ename[] = "illegal name"; +char Ebadctl[] = "unknown control message"; + +char *dirtab[] = +{ +[Qdir] ".", +[Qbody] "body", +[Qbcc] "bcc", +[Qcc] "cc", +[Qdate] "date", +[Qdigest] "digest", +[Qdisposition] "disposition", +[Qfilename] "filename", +[Qfrom] "from", +[Qheader] "header", +[Qinfo] "info", +[Qinreplyto] "inreplyto", +[Qlines] "lines", +[Qmimeheader] "mimeheader", +[Qmessageid] "messageid", +[Qraw] "raw", +[Qrawunix] "rawunix", +[Qrawbody] "rawbody", +[Qrawheader] "rawheader", +[Qreplyto] "replyto", +[Qsender] "sender", +[Qsubject] "subject", +[Qto] "to", +[Qtype] "type", +[Qunixdate] "unixdate", +[Qunixheader] "unixheader", +[Qctl] "ctl", +[Qmboxctl] "ctl", +}; + +enum +{ + Hsize= 1277, +}; + +Hash *htab[Hsize]; + +int debug; +int fflag; +int logging; + +void +usage(void) +{ + fprint(2, "usage: %s [-b -m mountpoint]\n", argv0); + threadexits("usage"); +} + +void +notifyf(void *a, char *s) +{ + USED(a); + if(strncmp(s, "interrupt", 9) == 0) + noted(NCONT); + noted(NDFLT); +} + +void +threadmain(int argc, char *argv[]) +{ + int p[2], std, nodflt; + char maildir[128]; + char mbox[128]; + char *mboxfile, *err; + char srvfile[64]; + int srvpost; + + rfork(RFNOTEG); + mntpt = nil; + fflag = 0; + mboxfile = nil; + std = 0; + nodflt = 0; + srvpost = 0; + + ARGBEGIN{ + case 'b': + biffing = 1; + break; + case 'f': + fflag = 1; + mboxfile = ARGF(); + break; + case 'm': + mntpt = ARGF(); + break; + case 'd': + debug = 1; + break; + case 'p': + plumbing = 0; + break; + case 's': + srvpost = 1; + break; + case 'l': + logging = 1; + break; + case 'n': + nodflt = 1; + break; + default: + usage(); + }ARGEND + + if(pipe(p) < 0) + error("pipe failed"); + mfd[0] = p[0]; + mfd[1] = p[0]; + + notify(notifyf); + strcpy(user, getuser()); + if(mntpt == nil){ + snprint(maildir, sizeof(maildir), "/mail/fs"); + mntpt = maildir; + } + if(mboxfile == nil && !nodflt){ + snprint(mbox, sizeof(mbox), "/mail/box/%s/mbox", user); + mboxfile = mbox; + std = 1; + } + + if(debug) + fmtinstall('F', fcallfmt); + + if(mboxfile != nil){ + err = newmbox(mboxfile, "mbox", std); + if(err != nil) + sysfatal("opening mailbox: %s", err); + } + + switch(rfork(RFFDG|RFPROC|RFNAMEG|RFNOTEG)){ /* jpc removed RFEND */ + case -1: + error("fork"); + case 0: + henter(PATH(0, Qtop), dirtab[Qctl], + (Qid){PATH(0, Qctl), 0, QTFILE}, nil, nil); + close(p[1]); + io(); + postnote(PNGROUP, getpid(), "die yankee pig dog"); + break; + default: + close(p[0]); /* don't deadlock if child fails */ + if(srvpost){ + sprint(srvfile, "/srv/upasfs.%s", user); + /* post(srvfile, "upasfs", p[1]); jpc */ + post9pservice(p[1], "upasfs"); /* jpc */ + } else { + error("tried to mount, fixme"); /* jpc */ + /* if(mount(p[1], -1, mntpt, MREPL, "") < 0) + error("mount failed"); jpc */ + } + } + threadexits(0); +} + +void run_io(void *v) { + int *p; + + p = v; + henter(PATH(0, Qtop), dirtab[Qctl], + (Qid){PATH(0, Qctl), 0, QTFILE}, nil, nil); + close(p[1]); + io(); + postnote(PNGROUP, getpid(), "die yankee pig dog"); +} + +static int +fileinfo(Message *m, int t, char **pp) +{ + char *p; + int len; + + p = ""; + len = 0; + switch(t){ + case Qbody: + p = m->body; + len = m->bend - m->body; + break; + case Qbcc: + if(m->bcc822){ + p = s_to_c(m->bcc822); + len = strlen(p); + } + break; + case Qcc: + if(m->cc822){ + p = s_to_c(m->cc822); + len = strlen(p); + } + break; + case Qdisposition: + switch(m->disposition){ + case Dinline: + p = "inline"; + break; + case Dfile: + p = "file"; + break; + } + len = strlen(p); + break; + case Qdate: + if(m->date822){ + p = s_to_c(m->date822); + len = strlen(p); + } else if(m->unixdate != nil){ + p = s_to_c(m->unixdate); + len = strlen(p); + } + break; + case Qfilename: + if(m->filename){ + p = s_to_c(m->filename); + len = strlen(p); + } + break; + case Qinreplyto: + if(m->inreplyto822){ + p = s_to_c(m->inreplyto822); + len = strlen(p); + } + break; + case Qmessageid: + if(m->messageid822){ + p = s_to_c(m->messageid822); + len = strlen(p); + } + break; + case Qfrom: + if(m->from822){ + p = s_to_c(m->from822); + len = strlen(p); + } else if(m->unixfrom != nil){ + p = s_to_c(m->unixfrom); + len = strlen(p); + } + break; + case Qheader: + p = m->header; + len = headerlen(m); + break; + case Qlines: + p = m->lines; + if(*p == 0) + countlines(m); + len = strlen(m->lines); + break; + case Qraw: + p = m->start; + if(strncmp(m->start, "From ", 5) == 0){ + p = strchr(p, '\n'); + if(p == nil) + p = m->start; + else + p++; + } + len = m->end - p; + break; + case Qrawunix: + p = m->start; + len = m->end - p; + break; + case Qrawbody: + p = m->rbody; + len = m->rbend - p; + break; + case Qrawheader: + p = m->header; + len = m->hend - p; + break; + case Qmimeheader: + p = m->mheader; + len = m->mhend - p; + break; + case Qreplyto: + p = nil; + if(m->replyto822 != nil){ + p = s_to_c(m->replyto822); + len = strlen(p); + } else if(m->from822 != nil){ + p = s_to_c(m->from822); + len = strlen(p); + } else if(m->sender822 != nil){ + p = s_to_c(m->sender822); + len = strlen(p); + } else if(m->unixfrom != nil){ + p = s_to_c(m->unixfrom); + len = strlen(p); + } + break; + case Qsender: + if(m->sender822){ + p = s_to_c(m->sender822); + len = strlen(p); + } + break; + case Qsubject: + p = nil; + if(m->subject822){ + p = s_to_c(m->subject822); + len = strlen(p); + } + break; + case Qto: + if(m->to822){ + p = s_to_c(m->to822); + len = strlen(p); + } + break; + case Qtype: + if(m->type){ + p = s_to_c(m->type); + len = strlen(p); + } + break; + case Qunixdate: + if(m->unixdate){ + p = s_to_c(m->unixdate); + len = strlen(p); + } + break; + case Qunixheader: + if(m->unixheader){ + p = s_to_c(m->unixheader); + len = s_len(m->unixheader); + } + break; + case Qdigest: + if(m->sdigest){ + p = s_to_c(m->sdigest); + len = strlen(p); + } + break; + } + *pp = p; + return len; +} + +int infofields[] = { + Qfrom, + Qto, + Qcc, + Qreplyto, + Qunixdate, + Qsubject, + Qtype, + Qdisposition, + Qfilename, + Qdigest, + Qbcc, + Qinreplyto, + Qdate, + Qsender, + Qmessageid, + Qlines, + -1, +}; + +static int +readinfo(Message *m, char *buf, long off, int count) +{ + char *p; + int len, i, n; + String *s; + + s = s_new(); + len = 0; + for(i = 0; len < count && infofields[i] >= 0; i++){ + n = fileinfo(m, infofields[i], &p); + s = stringconvert(s, p, n); + s_append(s, "\n"); + p = s_to_c(s); + n = strlen(p); + if(off > 0){ + if(off >= n){ + off -= n; + continue; + } + p += off; + n -= off; + off = 0; + } + if(n > count - len) + n = count - len; + if(buf) + memmove(buf+len, p, n); + len += n; + } + s_free(s); + return len; +} + +static void +mkstat(Dir *d, Mailbox *mb, Message *m, int t) +{ + char *p; + + d->uid = user; + d->gid = user; + d->muid = user; + d->mode = 0444; + d->qid.vers = 0; + d->qid.type = QTFILE; + d->type = 0; + d->dev = 0; + if(mb != nil && mb->d != nil){ + d->atime = mb->d->atime; + d->mtime = mb->d->mtime; + } else { + d->atime = time(0); + d->mtime = d->atime; + } + + switch(t){ + case Qtop: + d->name = "."; + d->mode = DMDIR|0555; + d->atime = d->mtime = time(0); + d->length = 0; + d->qid.path = PATH(0, Qtop); + d->qid.type = QTDIR; + break; + case Qmbox: + d->name = mb->name; + d->mode = DMDIR|0555; + d->length = 0; + d->qid.path = PATH(mb->id, Qmbox); + d->qid.type = QTDIR; + d->qid.vers = mb->vers; + break; + case Qdir: + d->name = m->name; + d->mode = DMDIR|0555; + d->length = 0; + d->qid.path = PATH(m->id, Qdir); + d->qid.type = QTDIR; + break; + case Qctl: + d->name = dirtab[t]; + d->mode = 0666; + d->atime = d->mtime = time(0); + d->length = 0; + d->qid.path = PATH(0, Qctl); + break; + case Qmboxctl: + d->name = dirtab[t]; + d->mode = 0222; + d->atime = d->mtime = time(0); + d->length = 0; + d->qid.path = PATH(mb->id, Qmboxctl); + break; + case Qinfo: + d->name = dirtab[t]; + d->length = readinfo(m, nil, 0, 1<<30); + d->qid.path = PATH(m->id, t); + break; + default: + d->name = dirtab[t]; + d->length = fileinfo(m, t, &p); + d->qid.path = PATH(m->id, t); + break; + } +} + +char* +rversion(Fid* dummy) +{ + Fid *f; + + if(thdr.msize < 256) + return "max messagesize too small"; + if(thdr.msize < messagesize) + messagesize = thdr.msize; + rhdr.msize = messagesize; + if(strncmp(thdr.version, "9P2000", 6) != 0) + return "unknown 9P version"; + else + rhdr.version = "9P2000"; + for(f = fids; f; f = f->next) + if(f->busy) + rclunk(f); + return nil; +} + +char* +rauth(Fid* dummy) +{ + return Enoauth; +} + +char* +rflush(Fid *f) +{ + USED(f); + return 0; +} + +char* +rattach(Fid *f) +{ + f->busy = 1; + f->m = nil; + f->mb = nil; + f->qid.path = PATH(0, Qtop); + f->qid.type = QTDIR; + f->qid.vers = 0; + rhdr.qid = f->qid; + if(strcmp(thdr.uname, user) != 0) + return Eperm; + return 0; +} + +static Fid* +doclone(Fid *f, int nfid) +{ + Fid *nf; + + nf = newfid(nfid); + if(nf->busy) + return nil; + nf->busy = 1; + nf->open = 0; + nf->m = f->m; + nf->mtop = f->mtop; + nf->mb = f->mb; + if(f->mb != nil) + mboxincref(f->mb); + if(f->mtop != nil){ + qlock(&f->mb->ql); + msgincref(f->mtop); + qunlock(&f->mb->ql); + } + nf->qid = f->qid; + return nf; +} + +char* +dowalk(Fid *f, char *name) +{ + int t; + Mailbox *omb, *mb; + char *rv, *p; + Hash *h; + + t = FILE(f->qid.path); + + rv = Enotexist; + + omb = f->mb; + if(omb) + qlock(&omb->ql); + else + qlock(&mbllock); + + // this must catch everything except . and .. +retry: + h = hlook(f->qid.path, name); + if(h != nil){ + f->mb = h->mb; + f->m = h->m; + switch(t){ + case Qtop: + if(f->mb != nil) + mboxincref(f->mb); + break; + case Qmbox: + if(f->m){ + msgincref(f->m); + f->mtop = f->m; + } + break; + } + f->qid = h->qid; + rv = nil; + } else if((p = strchr(name, '.')) != nil && *name != '.'){ + *p = 0; + goto retry; + } + + if(omb) + qunlock(&omb->ql); + else + qunlock(&mbllock); + if(rv == nil) + return rv; + + if(strcmp(name, ".") == 0) + return nil; + + if(f->qid.type != QTDIR) + return Enotdir; + + if(strcmp(name, "..") == 0){ + switch(t){ + case Qtop: + f->qid.path = PATH(0, Qtop); + f->qid.type = QTDIR; + f->qid.vers = 0; + break; + case Qmbox: + f->qid.path = PATH(0, Qtop); + f->qid.type = QTDIR; + f->qid.vers = 0; + qlock(&mbllock); + mb = f->mb; + f->mb = nil; + mboxdecref(mb); + qunlock(&mbllock); + break; + case Qdir: + qlock(&f->mb->ql); + if(f->m->whole == f->mb->root){ + f->qid.path = PATH(f->mb->id, Qmbox); + f->qid.type = QTDIR; + f->qid.vers = f->mb->d->qid.vers; + msgdecref(f->mb, f->mtop); + f->m = f->mtop = nil; + } else { + f->m = f->m->whole; + f->qid.path = PATH(f->m->id, Qdir); + f->qid.type = QTDIR; + } + qunlock(&f->mb->ql); + break; + } + rv = nil; + } + return rv; +} + +char* +rwalk(Fid *f) +{ + Fid *nf; + char *rv; + int i; + + if(f->open) + return Eisopen; + + rhdr.nwqid = 0; + nf = nil; + + /* clone if requested */ + if(thdr.newfid != thdr.fid){ + nf = doclone(f, thdr.newfid); + if(nf == nil) + return "new fid in use"; + f = nf; + } + + /* if it's just a clone, return */ + if(thdr.nwname == 0 && nf != nil) + return nil; + + /* walk each element */ + rv = nil; + for(i = 0; i < thdr.nwname; i++){ + rv = dowalk(f, thdr.wname[i]); + if(rv != nil){ + if(nf != nil) + rclunk(nf); + break; + } + rhdr.wqid[i] = f->qid; + } + rhdr.nwqid = i; + + /* we only error out if no walk */ + if(i > 0) + rv = nil; + + return rv; +} + +char * +ropen(Fid *f) +{ + int file; + + if(f->open) + return Eisopen; + + file = FILE(f->qid.path); + if(thdr.mode != OREAD) + if(file != Qctl && file != Qmboxctl) + return Eperm; + + // make sure we've decoded + if(file == Qbody){ + if(f->m->decoded == 0) + decode(f->m); + if(f->m->converted == 0) + convert(f->m); + } + + rhdr.iounit = 0; + rhdr.qid = f->qid; + f->open = 1; + return 0; +} + +char * +rcreate(Fid* dummy) +{ + return Eperm; +} + +int +readtopdir(Fid* dummy, uchar *buf, long off, int cnt, int blen) +{ + Dir d; + int m, n; + long pos; + Mailbox *mb; + + n = 0; + pos = 0; + mkstat(&d, nil, nil, Qctl); + m = convD2M(&d, &buf[n], blen); + if(off <= pos){ + if(m <= BIT16SZ || m > cnt) + return 0; + n += m; + cnt -= m; + } + pos += m; + + for(mb = mbl; mb != nil; mb = mb->next){ + mkstat(&d, mb, nil, Qmbox); + m = convD2M(&d, &buf[n], blen-n); + if(off <= pos){ + if(m <= BIT16SZ || m > cnt) + break; + n += m; + cnt -= m; + } + pos += m; + } + return n; +} + +int +readmboxdir(Fid *f, uchar *buf, long off, int cnt, int blen) +{ + Dir d; + int n, m; + long pos; + Message *msg; + + n = 0; + if(f->mb->ctl){ + mkstat(&d, f->mb, nil, Qmboxctl); + m = convD2M(&d, &buf[n], blen); + if(off == 0){ + if(m <= BIT16SZ || m > cnt){ + f->fptr = nil; + return 0; + } + n += m; + cnt -= m; + } else + off -= m; + } + + // to avoid n**2 reads of the directory, use a saved finger pointer + if(f->mb->vers == f->fvers && off >= f->foff && f->fptr != nil){ + msg = f->fptr; + pos = f->foff; + } else { + msg = f->mb->root->part; + pos = 0; + } + + for(; cnt > 0 && msg != nil; msg = msg->next){ + // act like deleted files aren't there + if(msg->deleted) + continue; + + mkstat(&d, f->mb, msg, Qdir); + m = convD2M(&d, &buf[n], blen-n); + if(off <= pos){ + if(m <= BIT16SZ || m > cnt) + break; + n += m; + cnt -= m; + } + pos += m; + } + + // save a finger pointer for next read of the mbox directory + f->foff = pos; + f->fptr = msg; + f->fvers = f->mb->vers; + + return n; +} + +int +readmsgdir(Fid *f, uchar *buf, long off, int cnt, int blen) +{ + Dir d; + int i, n, m; + long pos; + Message *msg; + + n = 0; + pos = 0; + for(i = 0; i < Qmax; i++){ + mkstat(&d, f->mb, f->m, i); + m = convD2M(&d, &buf[n], blen-n); + if(off <= pos){ + if(m <= BIT16SZ || m > cnt) + return n; + n += m; + cnt -= m; + } + pos += m; + } + for(msg = f->m->part; msg != nil; msg = msg->next){ + mkstat(&d, f->mb, msg, Qdir); + m = convD2M(&d, &buf[n], blen-n); + if(off <= pos){ + if(m <= BIT16SZ || m > cnt) + break; + n += m; + cnt -= m; + } + pos += m; + } + + return n; +} + +char* +rread(Fid *f) +{ + long off; + int t, i, n, cnt; + char *p; + + rhdr.count = 0; + off = thdr.offset; + cnt = thdr.count; + + if(cnt > messagesize - IOHDRSZ) + cnt = messagesize - IOHDRSZ; + + rhdr.data = (char*)mbuf; + + t = FILE(f->qid.path); + if(f->qid.type & QTDIR){ + if(t == Qtop) { + qlock(&mbllock); + n = readtopdir(f, mbuf, off, cnt, messagesize - IOHDRSZ); + qunlock(&mbllock); + } else if(t == Qmbox) { + qlock(&f->mb->ql); + if(off == 0) + syncmbox(f->mb, 1); + n = readmboxdir(f, mbuf, off, cnt, messagesize - IOHDRSZ); + qunlock(&f->mb->ql); + } else if(t == Qmboxctl) { + n = 0; + } else { + n = readmsgdir(f, mbuf, off, cnt, messagesize - IOHDRSZ); + } + + rhdr.count = n; + return nil; + } + + if(FILE(f->qid.path) == Qheader){ + rhdr.count = readheader(f->m, (char*)mbuf, off, cnt); + return nil; + } + + if(FILE(f->qid.path) == Qinfo){ + rhdr.count = readinfo(f->m, (char*)mbuf, off, cnt); + return nil; + } + + i = fileinfo(f->m, FILE(f->qid.path), &p); + if(off < i){ + if((off + cnt) > i) + cnt = i - off; + memmove(mbuf, p + off, cnt); + rhdr.count = cnt; + } + return nil; +} + +char* +rwrite(Fid *f) +{ + char *err; + char *token[1024]; + int t, n; + String *file; + + t = FILE(f->qid.path); + rhdr.count = thdr.count; + switch(t){ + case Qctl: + if(thdr.count == 0) + return Ebadctl; + if(thdr.data[thdr.count-1] == '\n') + thdr.data[thdr.count-1] = 0; + else + thdr.data[thdr.count] = 0; + n = tokenize(thdr.data, token, nelem(token)); + if(n == 0) + return Ebadctl; + if(strcmp(token[0], "open") == 0){ + file = s_new(); + switch(n){ + case 1: + err = Ebadctl; + break; + case 2: + mboxpath(token[1], getlog(), file, 0); + err = newmbox(s_to_c(file), nil, 0); + break; + default: + mboxpath(token[1], getlog(), file, 0); + if(strchr(token[2], '/') != nil) + err = "/ not allowed in mailbox name"; + else + err = newmbox(s_to_c(file), token[2], 0); + break; + } + s_free(file); + return err; + } + if(strcmp(token[0], "close") == 0){ + if(n < 2) + return nil; + freembox(token[1]); + return nil; + } + if(strcmp(token[0], "delete") == 0){ + if(n < 3) + return nil; + delmessages(n-1, &token[1]); + return nil; + } + return Ebadctl; + case Qmboxctl: + if(f->mb && f->mb->ctl){ + if(thdr.count == 0) + return Ebadctl; + if(thdr.data[thdr.count-1] == '\n') + thdr.data[thdr.count-1] = 0; + else + thdr.data[thdr.count] = 0; + n = tokenize(thdr.data, token, nelem(token)); + if(n == 0) + return Ebadctl; + return (*f->mb->ctl)(f->mb, n, token); + } + } + return Eperm; +} + +char * +rclunk(Fid *f) +{ + Mailbox *mb; + + f->busy = 0; + f->open = 0; + if(f->mtop != nil){ + qlock(&f->mb->ql); + msgdecref(f->mb, f->mtop); + qunlock(&f->mb->ql); + } + f->m = f->mtop = nil; + mb = f->mb; + if(mb != nil){ + f->mb = nil; + assert(mb->refs > 0); + qlock(&mbllock); + mboxdecref(mb); + qunlock(&mbllock); + } + f->fid = -1; + return 0; +} + +char * +rremove(Fid *f) +{ + if(f->m != nil){ + if(f->m->deleted == 0) + mailplumb(f->mb, f->m, 1); + f->m->deleted = 1; + } + return rclunk(f); +} + +char * +rstat(Fid *f) +{ + Dir d; + + if(FILE(f->qid.path) == Qmbox){ + qlock(&f->mb->ql); + syncmbox(f->mb, 1); + qunlock(&f->mb->ql); + } + mkstat(&d, f->mb, f->m, FILE(f->qid.path)); + rhdr.nstat = convD2M(&d, mbuf, messagesize - IOHDRSZ); + rhdr.stat = mbuf; + return 0; +} + +char * +rwstat(Fid* dummy) +{ + return Eperm; +} + +Fid * +newfid(int fid) +{ + Fid *f, *ff; + + ff = 0; + for(f = fids; f; f = f->next) + if(f->fid == fid) + return f; + else if(!ff && !f->busy) + ff = f; + if(ff){ + ff->fid = fid; + ff->fptr = nil; + return ff; + } + f = emalloc(sizeof *f); + f->fid = fid; + f->fptr = nil; + f->next = fids; + fids = f; + return f; +} + +int +fidmboxrefs(Mailbox *mb) +{ + Fid *f; + int refs = 0; + + for(f = fids; f; f = f->next){ + if(f->mb == mb) + refs++; + } + return refs; +} + +void +io(void) +{ + char *err; + int n, nw; + + /* start a process to watch the mailboxes*/ + if(plumbing){ + proccreate(reader, nil, 16000); +#if 0 /* jpc */ + switch(rfork(RFPROC|RFMEM)){ + case -1: + /* oh well */ + break; + case 0: + reader(); + threadexits(nil); + default: + break; + } +#endif /* jpc */ + } + + for(;;){ + /* + * reading from a pipe or a network device + * will give an error after a few eof reads + * however, we cannot tell the difference + * between a zero-length read and an interrupt + * on the processes writing to us, + * so we wait for the error + */ + checkmboxrefs(); + n = read9pmsg(mfd[0], mdata, messagesize); + if(n == 0) + continue; + if(n < 0) + return; + if(convM2S(mdata, n, &thdr) == 0) + continue; + + if(debug) + fprint(2, "%s:<-%F\n", argv0, &thdr); + + rhdr.data = (char*)mdata + messagesize; + if(!fcalls[thdr.type]) + err = "bad fcall type"; + else + err = (*fcalls[thdr.type])(newfid(thdr.fid)); + if(err){ + rhdr.type = Rerror; + rhdr.ename = err; + }else{ + rhdr.type = thdr.type + 1; + rhdr.fid = thdr.fid; + } + rhdr.tag = thdr.tag; + if(debug) + fprint(2, "%s:->%F\n", argv0, &rhdr);/**/ + n = convS2M(&rhdr, mdata, messagesize); + if((nw = write(mfd[1], mdata, n)) != n) { + fprint(2,"wrote %d bytes\n",nw); + error("mount write"); + } + } +} + +void +reader(void *dummy) +{ + ulong t; + Dir *d; + Mailbox *mb; + + sleep(15*1000); + for(;;){ + t = time(0); + qlock(&mbllock); + for(mb = mbl; mb != nil; mb = mb->next){ + assert(mb->refs > 0); + if(mb->waketime != 0 && t > mb->waketime){ + qlock(&mb->ql); + mb->waketime = 0; + break; + } + + d = dirstat(mb->path); + if(d == nil) + continue; + + qlock(&mb->ql); + if(mb->d) + if(d->qid.path != mb->d->qid.path + || d->qid.vers != mb->d->qid.vers){ + free(d); + break; + } + qunlock(&mb->ql); + free(d); + } + qunlock(&mbllock); + if(mb != nil){ + syncmbox(mb, 1); + qunlock(&mb->ql); + } else + sleep(15*1000); + } +} + +int +newid(void) +{ + int rv; + static int id; + static Lock idlock; + + lock(&idlock); + rv = ++id; + unlock(&idlock); + + return rv; +} + +void +error(char *s) +{ + postnote(PNGROUP, getpid(), "die yankee pig dog"); + fprint(2, "%s: %s: %r\n", argv0, s); + threadexits(s); +} + + +typedef struct Ignorance Ignorance; +struct Ignorance +{ + Ignorance *next; + char *str; /* string */ + int partial; /* true if not exact match */ +}; +Ignorance *ignorance; + +/* + * read the file of headers to ignore + */ +void +readignore(void) +{ + char *p; + Ignorance *i; + Biobuf *b; + + if(ignorance != nil) + return; + + b = Bopen("/mail/lib/ignore", OREAD); + if(b == 0) + return; + while(p = Brdline(b, '\n')){ + p[Blinelen(b)-1] = 0; + while(*p && (*p == ' ' || *p == '\t')) + p++; + if(*p == '#') + continue; + i = malloc(sizeof(Ignorance)); + if(i == 0) + break; + i->partial = strlen(p); + i->str = strdup(p); + if(i->str == 0){ + free(i); + break; + } + i->next = ignorance; + ignorance = i; + } + Bterm(b); +} + +int +ignore(char *p) +{ + Ignorance *i; + + readignore(); + for(i = ignorance; i != nil; i = i->next) + if(cistrncmp(i->str, p, i->partial) == 0) + return 1; + return 0; +} + +int +hdrlen(char *p, char *e) +{ + char *ep; + + ep = p; + do { + ep = strchr(ep, '\n'); + if(ep == nil){ + ep = e; + break; + } + ep++; + if(ep >= e){ + ep = e; + break; + } + } while(*ep == ' ' || *ep == '\t'); + return ep - p; +} + +// rfc2047 non-ascii +typedef struct Charset Charset; +struct Charset { + char *name; + int len; + int convert; + char *tcsname; +} charsets[] = +{ + { "us-ascii", 8, 1, nil, }, + { "utf-8", 5, 0, nil, }, + { "iso-8859-1", 10, 1, nil, }, + { "iso-8859-2", 10, 2, "8859-2", }, + { "big5", 4, 2, "big5", }, + { "iso-2022-jp", 11, 2, "jis", }, + { "windows-1251", 12, 2, "cp1251"}, + { "koi8-r", 6, 2, "koi8"}, +}; + +int +rfc2047convert(String *s, char *token, int len) +{ + char decoded[1024]; + char utfbuf[2*1024]; + int i; + char *e, *x; + + if(len == 0) + return -1; + + e = token+len-2; + token += 2; + + // bail if we don't understand the character set + for(i = 0; i < nelem(charsets); i++) + if(cistrncmp(charsets[i].name, token, charsets[i].len) == 0) + if(token[charsets[i].len] == '?'){ + token += charsets[i].len + 1; + break; + } + if(i >= nelem(charsets)) + return -1; + + // bail if it doesn't fit + if(e-token > sizeof(decoded)-1) + return -1; + + // bail if we don't understand the encoding + if(cistrncmp(token, "b?", 2) == 0){ + token += 2; + len = dec64((uchar*)decoded, sizeof(decoded), token, e-token); + decoded[len] = 0; + } else if(cistrncmp(token, "q?", 2) == 0){ + token += 2; + len = decquoted(decoded, token, e); + if(len > 0 && decoded[len-1] == '\n') + len--; + decoded[len] = 0; + } else + return -1; + + switch(charsets[i].convert){ + case 0: + s_append(s, decoded); + break; + case 1: + latin1toutf(utfbuf, decoded, decoded+len); + s_append(s, utfbuf); + break; + case 2: + if(xtoutf(charsets[i].tcsname, &x, decoded, decoded+len) <= 0){ + s_append(s, decoded); + } else { + s_append(s, x); + free(x); + } + break; + } + + return 0; +} + +char* +rfc2047start(char *start, char *end) +{ + int quests; + + if(*--end != '=') + return nil; + if(*--end != '?') + return nil; + + quests = 0; + for(end--; end >= start; end--){ + switch(*end){ + case '=': + if(quests == 3 && *(end+1) == '?') + return end; + break; + case '?': + ++quests; + break; + case ' ': + case '\t': + case '\n': + case '\r': + /* can't have white space in a token */ + return nil; + } + } + return nil; +} + +// convert a header line +String* +stringconvert(String *s, char *uneaten, int len) +{ + char *token; + char *p; + int i; + + s = s_reset(s); + p = uneaten; + for(i = 0; i < len; i++){ + if(*p++ == '='){ + token = rfc2047start(uneaten, p); + if(token != nil){ + s_nappend(s, uneaten, token-uneaten); + if(rfc2047convert(s, token, p - token) < 0) + s_nappend(s, token, p - token); + uneaten = p; + } + } + } + if(p > uneaten) + s_nappend(s, uneaten, p-uneaten); + return s; +} + +int +readheader(Message *m, char *buf, int off, int cnt) +{ + char *p, *e; + int n, ns; + char *to = buf; + String *s; + + p = m->header; + e = m->hend; + s = nil; + + // copy in good headers + while(cnt > 0 && p < e){ + n = hdrlen(p, e); + if(ignore(p)){ + p += n; + continue; + } + + // rfc2047 processing + s = stringconvert(s, p, n); + ns = s_len(s); + if(off > 0){ + if(ns <= off){ + off -= ns; + p += n; + continue; + } + ns -= off; + } + if(ns > cnt) + ns = cnt; + memmove(to, s_to_c(s)+off, ns); + to += ns; + p += n; + cnt -= ns; + off = 0; + } + + s_free(s); + return to - buf; +} + +int +headerlen(Message *m) +{ + char buf[1024]; + int i, n; + + if(m->hlen >= 0) + return m->hlen; + for(n = 0; ; n += i){ + i = readheader(m, buf, n, sizeof(buf)); + if(i <= 0) + break; + } + m->hlen = n; + return n; +} + +QLock hashlock; + +uint +hash(ulong ppath, char *name) +{ + uchar *p; + uint h; + + h = 0; + for(p = (uchar*)name; *p; p++) + h = h*7 + *p; + h += ppath; + + return h % Hsize; +} + +Hash* +hlook(ulong ppath, char *name) +{ + int h; + Hash *hp; + + qlock(&hashlock); + h = hash(ppath, name); + for(hp = htab[h]; hp != nil; hp = hp->next) + if(ppath == hp->ppath && strcmp(name, hp->name) == 0){ + qunlock(&hashlock); + return hp; + } + qunlock(&hashlock); + return nil; +} + +void +henter(ulong ppath, char *name, Qid qid, Message *m, Mailbox *mb) +{ + int h; + Hash *hp, **l; + + qlock(&hashlock); + h = hash(ppath, name); + for(l = &htab[h]; *l != nil; l = &(*l)->next){ + hp = *l; + if(ppath == hp->ppath && strcmp(name, hp->name) == 0){ + hp->m = m; + hp->mb = mb; + hp->qid = qid; + qunlock(&hashlock); + return; + } + } + + *l = hp = emalloc(sizeof(*hp)); + hp->m = m; + hp->mb = mb; + hp->qid = qid; + hp->name = name; + hp->ppath = ppath; + qunlock(&hashlock); +} + +void +hfree(ulong ppath, char *name) +{ + int h; + Hash *hp, **l; + + qlock(&hashlock); + h = hash(ppath, name); + for(l = &htab[h]; *l != nil; l = &(*l)->next){ + hp = *l; + if(ppath == hp->ppath && strcmp(name, hp->name) == 0){ + hp->mb = nil; + *l = hp->next; + free(hp); + break; + } + } + qunlock(&hashlock); +} + +int +hashmboxrefs(Mailbox *mb) +{ + int h; + Hash *hp; + int refs = 0; + + qlock(&hashlock); + for(h = 0; h < Hsize; h++){ + for(hp = htab[h]; hp != nil; hp = hp->next) + if(hp->mb == mb) + refs++; + } + qunlock(&hashlock); + return refs; +} + +void +checkmboxrefs(void) +{ + int f, refs; + Mailbox *mb; + + qlock(&mbllock); + for(mb=mbl; mb; mb=mb->next){ + qlock(&mb->ql); + refs = (f=fidmboxrefs(mb))+1; + if(refs != mb->refs){ + fprint(2, "mbox %s %s ref mismatch actual %d (%d+1) expected %d\n", mb->name, mb->path, refs, f, mb->refs); + abort(); + } + qunlock(&mb->ql); + } + qunlock(&mbllock); +} + +void +post(char *name, char *envname, int srvfd) +{ + int fd; + char buf[32]; + + fd = create(name, OWRITE, 0600); + if(fd < 0) + error("post failed"); + sprint(buf, "%d",srvfd); + if(write(fd, buf, strlen(buf)) != strlen(buf)) + error("srv write"); + close(fd); + putenv(envname, name); +} blob - /dev/null blob + 152d15cf427a104df203fe444718537fdd1b6f18 (mode 644) --- /dev/null +++ src/cmd/upas/fs/imap4.c @@ -0,0 +1,876 @@ +#include "common.h" +#include +#include +#include +#include +#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; idata == 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; inuid; 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, '"'); +} blob - /dev/null blob + c539df8de3ff86d967699ad7de54d5d56b427021 (mode 644) --- /dev/null +++ src/cmd/upas/fs/mbox.c @@ -0,0 +1,1601 @@ +#include "common.h" +#include +#include +#include +#include +#include "dat.h" + +extern char* dirtab[]; /* jpc */ + +typedef struct Header Header; + +struct Header { + char *type; + void (*f)(Message*, Header*, char*); + int len; +}; + +/* headers */ +static void ctype(Message*, Header*, char*); +static void cencoding(Message*, Header*, char*); +static void cdisposition(Message*, Header*, char*); +static void date822(Message*, Header*, char*); +static void from822(Message*, Header*, char*); +static void to822(Message*, Header*, char*); +static void sender822(Message*, Header*, char*); +static void replyto822(Message*, Header*, char*); +static void subject822(Message*, Header*, char*); +static void inreplyto822(Message*, Header*, char*); +static void cc822(Message*, Header*, char*); +static void bcc822(Message*, Header*, char*); +static void messageid822(Message*, Header*, char*); +static void mimeversion(Message*, Header*, char*); +static void nullsqueeze(Message*); +enum +{ + Mhead= 11, /* offset of first mime header */ +}; + +Header head[] = +{ + { "date:", date822, }, + { "from:", from822, }, + { "to:", to822, }, + { "sender:", sender822, }, + { "reply-to:", replyto822, }, + { "subject:", subject822, }, + { "cc:", cc822, }, + { "bcc:", bcc822, }, + { "in-reply-to:", inreplyto822, }, + { "mime-version:", mimeversion, }, + { "message-id:", messageid822, }, + +[Mhead] { "content-type:", ctype, }, + { "content-transfer-encoding:", cencoding, }, + { "content-disposition:", cdisposition, }, + { 0, }, +}; + +/* static void fatal(char *fmt, ...); jpc */ +static void initquoted(void); +/* static void startheader(Message*); +static void startbody(Message*); jpc */ +static char* skipwhite(char*); +static char* skiptosemi(char*); +static char* getstring(char*, String*, int); +static void setfilename(Message*, char*); +/* static char* lowercase(char*); jpc */ +static int is8bit(Message*); +static int headerline(char**, String*); +static void initheaders(void); +static void parseattachments(Message*, Mailbox*); + +int debug; + +char *Enotme = "path not served by this file server"; + +enum +{ + Chunksize = 1024, +}; + +Mailboxinit *boxinit[] = { + imap4mbox, + pop3mbox, + plan9mbox, +}; + +char* +syncmbox(Mailbox *mb, int doplumb) +{ + return (*mb->sync)(mb, doplumb); +} + +/* create a new mailbox */ +char* +newmbox(char *path, char *name, int std) +{ + Mailbox *mb, **l; + char *p, *rv; + int i; + + initheaders(); + + mb = emalloc(sizeof(*mb)); + strncpy(mb->path, path, sizeof(mb->path)-1); + if(name == nil){ + p = strrchr(path, '/'); + if(p == nil) + p = path; + else + p++; + if(*p == 0){ + free(mb); + return "bad mbox name"; + } + strncpy(mb->name, p, sizeof(mb->name)-1); + } else { + strncpy(mb->name, name, sizeof(mb->name)-1); + } + + rv = nil; + // check for a mailbox type + for(i=0; inext){ + if(strcmp((*l)->name, mb->name) == 0){ + if(strcmp(path, (*l)->path) == 0) + rv = nil; + else + rv = "mbox name in use"; + if(mb->close) + (*mb->close)(mb); + free(mb); + qunlock(&mbllock); + return rv; + } + } + + // always try locking + mb->dolock = 1; + + mb->refs = 1; + mb->next = nil; + mb->id = newid(); + mb->root = newmessage(nil); + mb->std = std; + *l = mb; + qunlock(&mbllock); + + qlock(&mb->ql); + if(mb->ctl){ + henter(PATH(mb->id, Qmbox), "ctl", + (Qid){PATH(mb->id, Qmboxctl), 0, QTFILE}, nil, mb); + } + rv = syncmbox(mb, 0); + qunlock(&mb->ql); + + return rv; +} + +// close the named mailbox +void +freembox(char *name) +{ + Mailbox **l, *mb; + + qlock(&mbllock); + for(l=&mbl; *l != nil; l=&(*l)->next){ + if(strcmp(name, (*l)->name) == 0){ + mb = *l; + *l = mb->next; + mboxdecref(mb); + break; + } + } + hfree(PATH(0, Qtop), name); + qunlock(&mbllock); +} + +static void +initheaders(void) +{ + Header *h; + static int already; + + if(already) + return; + already = 1; + + for(h = head; h->type != nil; h++) + h->len = strlen(h->type); +} + +/* + * parse a Unix style header + */ +void +parseunix(Message *m) +{ + char *p; + String *h; + + h = s_new(); + for(p = m->start + 5; *p && *p != '\r' && *p != '\n'; p++) + s_putc(h, *p); + s_terminate(h); + s_restart(h); + + m->unixfrom = s_parse(h, s_reset(m->unixfrom)); + m->unixdate = s_append(s_reset(m->unixdate), h->ptr); + + s_free(h); +} + +/* + * parse a message + */ +void +parseheaders(Message *m, int justmime, Mailbox *mb, int addfrom) +{ + String *hl; + Header *h; + char *p, *q; + int i; + + if(m->whole == m->whole->whole){ + henter(PATH(mb->id, Qmbox), m->name, + (Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb); + } else { + henter(PATH(m->whole->id, Qdir), m->name, + (Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb); + } + for(i = 0; i < Qmax; i++) + henter(PATH(m->id, Qdir), dirtab[i], + (Qid){PATH(m->id, i), 0, QTFILE}, m, mb); + + // parse mime headers + p = m->header; + hl = s_new(); + while(headerline(&p, hl)){ + if(justmime) + h = &head[Mhead]; + else + h = head; + for(; h->type; h++){ + if(cistrncmp(s_to_c(hl), h->type, h->len) == 0){ + (*h->f)(m, h, s_to_c(hl)); + break; + } + } + s_reset(hl); + } + s_free(hl); + + // the blank line isn't really part of the body or header + if(justmime){ + m->mhend = p; + m->hend = m->header; + } else { + m->hend = p; + } + if(*p == '\n') + p++; + m->rbody = m->body = p; + + // if type is text, get any nulls out of the body. This is + // for the two seans and imap clients that get confused. + if(strncmp(s_to_c(m->type), "text/", 5) == 0) + nullsqueeze(m); + + // + // cobble together Unix-style from line + // for local mailbox messages, we end up recreating the + // original header. + // for pop3 messages, the best we can do is + // use the From: information and the RFC822 date. + // + if(m->unixdate == nil || strcmp(s_to_c(m->unixdate), "???") == 0 + || strcmp(s_to_c(m->unixdate), "Thu Jan 1 00:00:00 GMT 1970") == 0){ + if(m->unixdate){ + s_free(m->unixdate); + m->unixdate = nil; + } + // look for the date in the first Received: line. + // it's likely to be the right time zone (it's + // the local system) and in a convenient format. + if(cistrncmp(m->header, "received:", 9)==0){ + if((q = strchr(m->header, ';')) != nil){ + p = q; + while((p = strchr(p, '\n')) != nil){ + if(p[1] != ' ' && p[1] != '\t' && p[1] != '\n') + break; + p++; + } + if(p){ + *p = '\0'; + m->unixdate = date822tounix(q+1); + *p = '\n'; + } + } + } + + // fall back on the rfc822 date + if(m->unixdate==nil && m->date822) + m->unixdate = date822tounix(s_to_c(m->date822)); + } + + if(m->unixheader != nil) + s_free(m->unixheader); + + // only fake header for top-level messages for pop3 and imap4 + // clients (those protocols don't include the unix header). + // adding the unix header all the time screws up mime-attached + // rfc822 messages. + if(!addfrom && !m->unixfrom){ + m->unixheader = nil; + return; + } + + m->unixheader = s_copy("From "); + if(m->unixfrom && strcmp(s_to_c(m->unixfrom), "???") != 0) + s_append(m->unixheader, s_to_c(m->unixfrom)); + else if(m->from822) + s_append(m->unixheader, s_to_c(m->from822)); + else + s_append(m->unixheader, "???"); + + s_append(m->unixheader, " "); + if(m->unixdate) + s_append(m->unixheader, s_to_c(m->unixdate)); + else + s_append(m->unixheader, "Thu Jan 1 00:00:00 GMT 1970"); + + s_append(m->unixheader, "\n"); +} + +String* +promote(String **sp) +{ + String *s; + + if(*sp != nil) + s = s_clone(*sp); + else + s = nil; + return s; +} + +void +parsebody(Message *m, Mailbox *mb) +{ + Message *nm; + + // recurse + if(strncmp(s_to_c(m->type), "multipart/", 10) == 0){ + parseattachments(m, mb); + } else if(strcmp(s_to_c(m->type), "message/rfc822") == 0){ + decode(m); + parseattachments(m, mb); + nm = m->part; + + // promote headers + if(m->replyto822 == nil && m->from822 == nil && m->sender822 == nil){ + m->from822 = promote(&nm->from822); + m->to822 = promote(&nm->to822); + m->date822 = promote(&nm->date822); + m->sender822 = promote(&nm->sender822); + m->replyto822 = promote(&nm->replyto822); + m->subject822 = promote(&nm->subject822); + m->unixdate = promote(&nm->unixdate); + } + } +} + +void +parse(Message *m, int justmime, Mailbox *mb, int addfrom) +{ + parseheaders(m, justmime, mb, addfrom); + parsebody(m, mb); +} + +static void +parseattachments(Message *m, Mailbox *mb) +{ + Message *nm, **l; + char *p, *x; + + // if there's a boundary, recurse... + if(m->boundary != nil){ + p = m->body; + nm = nil; + l = &m->part; + for(;;){ + x = strstr(p, s_to_c(m->boundary)); + + /* no boundary, we're done */ + if(x == nil){ + if(nm != nil) + nm->rbend = nm->bend = nm->end = m->bend; + break; + } + + /* boundary must be at the start of a line */ + if(x != m->body && *(x-1) != '\n'){ + p = x+1; + continue; + } + + if(nm != nil) + nm->rbend = nm->bend = nm->end = x; + x += strlen(s_to_c(m->boundary)); + + /* is this the last part? ignore anything after it */ + if(strncmp(x, "--", 2) == 0) + break; + + p = strchr(x, '\n'); + if(p == nil) + break; + nm = newmessage(m); + nm->start = nm->header = nm->body = nm->rbody = ++p; + nm->mheader = nm->header; + *l = nm; + l = &nm->next; + } + for(nm = m->part; nm != nil; nm = nm->next) + parse(nm, 1, mb, 0); + return; + } + + // if we've got an rfc822 message, recurse... + if(strcmp(s_to_c(m->type), "message/rfc822") == 0){ + nm = newmessage(m); + m->part = nm; + nm->start = nm->header = nm->body = nm->rbody = m->body; + nm->end = nm->bend = nm->rbend = m->bend; + parse(nm, 0, mb, 0); + } +} + +/* + * pick up a header line + */ +static int +headerline(char **pp, String *hl) +{ + char *p, *x; + + s_reset(hl); + p = *pp; + x = strpbrk(p, ":\n"); + if(x == nil || *x == '\n') + return 0; + for(;;){ + x = strchr(p, '\n'); + if(x == nil) + x = p + strlen(p); + s_nappend(hl, p, x-p); + p = x; + if(*p != '\n' || *++p != ' ' && *p != '\t') + break; + while(*p == ' ' || *p == '\t') + p++; + s_putc(hl, ' '); + } + *pp = p; + return 1; +} + +static String* +addr822(char *p) +{ + String *s, *list; + int incomment, addrdone, inanticomment, quoted; + int n; + int c; + + list = s_new(); + s = s_new(); + quoted = incomment = addrdone = inanticomment = 0; + n = 0; + for(; *p; p++){ + c = *p; + + // whitespace is ignored + if(!quoted && isspace(c) || c == '\r') + continue; + + // strings are always treated as atoms + if(!quoted && c == '"'){ + if(!addrdone && !incomment) + s_putc(s, c); + for(p++; *p; p++){ + if(!addrdone && !incomment) + s_putc(s, *p); + if(!quoted && *p == '"') + break; + if(*p == '\\') + quoted = 1; + else + quoted = 0; + } + if(*p == 0) + break; + quoted = 0; + continue; + } + + // ignore everything in an expicit comment + if(!quoted && c == '('){ + incomment = 1; + continue; + } + if(incomment){ + if(!quoted && c == ')') + incomment = 0; + quoted = 0; + continue; + } + + // anticomments makes everything outside of them comments + if(!quoted && c == '<' && !inanticomment){ + inanticomment = 1; + s = s_reset(s); + continue; + } + if(!quoted && c == '>' && inanticomment){ + addrdone = 1; + inanticomment = 0; + continue; + } + + // commas separate addresses + if(!quoted && c == ',' && !inanticomment){ + s_terminate(s); + addrdone = 0; + if(n++ != 0) + s_append(list, " "); + s_append(list, s_to_c(s)); + s = s_reset(s); + continue; + } + + // what's left is part of the address + s_putc(s, c); + + // quoted characters are recognized only as characters + if(c == '\\') + quoted = 1; + else + quoted = 0; + + } + + if(*s_to_c(s) != 0){ + s_terminate(s); + if(n++ != 0) + s_append(list, " "); + s_append(list, s_to_c(s)); + } + s_free(s); + + if(n == 0){ + s_free(list); + return nil; + } + return list; +} + +static void +to822(Message *m, Header *h, char *p) +{ + p += strlen(h->type); + s_free(m->to822); + m->to822 = addr822(p); +} + +static void +cc822(Message *m, Header *h, char *p) +{ + p += strlen(h->type); + s_free(m->cc822); + m->cc822 = addr822(p); +} + +static void +bcc822(Message *m, Header *h, char *p) +{ + p += strlen(h->type); + s_free(m->bcc822); + m->bcc822 = addr822(p); +} + +static void +from822(Message *m, Header *h, char *p) +{ + p += strlen(h->type); + s_free(m->from822); + m->from822 = addr822(p); +} + +static void +sender822(Message *m, Header *h, char *p) +{ + p += strlen(h->type); + s_free(m->sender822); + m->sender822 = addr822(p); +} + +static void +replyto822(Message *m, Header *h, char *p) +{ + p += strlen(h->type); + s_free(m->replyto822); + m->replyto822 = addr822(p); +} + +static void +mimeversion(Message *m, Header *h, char *p) +{ + p += strlen(h->type); + s_free(m->mimeversion); + m->mimeversion = addr822(p); +} + +static void +killtrailingwhite(char *p) +{ + char *e; + + e = p + strlen(p) - 1; + while(e > p && isspace(*e)) + *e-- = 0; +} + +static void +date822(Message *m, Header *h, char *p) +{ + p += strlen(h->type); + p = skipwhite(p); + s_free(m->date822); + m->date822 = s_copy(p); + p = s_to_c(m->date822); + killtrailingwhite(p); +} + +static void +subject822(Message *m, Header *h, char *p) +{ + p += strlen(h->type); + p = skipwhite(p); + s_free(m->subject822); + m->subject822 = s_copy(p); + p = s_to_c(m->subject822); + killtrailingwhite(p); +} + +static void +inreplyto822(Message *m, Header *h, char *p) +{ + p += strlen(h->type); + p = skipwhite(p); + s_free(m->inreplyto822); + m->inreplyto822 = s_copy(p); + p = s_to_c(m->inreplyto822); + killtrailingwhite(p); +} + +static void +messageid822(Message *m, Header *h, char *p) +{ + p += strlen(h->type); + p = skipwhite(p); + s_free(m->messageid822); + m->messageid822 = s_copy(p); + p = s_to_c(m->messageid822); + killtrailingwhite(p); +} + +static int +isattribute(char **pp, char *attr) +{ + char *p; + int n; + + n = strlen(attr); + p = *pp; + if(cistrncmp(p, attr, n) != 0) + return 0; + p += n; + while(*p == ' ') + p++; + if(*p++ != '=') + return 0; + while(*p == ' ') + p++; + *pp = p; + return 1; +} + +static void +ctype(Message *m, Header *h, char *p) +{ + String *s; + + p += h->len; + p = skipwhite(p); + + p = getstring(p, m->type, 1); + + while(*p){ + if(isattribute(&p, "boundary")){ + s = s_new(); + p = getstring(p, s, 0); + m->boundary = s_reset(m->boundary); + s_append(m->boundary, "--"); + s_append(m->boundary, s_to_c(s)); + s_free(s); + } else if(cistrncmp(p, "multipart", 9) == 0){ + /* + * the first unbounded part of a multipart message, + * the preamble, is not displayed or saved + */ + } else if(isattribute(&p, "name")){ + if(m->filename == nil) + setfilename(m, p); + } else if(isattribute(&p, "charset")){ + p = getstring(p, s_reset(m->charset), 0); + } + + p = skiptosemi(p); + } +} + +static void +cencoding(Message *m, Header *h, char *p) +{ + p += h->len; + p = skipwhite(p); + if(cistrncmp(p, "base64", 6) == 0) + m->encoding = Ebase64; + else if(cistrncmp(p, "quoted-printable", 16) == 0) + m->encoding = Equoted; +} + +static void +cdisposition(Message *m, Header *h, char *p) +{ + p += h->len; + p = skipwhite(p); + while(*p){ + if(cistrncmp(p, "inline", 6) == 0){ + m->disposition = Dinline; + } else if(cistrncmp(p, "attachment", 10) == 0){ + m->disposition = Dfile; + } else if(cistrncmp(p, "filename=", 9) == 0){ + p += 9; + setfilename(m, p); + } + p = skiptosemi(p); + } + +} + +ulong msgallocd, msgfreed; + +Message* +newmessage(Message *parent) +{ + /* static int id; jpc */ + Message *m; + + msgallocd++; + + m = emalloc(sizeof(*m)); + memset(m, 0, sizeof(*m)); + m->disposition = Dnone; + m->type = s_copy("text/plain"); + m->charset = s_copy("iso-8859-1"); + m->id = newid(); + if(parent) + sprint(m->name, "%d", ++(parent->subname)); + if(parent == nil) + parent = m; + m->whole = parent; + m->hlen = -1; + return m; +} + +// delete a message from a mailbox +void +delmessage(Mailbox *mb, Message *m) +{ + Message **l; + int i; + + mb->vers++; + msgfreed++; + + if(m->whole != m){ + // unchain from parent + for(l = &m->whole->part; *l && *l != m; l = &(*l)->next) + ; + if(*l != nil) + *l = m->next; + + // clear out of name lookup hash table + if(m->whole->whole == m->whole) + hfree(PATH(mb->id, Qmbox), m->name); + else + hfree(PATH(m->whole->id, Qdir), m->name); + for(i = 0; i < Qmax; i++) + hfree(PATH(m->id, Qdir), dirtab[i]); + } + + /* recurse through sub-parts */ + while(m->part) + delmessage(mb, m->part); + + /* free memory */ + if(m->mallocd) + free(m->start); + if(m->hallocd) + free(m->header); + if(m->ballocd) + free(m->body); + s_free(m->unixfrom); + s_free(m->unixdate); + s_free(m->unixheader); + s_free(m->from822); + s_free(m->sender822); + s_free(m->to822); + s_free(m->bcc822); + s_free(m->cc822); + s_free(m->replyto822); + s_free(m->date822); + s_free(m->inreplyto822); + s_free(m->subject822); + s_free(m->messageid822); + s_free(m->addrs); + s_free(m->mimeversion); + s_free(m->sdigest); + s_free(m->boundary); + s_free(m->type); + s_free(m->charset); + s_free(m->filename); + + free(m); +} + +// mark messages (identified by path) for deletion +void +delmessages(int ac, char **av) +{ + Mailbox *mb; + Message *m; + int i, needwrite; + + qlock(&mbllock); + for(mb = mbl; mb != nil; mb = mb->next) + if(strcmp(av[0], mb->name) == 0){ + qlock(&mb->ql); + break; + } + qunlock(&mbllock); + if(mb == nil) + return; + + needwrite = 0; + for(i = 1; i < ac; i++){ + for(m = mb->root->part; m != nil; m = m->next) + if(strcmp(m->name, av[i]) == 0){ + if(!m->deleted){ + mailplumb(mb, m, 1); + needwrite = 1; + m->deleted = 1; + logmsg("deleting", m); + } + break; + } + } + if(needwrite) + syncmbox(mb, 1); + qunlock(&mb->ql); +} + +/* + * the following are called with the mailbox qlocked + */ +void +msgincref(Message *m) +{ + m->refs++; +} +void +msgdecref(Mailbox *mb, Message *m) +{ + m->refs--; + if(m->refs == 0 && m->deleted) + syncmbox(mb, 1); +} + +/* + * the following are called with mbllock'd + */ +void +mboxincref(Mailbox *mb) +{ + assert(mb->refs > 0); + mb->refs++; +} +void +mboxdecref(Mailbox *mb) +{ + assert(mb->refs > 0); + qlock(&mb->ql); + mb->refs--; + if(mb->refs == 0){ + delmessage(mb, mb->root); + if(mb->ctl) + hfree(PATH(mb->id, Qmbox), "ctl"); + if(mb->close) + (*mb->close)(mb); + free(mb); + } else + qunlock(&mb->ql); +} + +int +cistrncmp(char *a, char *b, int n) +{ + while(n-- > 0){ + if(tolower(*a++) != tolower(*b++)) + return -1; + } + return 0; +} + +int +cistrcmp(char *a, char *b) +{ + for(;;){ + if(tolower(*a) != tolower(*b++)) + return -1; + if(*a++ == 0) + break; + } + return 0; +} + +static char* +skipwhite(char *p) +{ + while(isspace(*p)) + p++; + return p; +} + +static char* +skiptosemi(char *p) +{ + while(*p && *p != ';') + p++; + while(*p == ';' || isspace(*p)) + p++; + return p; +} + +static char* +getstring(char *p, String *s, int dolower) +{ + s = s_reset(s); + p = skipwhite(p); + if(*p == '"'){ + p++; + for(;*p && *p != '"'; p++) + if(dolower) + s_putc(s, tolower(*p)); + else + s_putc(s, *p); + if(*p == '"') + p++; + s_terminate(s); + + return p; + } + + for(; *p && !isspace(*p) && *p != ';'; p++) + if(dolower) + s_putc(s, tolower(*p)); + else + s_putc(s, *p); + s_terminate(s); + + return p; +} + +static void +setfilename(Message *m, char *p) +{ + m->filename = s_reset(m->filename); + getstring(p, m->filename, 0); + for(p = s_to_c(m->filename); *p; p++) + if(*p == ' ' || *p == '\t' || *p == ';') + *p = '_'; +} + +// +// undecode message body +// +void +decode(Message *m) +{ + int i, len; + char *x; + + if(m->decoded) + return; + switch(m->encoding){ + case Ebase64: + len = m->bend - m->body; + i = (len*3)/4+1; // room for max chars + null + x = emalloc(i); + len = dec64((uchar*)x, i, m->body, len); + if(m->ballocd) + free(m->body); + m->body = x; + m->bend = x + len; + m->ballocd = 1; + break; + case Equoted: + len = m->bend - m->body; + x = emalloc(len+2); // room for null and possible extra nl + len = decquoted(x, m->body, m->bend); + if(m->ballocd) + free(m->body); + m->body = x; + m->bend = x + len; + m->ballocd = 1; + break; + default: + break; + } + m->decoded = 1; +} + +// convert latin1 to utf +void +convert(Message *m) +{ + int len; + char *x; + + // don't convert if we're not a leaf, not text, or already converted + if(m->converted) + return; + if(m->part != nil) + return; + if(cistrncmp(s_to_c(m->type), "text", 4) != 0) + return; + + if(cistrcmp(s_to_c(m->charset), "us-ascii") == 0 || + cistrcmp(s_to_c(m->charset), "iso-8859-1") == 0){ + len = is8bit(m); + if(len > 0){ + len = 2*len + m->bend - m->body + 1; + x = emalloc(len); + len = latin1toutf(x, m->body, m->bend); + if(m->ballocd) + free(m->body); + m->body = x; + m->bend = x + len; + m->ballocd = 1; + } + } else if(cistrcmp(s_to_c(m->charset), "iso-8859-2") == 0){ + len = xtoutf("8859-2", &x, m->body, m->bend); + if(len != 0){ + if(m->ballocd) + free(m->body); + m->body = x; + m->bend = x + len; + m->ballocd = 1; + } + } else if(cistrcmp(s_to_c(m->charset), "iso-8859-15") == 0){ + len = xtoutf("8859-15", &x, m->body, m->bend); + if(len != 0){ + if(m->ballocd) + free(m->body); + m->body = x; + m->bend = x + len; + m->ballocd = 1; + } + } else if(cistrcmp(s_to_c(m->charset), "big5") == 0){ + len = xtoutf("big5", &x, m->body, m->bend); + if(len != 0){ + if(m->ballocd) + free(m->body); + m->body = x; + m->bend = x + len; + m->ballocd = 1; + } + } else if(cistrcmp(s_to_c(m->charset), "iso-2022-jp") == 0){ + len = xtoutf("jis", &x, m->body, m->bend); + if(len != 0){ + if(m->ballocd) + free(m->body); + m->body = x; + m->bend = x + len; + m->ballocd = 1; + } + } else if(cistrcmp(s_to_c(m->charset), "windows-1257") == 0 + || cistrcmp(s_to_c(m->charset), "windows-1252") == 0){ + len = is8bit(m); + if(len > 0){ + len = 2*len + m->bend - m->body + 1; + x = emalloc(len); + len = windows1257toutf(x, m->body, m->bend); + if(m->ballocd) + free(m->body); + m->body = x; + m->bend = x + len; + m->ballocd = 1; + } + } else if(cistrcmp(s_to_c(m->charset), "windows-1251") == 0){ + len = xtoutf("cp1251", &x, m->body, m->bend); + if(len != 0){ + if(m->ballocd) + free(m->body); + m->body = x; + m->bend = x + len; + m->ballocd = 1; + } + } else if(cistrcmp(s_to_c(m->charset), "koi8-r") == 0){ + len = xtoutf("koi8", &x, m->body, m->bend); + if(len != 0){ + if(m->ballocd) + free(m->body); + m->body = x; + m->bend = x + len; + m->ballocd = 1; + } + } + + m->converted = 1; +} + +enum +{ + Self= 1, + Hex= 2, +}; +uchar tableqp[256]; + +static void +initquoted(void) +{ + int c; + + memset(tableqp, 0, 256); + for(c = ' '; c <= '<'; c++) + tableqp[c] = Self; + for(c = '>'; c <= '~'; c++) + tableqp[c] = Self; + tableqp['\t'] = Self; + tableqp['='] = Hex; +} + +static int +hex2int(int x) +{ + if(x >= '0' && x <= '9') + return x - '0'; + if(x >= 'A' && x <= 'F') + return (x - 'A') + 10; + if(x >= 'a' && x <= 'f') + return (x - 'a') + 10; + return 0; +} + +static char* +decquotedline(char *out, char *in, char *e) +{ + int c, soft; + + /* dump trailing white space */ + while(e >= in && (*e == ' ' || *e == '\t' || *e == '\r' || *e == '\n')) + e--; + + /* trailing '=' means no newline */ + if(*e == '='){ + soft = 1; + e--; + } else + soft = 0; + + while(in <= e){ + c = (*in++) & 0xff; + switch(tableqp[c]){ + case Self: + *out++ = c; + break; + case Hex: + c = hex2int(*in++)<<4; + c |= hex2int(*in++); + *out++ = c; + break; + } + } + if(!soft) + *out++ = '\n'; + *out = 0; + + return out; +} + +int +decquoted(char *out, char *in, char *e) +{ + char *p, *nl; + + if(tableqp[' '] == 0) + initquoted(); + + p = out; + while((nl = strchr(in, '\n')) != nil && nl < e){ + p = decquotedline(p, in, nl); + in = nl + 1; + } + if(in < e) + p = decquotedline(p, in, e-1); + + // make sure we end with a new line + if(*(p-1) != '\n'){ + *p++ = '\n'; + *p = 0; + } + + return p - out; +} + +#if 0 /* jpc */ +static char* +lowercase(char *p) +{ + char *op; + int c; + + for(op = p; c = *p; p++) + if(isupper(c)) + *p = tolower(c); + return op; +} +#endif + +/* + * return number of 8 bit characters + */ +static int +is8bit(Message *m) +{ + int count = 0; + char *p; + + for(p = m->body; p < m->bend; p++) + if(*p & 0x80) + count++; + return count; +} + +// translate latin1 directly since it fits neatly in utf +int +latin1toutf(char *out, char *in, char *e) +{ + Rune r; + char *p; + + p = out; + for(; in < e; in++){ + r = (*in) & 0xff; + p += runetochar(p, &r); + } + *p = 0; + return p - out; +} + +// translate any thing else using the tcs program +int +xtoutf(char *charset, char **out, char *in, char *e) +{ + char *av[4]; + int totcs[2]; + int fromtcs[2]; + int n, len, sofar; + char *p; + + len = e-in+1; + sofar = 0; + *out = p = malloc(len+1); + if(p == nil) + return 0; + + av[0] = charset; + av[1] = "-f"; + av[2] = charset; + av[3] = 0; + if(pipe(totcs) < 0) + return 0; + if(pipe(fromtcs) < 0){ + close(totcs[0]); close(totcs[1]); + return 0; + } + switch(rfork(RFPROC|RFFDG|RFNOWAIT)){ + case -1: + close(fromtcs[0]); close(fromtcs[1]); + close(totcs[0]); close(totcs[1]); + return 0; + case 0: + close(fromtcs[0]); close(totcs[1]); + dup(fromtcs[1], 1); + dup(totcs[0], 0); + close(fromtcs[1]); close(totcs[0]); + dup(open("/dev/null", OWRITE), 2); + //jpc exec("/bin/tcs", av); + exec(unsharp("#9/bin/tcs"), av); + /* _exits(0); */ + threadexits(nil); + default: + close(fromtcs[1]); close(totcs[0]); + switch(rfork(RFPROC|RFFDG|RFNOWAIT)){ + case -1: + close(fromtcs[0]); close(totcs[1]); + return 0; + case 0: + close(fromtcs[0]); + while(in < e){ + n = write(totcs[1], in, e-in); + if(n <= 0) + break; + in += n; + } + close(totcs[1]); + /* _exits(0); */ + threadexits(nil); + default: + close(totcs[1]); + for(;;){ + n = read(fromtcs[0], &p[sofar], len-sofar); + if(n <= 0) + break; + sofar += n; + p[sofar] = 0; + if(sofar == len){ + len += 1024; + *out = p = realloc(p, len+1); + if(p == nil) + return 0; + } + } + close(fromtcs[0]); + break; + } + break; + } + return sofar; +} + +enum { + Winstart= 0x7f, + Winend= 0x9f, +}; + +Rune winchars[] = { + L'•', + L'•', L'•', L'‚', L'ƒ', L'„', L'…', L'†', L'‡', + L'ˆ', L'‰', L'Š', L'‹', L'Œ', L'•', L'•', L'•', + L'•', L'‘', L'’', L'“', L'”', L'•', L'–', L'—', + L'˜', L'™', L'š', L'›', L'œ', L'•', L'•', L'Ÿ', +}; + +int +windows1257toutf(char *out, char *in, char *e) +{ + Rune r; + char *p; + + p = out; + for(; in < e; in++){ + r = (*in) & 0xff; + if(r >= 0x7f && r <= 0x9f) + r = winchars[r-0x7f]; + p += runetochar(p, &r); + } + *p = 0; + return p - out; +} + +void * +emalloc(ulong n) +{ + void *p; + + p = mallocz(n, 1); + if(!p){ + fprint(2, "%s: out of memory alloc %lud\n", argv0, n); + threadexits("out of memory"); + } + setmalloctag(p, getcallerpc(&n)); + return p; +} + +void * +erealloc(void *p, ulong n) +{ + if(n == 0) + n = 1; + p = realloc(p, n); + if(!p){ + fprint(2, "%s: out of memory realloc %lud\n", argv0, n); + threadexits("out of memory"); + } + setrealloctag(p, getcallerpc(&p)); + return p; +} + +void +mailplumb(Mailbox *mb, Message *m, int delete) +{ + Plumbmsg p; + Plumbattr a[7]; + char buf[256]; + int ai; + char lenstr[10], *from, *subject, *date; + static int fd = -1; + + if(m->subject822 == nil) + subject = ""; + else + subject = s_to_c(m->subject822); + + if(m->from822 != nil) + from = s_to_c(m->from822); + else if(m->unixfrom != nil) + from = s_to_c(m->unixfrom); + else + from = ""; + + if(m->unixdate != nil) + date = s_to_c(m->unixdate); + else + date = ""; + + sprint(lenstr, "%ld", m->end-m->start); + + if(biffing && !delete) + print("[ %s / %s / %s ]\n", from, subject, lenstr); + + if(!plumbing) + return; + + if(fd < 0) + fd = plumbopen("send", OWRITE); + if(fd < 0) + return; + + p.src = "mailfs"; + p.dst = "seemail"; + p.wdir = "/mail/fs"; + p.type = "text"; + + ai = 0; + a[ai].name = "filetype"; + a[ai].value = "mail"; + + a[++ai].name = "sender"; + a[ai].value = from; + a[ai-1].next = &a[ai]; + + a[++ai].name = "length"; + a[ai].value = lenstr; + a[ai-1].next = &a[ai]; + + a[++ai].name = "mailtype"; + a[ai].value = delete?"delete":"new"; + a[ai-1].next = &a[ai]; + + a[++ai].name = "date"; + a[ai].value = date; + a[ai-1].next = &a[ai]; + + if(m->sdigest){ + a[++ai].name = "digest"; + a[ai].value = s_to_c(m->sdigest); + a[ai-1].next = &a[ai]; + } + + a[ai].next = nil; + + p.attr = a; + snprint(buf, sizeof(buf), "%s/%s/%s", + mntpt, mb->name, m->name); + p.ndata = strlen(buf); + p.data = buf; + + plumbsend(fd, &p); +} + +// +// count the number of lines in the body (for imap4) +// +void +countlines(Message *m) +{ + int i; + char *p; + + i = 0; + for(p = strchr(m->rbody, '\n'); p != nil && p < m->rbend; p = strchr(p+1, '\n')) + i++; + sprint(m->lines, "%d", i); +} + +char *LOG = "fs"; + +void +logmsg(char *s, Message *m) +{ + int pid; + + if(!logging) + return; + pid = getpid(); + if(m == nil) + syslog(0, LOG, "%s.%d: %s", user, pid, s); + else + syslog(0, LOG, "%s.%d: %s msg from %s digest %s", + user, pid, s, + m->from822 ? s_to_c(m->from822) : "?", + s_to_c(m->sdigest)); +} + +/* + * squeeze nulls out of the body + */ +static void +nullsqueeze(Message *m) +{ + char *p, *q; + + q = memchr(m->body, 0, m->end-m->body); + if(q == nil) + return; + + for(p = m->body; q < m->end; q++){ + if(*q == 0) + continue; + *p++ = *q; + } + m->bend = m->rbend = m->end = p; +} + + +// +// convert an RFC822 date into a Unix style date +// for when the Unix From line isn't there (e.g. POP3). +// enough client programs depend on having a Unix date +// that it's easiest to write this conversion code once, right here. +// +// people don't follow RFC822 particularly closely, +// so we use strtotm, which is a bunch of heuristics. +// + +extern int strtotm(char*, Tm*); +String* +date822tounix(char *s) +{ + char *p, *q; + Tm tm; + + if(strtotm(s, &tm) < 0) + return nil; + + p = asctime(&tm); + if(q = strchr(p, '\n')) + *q = '\0'; + return s_copy(p); +} + blob - /dev/null blob + c596ddfc89d4511a05916944b39a54afc7d2f592 (mode 644) --- /dev/null +++ src/cmd/upas/fs/mkfile @@ -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 blob - /dev/null blob + 05239549cae34c5edc033138d4d29994fdf5f24b (mode 644) --- /dev/null +++ src/cmd/upas/fs/mkfile.9 @@ -0,0 +1,27 @@ + +#include +#include +#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; +} blob - /dev/null blob + 4ea9adb32ebdda4a9e3c4abb9e0e082dec9b2bb7 (mode 644) --- /dev/null +++ src/cmd/upas/fs/pop3.c @@ -0,0 +1,700 @@ +#include "common.h" +#include +#include +#include +#include +#include +#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; +} + blob - /dev/null blob + 42028d4c1565a454ab4355dfe98a2274b4e13e0f (mode 644) --- /dev/null +++ src/cmd/upas/fs/readdir.c @@ -0,0 +1,15 @@ +#include +#include + +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); +} blob - /dev/null blob + bcf0bcee0ca1a9c00015d7141432f0cdf50ecbdd (mode 644) --- /dev/null +++ src/cmd/upas/fs/strtotm.c @@ -0,0 +1,113 @@ +#include +#include +#include + +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= 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; +} blob - /dev/null blob + 3d24012efa7eaa37b814786c7b6d213e2ad6838e (mode 644) --- /dev/null +++ src/cmd/upas/fs/tester.c @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#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); +} blob - /dev/null blob + cfc186c0a8dabd89c7a5a2f399fb4b470414d93b (mode 644) --- /dev/null +++ src/cmd/upas/marshal/marshal.c @@ -0,0 +1,1857 @@ +#include "common.h" +#include + +typedef struct Attach Attach; +typedef struct Alias Alias; +typedef struct Addr Addr; +typedef struct Ctype Ctype; + +struct Attach { + Attach *next; + char *path; + char *type; + int tinline; + Ctype *ctype; +}; + +struct Alias +{ + Alias *next; + int n; + Addr *addr; +}; + +struct Addr +{ + Addr *next; + char *v; +}; + +enum { + Hfrom, + Hto, + Hcc, + Hbcc, + Hsender, + Hreplyto, + Hinreplyto, + Hdate, + Hsubject, + Hmime, + Hpriority, + Hmsgid, + Hcontent, + Hx, + Hprecedence, + Nhdr, +}; + +enum { + PGPsign = 1, + PGPencrypt = 2, +}; + +char *hdrs[Nhdr] = { +[Hfrom] "from:", +[Hto] "to:", +[Hcc] "cc:", +[Hbcc] "bcc:", +[Hreplyto] "reply-to:", +[Hinreplyto] "in-reply-to:", +[Hsender] "sender:", +[Hdate] "date:", +[Hsubject] "subject:", +[Hpriority] "priority:", +[Hmsgid] "message-id:", +[Hmime] "mime-", +[Hcontent] "content-", +[Hx] "x-", +[Hprecedence] "precedence", +}; + +struct Ctype { + char *type; + char *ext; + int display; +}; + +Ctype ctype[] = { + { "text/plain", "txt", 1, }, + { "text/html", "html", 1, }, + { "text/html", "htm", 1, }, + { "text/tab-separated-values", "tsv", 1, }, + { "text/richtext", "rtx", 1, }, + { "message/rfc822", "txt", 1, }, + { "", 0, 0, }, +}; + +Ctype *mimetypes; + +int pid = -1; +int pgppid = -1; + +Attach* mkattach(char*, char*, int); +int readheaders(Biobuf*, int*, String**, Addr**, int); +void body(Biobuf*, Biobuf*, int); +char* mkboundary(void); +int printdate(Biobuf*); +int printfrom(Biobuf*); +int printto(Biobuf*, Addr*); +int printcc(Biobuf*, Addr*); +int printsubject(Biobuf*, char*); +int printinreplyto(Biobuf*, char*); +int sendmail(Addr*, Addr*, int*, char*); +void attachment(Attach*, Biobuf*); +int cistrncmp(char*, char*, int); +int cistrcmp(char*, char*); +char* waitforsubprocs(void); +int enc64(char*, int, uchar*, int); +Addr* expand(int, char**); +Alias* readaliases(void); +Addr* expandline(String**, Addr*); +void Bdrain(Biobuf*); +void freeaddr(Addr *); +int pgpopts(char*); +int pgpfilter(int*, int, int); +void readmimetypes(void); +char* estrdup(char*); +void* emalloc(int); +void* erealloc(void*, int); +void freeaddr(Addr*); +void freeaddrs(Addr*); +void freealias(Alias*); +void freealiases(Alias*); +int doublequote(Fmt*); + +int rflag, lbflag, xflag, holding, nflag, Fflag, eightflag, dflag; +int pgpflag = 0; +char *user; +char *login; +Alias *aliases; +int rfc822syntaxerror; +char lastchar; +char *replymsg; + +enum +{ + Ok = 0, + Nomessage = 1, + Nobody = 2, + Error = -1, +}; + +#pragma varargck type "Z" char* + +void +usage(void) +{ + fprint(2, "usage: %s [-Fr#xn] [-s subject] [-c ccrecipient] [-t type] [-aA attachment] [-p[es]] [-R replymsg] -8 | recipient-list\n", + argv0); + exits("usage"); +} + +void +fatal(char *fmt, ...) +{ + char buf[1024]; + va_list arg; + + if(pid >= 0) + postnote(PNPROC, pid, "die"); + if(pgppid >= 0) + postnote(PNPROC, pgppid, "die"); + + va_start(arg, fmt); + vseprint(buf, buf+sizeof(buf), fmt, arg); + va_end(arg); + fprint(2, "%s: %s\n", argv0, buf); + holdoff(holding); + exits(buf); +} + +void +main(int argc, char **argv) +{ + Attach *first, **l, *a; + char *subject, *type, *boundary; + int flags, fd; + Biobuf in, out, *b; + Addr *to; + Addr *cc; + String *file, *hdrstring; + int noinput, headersrv; + int ccargc; + char *ccargv[32]; + + noinput = 0; + subject = nil; + first = nil; + l = &first; + type = nil; + hdrstring = nil; + ccargc = 0; + + quotefmtinstall(); + fmtinstall('Z', doublequote); + + ARGBEGIN{ + case 't': + type = ARGF(); + if(type == nil) + usage(); + break; + case 'a': + flags = 0; + goto aflag; + case 'A': + flags = 1; + aflag: + a = mkattach(ARGF(), type, flags); + if(a == nil) + exits("bad args"); + type = nil; + *l = a; + l = &a->next; + break; + case 'C': + if(ccargc >= nelem(ccargv)-1) + sysfatal("too many cc's"); + ccargv[ccargc] = ARGF(); + if(ccargv[ccargc] == nil) + usage(); + ccargc++; + break; + case 'R': + replymsg = ARGF(); + break; + case 's': + subject = ARGF(); + break; + case 'F': + Fflag = 1; // file message + break; + case 'r': + rflag = 1; // for sendmail + break; + case 'd': + dflag = 1; // for sendmail + break; + case '#': + lbflag = 1; // for sendmail + break; + case 'x': + xflag = 1; // for sendmail + break; + case 'n': // no standard input + nflag = 1; + break; + case '8': // read recipients from rfc822 header + eightflag = 1; + break; + case 'p': // pgp flag: encrypt, sign, or both + if(pgpopts(ARGF()) < 0) + sysfatal("bad pgp options"); + break; + default: + usage(); + break; + }ARGEND; + + login = getlog(); + user = getenv("upasname"); + if(user == nil || *user == 0) + user = login; + if(user == nil || *user == 0) + sysfatal("can't read user name"); + + if(Binit(&in, 0, OREAD) < 0) + sysfatal("can't Binit 0: %r"); + + if(nflag && eightflag) + sysfatal("can't use both -n and -8"); + if(eightflag && argc >= 1) + usage(); + else if(!eightflag && argc < 1) + usage(); + + aliases = readaliases(); + if(!eightflag){ + to = expand(argc, argv); + cc = expand(ccargc, ccargv); + } else { + to = nil; + cc = nil; + } + + flags = 0; + headersrv = Nomessage; + if(!nflag && !xflag && !lbflag &&!dflag) { + // pass through headers, keeping track of which we've seen, + // perhaps building to list. + holding = holdon(); + headersrv = readheaders(&in, &flags, &hdrstring, eightflag ? &to : nil, 1); + if(rfc822syntaxerror){ + Bdrain(&in); + fatal("rfc822 syntax error, message not sent"); + } + if(to == nil){ + Bdrain(&in); + fatal("no addresses found, message not sent"); + } + + switch(headersrv){ + case Error: // error + fatal("reading"); + break; + case Nomessage: // no message, just exit mimicking old behavior + noinput = 1; + if(first == nil) + exits(0); + break; + } + } + + fd = sendmail(to, cc, &pid, Fflag ? argv[0] : nil); + if(fd < 0) + sysfatal("execing sendmail: %r\n:"); + if(xflag || lbflag || dflag){ + close(fd); + exits(waitforsubprocs()); + } + + if(Binit(&out, fd, OWRITE) < 0) + fatal("can't Binit 1: %r"); + + if(!nflag){ + if(Bwrite(&out, s_to_c(hdrstring), s_len(hdrstring)) != s_len(hdrstring)) + fatal("write error"); + s_free(hdrstring); + hdrstring = nil; + + // read user's standard headers + file = s_new(); + mboxpath("headers", user, file, 0); + b = Bopen(s_to_c(file), OREAD); + if(b != nil){ + switch(readheaders(b, &flags, &hdrstring, nil, 0)){ + case Error: // error + fatal("reading"); + } + Bterm(b); + if(Bwrite(&out, s_to_c(hdrstring), s_len(hdrstring)) != s_len(hdrstring)) + fatal("write error"); + s_free(hdrstring); + hdrstring = nil; + } + } + + // add any headers we need + if((flags & (1<next){ + if(lastchar != '\n') + Bprint(&out, "\n"); + Bprint(&out, "--%s\n", boundary); + attachment(a, &out); + } + + if(first != nil){ + if(lastchar != '\n') + Bprint(&out, "\n"); + Bprint(&out, "--%s--\n", boundary); + } + + Bterm(&out); + close(fd); + exits(waitforsubprocs()); +} + +// evaluate pgp option string +int +pgpopts(char *s) +{ + if(s == nil || s[0] == '\0') + return -1; + while(*s){ + switch(*s++){ + case 's': case 'S': + pgpflag |= PGPsign; + break; + case 'e': case 'E': + pgpflag |= PGPencrypt; + break; + default: + return -1; + } + } + return 0; +} + +// read headers from stdin into a String, expanding local aliases, +// keep track of which headers are there, which addresses we have +// remove Bcc: line. +int +readheaders(Biobuf *in, int *fp, String **sp, Addr **top, int strict) +{ + Addr *to; + String *s, *sline; + char *p; + int i, seen, hdrtype; + + s = s_new(); + sline = nil; + to = nil; + hdrtype = -1; + seen = 0; + for(;;) { + if((p = Brdline(in, '\n')) != nil) { + seen = 1; + p[Blinelen(in)-1] = 0; + + // coalesce multiline headers + if((*p == ' ' || *p == '\t') && sline){ + s_append(sline, "\n"); + s_append(sline, p); + p[Blinelen(in)-1] = '\n'; + continue; + } + } + + // process the current header, it's all been read + if(sline) { + assert(hdrtype != -1); + if(top){ + switch(hdrtype){ + case Hto: + case Hcc: + case Hbcc: + to = expandline(&sline, to); + break; + } + } + if(top==nil || hdrtype!=Hbcc){ + s_append(s, s_to_c(sline)); + s_append(s, "\n"); + } + s_free(sline); + sline = nil; + } + + if(p == nil) + break; + + // if no :, it's not a header, seek back and break + if(strchr(p, ':') == nil){ + p[Blinelen(in)-1] = '\n'; + Bseek(in, -Blinelen(in), 1); + break; + } + + sline = s_copy(p); + + // classify the header. If we don't recognize it, break. This is + // to take care of user's that start messages with lines that contain + // ':'s but that aren't headers. This is a bit hokey. Since I decided + // to let users type headers, I need some way to distinguish. Therefore, + // marshal tries to know all likely headers and will indeed screw up if + // the user types an unlikely one. -- presotto + hdrtype = -1; + for(i = 0; i < nelem(hdrs); i++){ + if(cistrncmp(hdrs[i], p, strlen(hdrs[i])) == 0){ + *fp |= 1< 0){ + if(i != '\n') + buf[n++] = '\n'; + buf[n++] = i; + } else { + buf[n++] = '\n'; + } + + // read into memory + if(docontenttype){ + while(docontenttype){ + if(n == len){ + len += len>>2; + buf = realloc(buf, len); + if(buf == nil) + sysfatal("%r"); + } + p = buf+n; + i = Bread(in, p, len - n); + if(i < 0) + fatal("input error2"); + if(i == 0) + break; + n += i; + for(; i > 0; i--) + if((*p++ & 0x80) && docontenttype){ + Bprint(out, "Content-Type: text/plain; charset=\"UTF-8\"\n"); + Bprint(out, "Content-Transfer-Encoding: 8bit\n"); + docontenttype = 0; + break; + } + } + if(docontenttype){ + Bprint(out, "Content-Type: text/plain; charset=\"US-ASCII\"\n"); + Bprint(out, "Content-Transfer-Encoding: 7bit\n"); + } + } + + // write what we already read + if(Bwrite(out, buf, n) < 0) + fatal("output error"); + if(n > 0) + lastchar = buf[n-1]; + else + lastchar = '\n'; + + + // pass the rest + for(;;){ + n = Bread(in, buf, len); + if(n < 0) + fatal("input error2"); + if(n == 0) + break; + if(Bwrite(out, buf, n) < 0) + fatal("output error"); + lastchar = buf[n-1]; + } +} + +// pass the body to sendmail encoding with base64 +// +// the size of buf is very important to enc64. Anything other than +// a multiple of 3 will cause enc64 to output a termination sequence. +// To ensure that a full buf corresponds to a multiple of complete lines, +// we make buf a multiple of 3*18 since that's how many enc64 sticks on +// a single line. This avoids short lines in the output which is pleasing +// but not necessary. +// +void +body64(Biobuf *in, Biobuf *out) +{ + uchar buf[3*18*54]; + char obuf[3*18*54*2]; + int m, n; + + Bprint(out, "\n"); + for(;;){ + n = Bread(in, buf, sizeof(buf)); + fprint(2,"read %d bytes\n",n); + if(n < 0) + fatal("input error"); + if(n == 0) + break; + m = enc64(obuf, sizeof(obuf), buf, n); + fprint(2,"encoded %d bytes\n",m); + fprint(2,"writing to %x\n",out); + if((n=Bwrite(out, obuf, m)) < 0) + fatal("output error"); + fprint(2,"wrote %d bytes\n",n); + } + lastchar = '\n'; + fprint(2,"done with attachment\n"); +} + +// pass message to sendmail, make sure body starts with a newline +void +copy(Biobuf *in, Biobuf *out) +{ + char buf[4*1024]; + int n; + + for(;;){ + n = Bread(in, buf, sizeof(buf)); + if(n < 0) + fatal("input error"); + if(n == 0) + break; + if(Bwrite(out, buf, n) < 0) + fatal("output error"); + } +} + +void +attachment(Attach *a, Biobuf *out) +{ + Biobuf *f; + char *p; + + // if it's already mime encoded, just copy + if(strcmp(a->type, "mime") == 0){ + f = Bopen(a->path, OREAD); + if(f == nil){ + /* hack: give marshal time to stdin, before we kill it (for dead.letter) */ + sleep(500); + postnote(PNPROC, pid, "interrupt"); + sysfatal("opening %s: %r", a->path); + } + copy(f, out); + Bterm(f); + } + + // if it's not already mime encoded ... + if(strcmp(a->type, "text/plain") != 0) + Bprint(out, "Content-Type: %s\n", a->type); + + if(a->tinline){ + Bprint(out, "Content-Disposition: inline\n"); + } else { + p = strrchr(a->path, '/'); + if(p == nil) + p = a->path; + else + p++; + Bprint(out, "Content-Disposition: attachment; filename=%Z\n", p); + } + + f = Bopen(a->path, OREAD); + if(f == nil){ + /* hack: give marshal time to stdin, before we kill it (for dead.letter) */ + sleep(500); + postnote(PNPROC, pid, "interrupt"); + sysfatal("opening %s: %r", a->path); + } + + /* dump our local 'From ' line when passing along mail messages */ + if(strcmp(a->type, "message/rfc822") == 0){ + p = Brdline(f, '\n'); + if(strncmp(p, "From ", 5) != 0) + Bseek(f, 0, 0); + } + if(a->ctype->display){ + body(f, out, strcmp(a->type, "text/plain") == 0); + } else { + Bprint(out, "Content-Transfer-Encoding: base64\n"); + body64(f, out); + } + Bterm(f); +} + +char *ascwday[] = +{ + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" +}; + +char *ascmon[] = +{ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +int +printdate(Biobuf *b) +{ + Tm *tm; + int tz; + + tm = localtime(time(0)); + tz = (tm->tzoff/3600)*100 + ((tm->tzoff/60)%60); + + return Bprint(b, "Date: %s, %d %s %d %2.2d:%2.2d:%2.2d %s%.4d\n", + ascwday[tm->wday], tm->mday, ascmon[tm->mon], 1900+tm->year, + tm->hour, tm->min, tm->sec, tz>=0?"+":"", tz); +} + +int +printfrom(Biobuf *b) +{ + return Bprint(b, "From: %s\n", user); +} + +int +printto(Biobuf *b, Addr *a) +{ + int i; + + if(Bprint(b, "To: %s", a->v) < 0) + return -1; + i = 0; + for(a = a->next; a != nil; a = a->next) + if(Bprint(b, "%s%s", ((i++ & 7) == 7)?",\n\t":", ", a->v) < 0) + return -1; + if(Bprint(b, "\n") < 0) + return -1; + return 0; +} + +int +printcc(Biobuf *b, Addr *a) +{ + int i; + + if(a == nil) + return 0; + if(Bprint(b, "CC: %s", a->v) < 0) + return -1; + i = 0; + for(a = a->next; a != nil; a = a->next) + if(Bprint(b, "%s%s", ((i++ & 7) == 7)?",\n\t":", ", a->v) < 0) + return -1; + if(Bprint(b, "\n") < 0) + return -1; + return 0; +} + +int +printsubject(Biobuf *b, char *subject) +{ + return Bprint(b, "Subject: %s\n", subject); +} + +int +printinreplyto(Biobuf *out, char *dir) +{ + String *s = s_copy(dir); + char buf[256]; + int fd; + int n; + + s_append(s, "/messageid"); + fd = open(s_to_c(s), OREAD); + s_free(s); + if(fd < 0) + return 0; + n = read(fd, buf, sizeof(buf)-1); + close(fd); + if(n <= 0) + return 0; + buf[n] = 0; + return Bprint(out, "In-Reply-To: %s\n", buf); +} + +Attach* +mkattach(char *file, char *type, int tinline) +{ + Ctype *c; + Attach *a; + char ftype[64]; + char *p; + int n, pfd[2]; + + if(file == nil) + return nil; + if(access(file, 4) == -1){ + fprint(2, "%s: %s can't read file\n", argv0, file); + return nil; + } + a = emalloc(sizeof(*a)); + a->path = file; + a->next = nil; + a->type = type; + a->tinline = tinline; + a->ctype = nil; + if(type != nil){ + for(c = ctype; ; c++) + if(strncmp(type, c->type, strlen(c->type)) == 0){ + a->ctype = c; + break; + } + return a; + } + + // pick a type depending on extension + p = strchr(file, '.'); + if(p != nil) + p++; + + // check the builtin extensions + if(p != nil){ + for(c = ctype; c->ext != nil; c++) + if(strcmp(p, c->ext) == 0){ + a->type = c->type; + a->ctype = c; + return a; + } + } + + // try the mime types file + if(p != nil){ + if(mimetypes == nil) + readmimetypes(); + for(c = mimetypes; c != nil && c->ext != nil; c++) + if(strcmp(p, c->ext) == 0){ + a->type = c->type; + a->ctype = c; + return a; + } + } + + // run file to figure out the type + a->type = "application/octet-stream"; // safest default + if(pipe(pfd) < 0) + return a; + switch(fork()){ + case -1: + break; + case 0: + close(pfd[1]); + close(0); + dup(pfd[0], 0); + close(1); + dup(pfd[0], 1); + execl(unsharp("#9/bin/file"), "file", "-m", file, nil); + exits(0); + default: + close(pfd[0]); + n = read(pfd[1], ftype, sizeof(ftype)); + if(n > 0){ + ftype[n-1] = 0; + a->type = estrdup(ftype); + } + close(pfd[1]); + waitpid(); + break; + } + + for(c = ctype; ; c++) + if(strncmp(a->type, c->type, strlen(c->type)) == 0){ + a->ctype = c; + break; + } + + return a; +} + +char* +mkboundary(void) +{ + char buf[32]; + int i; + + srand((time(0)<<16)|getpid()); + strcpy(buf, "upas-"); + for(i = 5; i < sizeof(buf)-1; i++) + buf[i] = 'a' + nrand(26); + buf[i] = 0; + return estrdup(buf); +} + +// copy types to two fd's +static void +tee(int in, int out1, int out2) +{ + char buf[8*1024]; + int n; + + for(;;){ + n = read(in, buf, sizeof(buf)); + if(n <= 0) + break; + if(write(out1, buf, n) < 0) + break; + if(write(out2, buf, n) < 0) + break; + } +} + +// print the unix from line +int +printunixfrom(int fd) +{ + Tm *tm; + int tz; + + tm = localtime(time(0)); + tz = (tm->tzoff/3600)*100 + ((tm->tzoff/60)%60); + + return fprint(fd, "From %s %s %s %d %2.2d:%2.2d:%2.2d %s%.4d %d\n", + user, + ascwday[tm->wday], ascmon[tm->mon], tm->mday, + tm->hour, tm->min, tm->sec, tz>=0?"+":"", tz, 1900+tm->year); +} + +char *specialfile[] = +{ + "pipeto", + "pipefrom", + "L.mbox", + "forward", + "names" +}; + +// return 1 if this is a special file +static int +special(String *s) +{ + char *p; + int i; + + p = strrchr(s_to_c(s), '/'); + if(p == nil) + p = s_to_c(s); + else + p++; + for(i = 0; i < nelem(specialfile); i++) + if(strcmp(p, specialfile[i]) == 0) + return 1; + return 0; +} + +// open the folder using the recipients account name +static int +openfolder(char *rcvr) +{ + char *p; + int c; + String *file; + Dir *d; + int fd; + int scarey; + + file = s_new(); + mboxpath("f", user, file, 0); + + // if $mail/f exists, store there, otherwise in $mail + d = dirstat(s_to_c(file)); + if(d == nil || d->qid.type != QTDIR){ + scarey = 1; + file->ptr -= 1; + } else { + s_putc(file, '/'); + scarey = 0; + } + free(d); + + p = strrchr(rcvr, '!'); + if(p != nil) + rcvr = p+1; + + while(*rcvr && *rcvr != '@'){ + c = *rcvr++; + if(c == '/') + c = '_'; + s_putc(file, c); + } + s_terminate(file); + + if(scarey && special(file)){ + fprint(2, "%s: won't overwrite %s\n", argv0, s_to_c(file)); + s_free(file); + return -1; + } + + fd = open(s_to_c(file), OWRITE); + if(fd < 0) + fd = create(s_to_c(file), OWRITE, 0660); + + s_free(file); + return fd; +} + +// start up sendmail and return an fd to talk to it with +int +sendmail(Addr *to, Addr *cc, int *pid, char *rcvr) +{ + char **av, **v; + int ac, fd; + int pfd[2]; + String *cmd; + Addr *a; + + fd = -1; + if(rcvr != nil) + fd = openfolder(rcvr); + + ac = 0; + for(a = to; a != nil; a = a->next) + ac++; + for(a = cc; a != nil; a = a->next) + ac++; + v = av = emalloc(sizeof(char*)*(ac+20)); + ac = 0; + v[ac++] = "sendmail"; + if(xflag) + v[ac++] = "-x"; + if(rflag) + v[ac++] = "-r"; + if(lbflag) + v[ac++] = "-#"; + if(dflag) + v[ac++] = "-d"; + for(a = to; a != nil; a = a->next) + v[ac++] = a->v; + for(a = cc; a != nil; a = a->next) + v[ac++] = a->v; + v[ac] = 0; + + if(pipe(pfd) < 0) + fatal("%r"); + switch(*pid = rfork(RFFDG|RFPROC)){ // jpc - removed |RFENVG|RFREND| + case -1: + fatal("%r"); + break; + case 0: + if(holding) + close(holding); + close(pfd[1]); + dup(pfd[0], 0); + close(pfd[0]); + + if(rcvr != nil){ + if(pipe(pfd) < 0) + fatal("%r"); + switch(fork()){ + case -1: + fatal("%r"); + break; + case 0: + close(pfd[0]); + seek(fd, 0, 2); + printunixfrom(fd); + tee(0, pfd[1], fd); + write(fd, "\n", 1); + exits(0); + default: + close(fd); + close(pfd[1]); + dup(pfd[0], 0); + break; + } + } + + if(replymsg != nil) + putenv("replymsg", replymsg); + + cmd = mboxpath("pipefrom", login, s_new(), 0); + exec(s_to_c(cmd), av); + exec(unsharp("#9/bin/myupassend"), av); + exec(unsharp("#9/bin/upas/send"), av); + fatal("execing: %r"); + break; + default: + if(rcvr != nil) + close(fd); + close(pfd[0]); + break; + } + return pfd[1]; +} + +// start up pgp process and return an fd to talk to it with. +// its standard output will be the original fd, which goes to sendmail. +int +pgpfilter(int *pid, int fd, int pgpflag) +{ + char **av, **v; + int ac; + int pfd[2]; + + v = av = emalloc(sizeof(char*)*8); + ac = 0; + v[ac++] = "pgp"; + if(pgpflag & PGPsign) + v[ac++] = "-s"; + if(pgpflag & PGPencrypt) + v[ac++] = "-e"; + v[ac] = 0; + + if(pipe(pfd) < 0) + fatal("%r"); + switch(*pid = fork()){ + case -1: + fatal("%r"); + break; + case 0: + close(pfd[1]); + dup(pfd[0], 0); + close(pfd[0]); + dup(fd, 1); + close(fd); + + exec("/bin/upas/pgp", av); + fatal("execing: %r"); + break; + default: + close(pfd[0]); + break; + } + close(fd); + return pfd[1]; +} + +// wait for sendmail and pgp to exit; exit here if either failed +char* +waitforsubprocs(void) +{ + Waitmsg *w; + char *err; + + err = nil; + while((w = wait()) != nil){ + if(w->pid == pid || w->pid == pgppid){ + if(w->msg[0] != 0) + err = estrdup(w->msg); + } + free(w); + } + if(err) + exits(err); + return nil; +} + +int +cistrncmp(char *a, char *b, int n) +{ + while(n-- > 0){ + if(tolower(*a++) != tolower(*b++)) + return -1; + } + return 0; +} + +int +cistrcmp(char *a, char *b) +{ + for(;;){ + if(tolower(*a) != tolower(*b++)) + return -1; + if(*a++ == 0) + break; + } + return 0; +} + +static uchar t64d[256]; +static char t64e[64]; + +static void +init64(void) +{ + int c, i; + + memset(t64d, 255, 256); + memset(t64e, '=', 64); + i = 0; + for(c = 'A'; c <= 'Z'; c++){ + t64e[i] = c; + t64d[c] = i++; + } + for(c = 'a'; c <= 'z'; c++){ + t64e[i] = c; + t64d[c] = i++; + } + for(c = '0'; c <= '9'; c++){ + t64e[i] = c; + t64d[c] = i++; + } + t64e[i] = '+'; + t64d['+'] = i++; + t64e[i] = '/'; + t64d['/'] = i; +} + +int +enc64(char *out, int lim, uchar *in, int n) +{ + int i; + ulong b24; + char *start = out; + char *e = out + lim; + + if(t64e[0] == 0) + init64(); + for(i = 0; i < n/3; i++){ + b24 = (*in++)<<16; + b24 |= (*in++)<<8; + b24 |= *in++; + if(out + 5 >= e) + goto exhausted; + *out++ = t64e[(b24>>18)]; + *out++ = t64e[(b24>>12)&0x3f]; + *out++ = t64e[(b24>>6)&0x3f]; + *out++ = t64e[(b24)&0x3f]; + if((i%18) == 17) + *out++ = '\n'; + } + + switch(n%3){ + case 2: + b24 = (*in++)<<16; + b24 |= (*in)<<8; + if(out + 4 >= e) + goto exhausted; + *out++ = t64e[(b24>>18)]; + *out++ = t64e[(b24>>12)&0x3f]; + *out++ = t64e[(b24>>6)&0x3f]; + break; + case 1: + b24 = (*in)<<16; + if(out + 4 >= e) + goto exhausted; + *out++ = t64e[(b24>>18)]; + *out++ = t64e[(b24>>12)&0x3f]; + *out++ = '='; + break; + case 0: + if((i%18) != 0) + *out++ = '\n'; + *out = 0; + return out - start; + } +exhausted: + *out++ = '='; + *out++ = '\n'; + *out = 0; + return out - start; +} + +void +freealias(Alias *a) +{ + freeaddrs(a->addr); + free(a); +} + +void +freealiases(Alias *a) +{ + Alias *next; + + while(a != nil){ + next = a->next; + freealias(a); + a = next; + } +} + +// +// read alias file +// +Alias* +readaliases(void) +{ + Alias *a, **l, *first; + Addr *addr, **al; + String *file, *line, *token; + // jpc - static int already; + Sinstack *sp; + + first = nil; + file = s_new(); + line = s_new(); + token = s_new(); + + // open and get length + mboxpath("names", login, file, 0); + sp = s_allocinstack(s_to_c(file)); + if(sp == nil) + goto out; + + l = &first; + + // read a line at a time. + while(s_rdinstack(sp, s_restart(line))!=nil) { + s_restart(line); + a = emalloc(sizeof(Alias)); + al = &a->addr; + for(;;){ + if(s_parse(line, s_restart(token))==0) + break; + addr = emalloc(sizeof(Addr)); + addr->v = strdup(s_to_c(token)); + addr->next = 0; + *al = addr; + al = &addr->next; + } + if(a->addr == nil || a->addr->next == nil){ + freealias(a); + continue; + } + a->next = nil; + *l = a; + l = &a->next; + } + s_freeinstack(sp); + +out: + s_free(file); + s_free(line); + s_free(token); + return first; +} + +Addr* +newaddr(char *name) +{ + Addr *a; + + a = emalloc(sizeof(*a)); + a->next = nil; + a->v = estrdup(name); + if(a->v == nil) + sysfatal("%r"); + return a; +} + +// +// expand personal aliases since the names are meaningless in +// other contexts +// +Addr* +_expand(Addr *old, int *changedp) +{ + Alias *al; + Addr *first, *next, **l, *a; + + *changedp = 0; + first = nil; + l = &first; + for(;old != nil; old = next){ + next = old->next; + for(al = aliases; al != nil; al = al->next){ + if(strcmp(al->addr->v, old->v) == 0){ + for(a = al->addr->next; a != nil; a = a->next){ + *l = newaddr(a->v); + if(*l == nil) + sysfatal("%r"); + l = &(*l)->next; + *changedp = 1; + } + break; + } + } + if(al != nil){ + freeaddr(old); + continue; + } + *l = old; + old->next = nil; + l = &(*l)->next; + } + return first; +} + +Addr* +rexpand(Addr *old) +{ + int i, changed; + + changed = 0; + for(i=0; i<32; i++){ + old = _expand(old, &changed); + if(changed == 0) + break; + } + return old; +} + +Addr* +unique(Addr *first) +{ + Addr *a, **l, *x; + + for(a = first; a != nil; a = a->next){ + for(l = &a->next; *l != nil;){ + if(strcmp(a->v, (*l)->v) == 0){ + x = *l; + *l = x->next; + freeaddr(x); + } else + l = &(*l)->next; + } + } + return first; +} + +Addr* +expand(int ac, char **av) +{ + Addr *first, **l; + int i; + + first = nil; + + // make a list of the starting addresses + l = &first; + for(i = 0; i < ac; i++){ + *l = newaddr(av[i]); + if(*l == nil) + sysfatal("%r"); + l = &(*l)->next; + } + + // recurse till we don't change any more + return unique(rexpand(first)); +} + +Addr* +concataddr(Addr *a, Addr *b) +{ + Addr *oa; + + if(a == nil) + return b; + + oa = a; + for(; a->next; a=a->next) + ; + a->next = b; + return oa; +} + +void +freeaddr(Addr *ap) +{ + free(ap->v); + free(ap); +} + +void +freeaddrs(Addr *ap) +{ + Addr *next; + + for(; ap; ap=next) { + next = ap->next; + freeaddr(ap); + } +} + +String* +s_copyn(char *s, int n) +{ + return s_nappend(s_reset(nil), s, n); +} + +// fetch the next token from an RFC822 address string +// we assume the header is RFC822-conformant in that +// we recognize escaping anywhere even though it is only +// supposed to be in quoted-strings, domain-literals, and comments. +// +// i'd use yylex or yyparse here, but we need to preserve +// things like comments, which i think it tosses away. +// +// we're not strictly RFC822 compliant. we misparse such nonsense as +// +// To: gre @ (Grace) plan9 . (Emlin) bell-labs.com +// +// make sure there's no whitespace in your addresses and +// you'll be fine. +// +enum { + Twhite, + Tcomment, + Twords, + Tcomma, + Tleftangle, + Trightangle, + Terror, + Tend, +}; +//char *ty82[] = {"white", "comment", "words", "comma", "<", ">", "err", "end"}; +#define ISWHITE(p) ((p)==' ' || (p)=='\t' || (p)=='\n' || (p)=='\r') +int +get822token(String **tok, char *p, char **pp) +{ + char *op; + int type; + int quoting; + + op = p; + switch(*p){ + case '\0': + *tok = nil; + *pp = nil; + return Tend; + + case ' ': // get whitespace + case '\t': + case '\n': + case '\r': + type = Twhite; + while(ISWHITE(*p)) + p++; + break; + + case '(': // get comment + type = Tcomment; + for(p++; *p && *p != ')'; p++) + if(*p == '\\') { + if(*(p+1) == '\0') { + *tok = nil; + return Terror; + } + p++; + } + + if(*p != ')') { + *tok = nil; + return Terror; + } + p++; + break; + case ',': + type = Tcomma; + p++; + break; + case '<': + type = Tleftangle; + p++; + break; + case '>': + type = Trightangle; + p++; + break; + default: // bunch of letters, perhaps quoted strings tossed in + type = Twords; + quoting = 0; + for(; *p && (quoting || (!ISWHITE(*p) && *p != '>' && *p != '<' && *p != ',')); p++) { + if(*p == '"') + quoting = !quoting; + if(*p == '\\') { + if(*(p+1) == '\0') { + *tok = nil; + return Terror; + } + p++; + } + } + break; + } + + if(pp) + *pp = p; + *tok = s_copyn(op, p-op); + return type; +} + +// expand local aliases in an RFC822 mail line +// add list of expanded addresses to to. +Addr* +expandline(String **s, Addr *to) +{ + Addr *na, *nto, *ap; + char *p; + int tok, inangle, hadangle, nword; + String *os, *ns, *stok, *lastword, *sinceword; + + os = s_copy(s_to_c(*s)); + p = strchr(s_to_c(*s), ':'); + assert(p != nil); + p++; + + ns = s_copyn(s_to_c(*s), p-s_to_c(*s)); + stok = nil; + nto = nil; + // + // the only valid mailbox namings are word + // and word* < addr > + // without comments this would be simple. + // we keep the following: + // lastword - current guess at the address + // sinceword - whitespace and comment seen since lastword + // + lastword = s_new(); + sinceword = s_new(); + inangle = 0; + nword = 0; + hadangle = 0; + for(;;) { + stok = nil; + switch(tok = get822token(&stok, p, &p)){ + default: + abort(); + case Tcomma: + case Tend: + if(inangle) + goto Error; + if(nword != 1) + goto Error; + na = rexpand(newaddr(s_to_c(lastword))); + s_append(ns, na->v); + s_append(ns, s_to_c(sinceword)); + for(ap=na->next; ap; ap=ap->next) { + s_append(ns, ", "); + s_append(ns, ap->v); + } + nto = concataddr(na, nto); + if(tok == Tcomma){ + s_append(ns, ","); + s_free(stok); + } + if(tok == Tend) + goto Break2; + inangle = 0; + nword = 0; + hadangle = 0; + s_reset(sinceword); + s_reset(lastword); + break; + case Twhite: + case Tcomment: + s_append(sinceword, s_to_c(stok)); + s_free(stok); + break; + case Trightangle: + if(!inangle) + goto Error; + inangle = 0; + hadangle = 1; + s_append(sinceword, s_to_c(stok)); + s_free(stok); + break; + case Twords: + case Tleftangle: + if(hadangle) + goto Error; + if(tok != Tleftangle && inangle && s_len(lastword)) + goto Error; + if(tok == Tleftangle) { + inangle = 1; + nword = 1; + } + s_append(ns, s_to_c(lastword)); + s_append(ns, s_to_c(sinceword)); + s_reset(sinceword); + if(tok == Tleftangle) { + s_append(ns, "<"); + s_reset(lastword); + } else { + s_free(lastword); + lastword = stok; + } + if(!inangle) + nword++; + break; + case Terror: // give up, use old string, addrs + Error: + ns = os; + os = nil; + freeaddrs(nto); + nto = nil; + werrstr("rfc822 syntax error"); + rfc822syntaxerror = 1; + goto Break2; + } + } +Break2: + s_free(*s); + s_free(os); + *s = ns; + nto = concataddr(nto, to); + return nto; +} + +void +Bdrain(Biobuf *b) +{ + char buf[8192]; + + while(Bread(b, buf, sizeof buf) > 0) + ; +} + +void +readmimetypes(void) +{ + Biobuf *b; + char *p; + char *f[6]; + char type[256]; + static int alloced, inuse; + + if(mimetypes == 0){ + alloced = 256; + mimetypes = emalloc(alloced*sizeof(Ctype)); + mimetypes[0].ext = ""; + } + + b = Bopen(unsharp("#9/sys/lib/mimetype"), OREAD); + if(b == nil) + return; + for(;;){ + p = Brdline(b, '\n'); + if(p == nil) + break; + p[Blinelen(b)-1] = 0; + if(tokenize(p, f, 6) < 4) + continue; + if(strcmp(f[0], "-") == 0 || strcmp(f[1], "-") == 0 || strcmp(f[2], "-") == 0) + continue; + if(inuse + 1 >= alloced){ + alloced += 256; + mimetypes = erealloc(mimetypes, alloced*sizeof(Ctype)); + } + snprint(type, sizeof(type), "%s/%s", f[1], f[2]); + mimetypes[inuse].type = estrdup(type); + mimetypes[inuse].ext = estrdup(f[0]+1); + mimetypes[inuse].display = !strcmp(type, "text/plain"); + inuse++; + + // always make sure there's a terminator + mimetypes[inuse].ext = 0; + } + Bterm(b); +} + +char* +estrdup(char *x) +{ + x = strdup(x); + if(x == nil) + fatal("memory"); + return x; +} + +void* +emalloc(int n) +{ + void *x; + + x = malloc(n); + if(x == nil) + fatal("%r"); + return x; +} + +void* +erealloc(void *x, int n) +{ + x = realloc(x, n); + if(x == nil) + fatal("%r"); + return x; +} + +// +// 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, '"'); +} blob - /dev/null blob + bf446476d61ce46fe61dd8f60729d769beb96e2a (mode 644) --- /dev/null +++ src/cmd/upas/marshal/mkfile @@ -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 + blob - /dev/null blob + a304271c07ab0c34d298286429d2ea1b5c65f580 (mode 644) --- /dev/null +++ src/cmd/upas/misc/gone.fishing @@ -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 +} blob - /dev/null blob + 9eb1e5a6bba1b2c3bbdf8f066b50becce3d24c68 (mode 644) --- /dev/null +++ src/cmd/upas/misc/gone.msg @@ -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. blob - /dev/null blob + 20cf239706faa1ceb05f4db5d9b1220c5412f6cc (mode 644) --- /dev/null +++ src/cmd/upas/misc/mail.c @@ -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); +} + blob - /dev/null blob + 6913d743e1a9b52044800494e6d15e8d8518240f (mode 755) --- /dev/null +++ src/cmd/upas/misc/mail.rc @@ -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 $* +} blob - /dev/null blob + a41519aefa7e8826de8ee6f7b0a4f85c25a0915b (mode 644) --- /dev/null +++ src/cmd/upas/misc/mail.sh @@ -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 blob - /dev/null blob + e00c4f7e8f13e0b8f2d592ae134bc73e302fbb04 (mode 644) --- /dev/null +++ src/cmd/upas/misc/makefile @@ -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 + blob - /dev/null blob + 0bbd8a5ab355647dab620b7060a3834d95b782b9 (mode 644) --- /dev/null +++ src/cmd/upas/misc/mkfile @@ -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 blob - /dev/null blob + ab3853b1c2d15052b787b167d986d00e9fb75355 (mode 644) --- /dev/null +++ src/cmd/upas/misc/namefiles @@ -0,0 +1,2 @@ +names.local +names.global blob - /dev/null blob + ed64f38c439ca03cbf5773d05b430a46c3d288bf (mode 755) --- /dev/null +++ src/cmd/upas/misc/omail.rc @@ -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 $* +} blob - /dev/null blob + 77a3d228ad1413f28264fb31486354360ab7c1b1 (mode 755) --- /dev/null +++ src/cmd/upas/misc/qmail @@ -0,0 +1,6 @@ +#!/bin/rc +sender=$1 +shift +addr=$1 +shift +qer /mail/queue mail $sender $addr $* && runq /mail/queue /mail/lib/remotemail blob - /dev/null blob + 312ee58748d80bde6e815aab91935cdf4417ee03 (mode 755) --- /dev/null +++ src/cmd/upas/misc/remotemail @@ -0,0 +1,7 @@ +#!/bin/rc +shift +sender=$1 +shift +addr=$1 +shift +/bin/upas/smtp -g research.research.bell-labs.com $addr $sender $* blob - /dev/null blob + fd724eb1e76497bba88f7f448d4c5b7da379e864 (mode 644) --- /dev/null +++ src/cmd/upas/misc/rewrite @@ -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'" blob - /dev/null blob + 307a49255d9be40263571c98a790872bf2875e21 (mode 644) --- /dev/null +++ src/cmd/upas/ml/common.c @@ -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; + } +} blob - /dev/null blob + 3527af508d3c1d995cc8aa29fc8d4490d06f3b37 (mode 644) --- /dev/null +++ src/cmd/upas/ml/dat.h @@ -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); blob - /dev/null blob + 5142e56f45d3dd97bee0646cc3992839f0596585 (mode 644) --- /dev/null +++ src/cmd/upas/ml/mkfile @@ -0,0 +1,40 @@ +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); +} blob - /dev/null blob + a1d1b907e9d4233fe36355327fa3fabfe16fa936 (mode 644) --- /dev/null +++ src/cmd/upas/ml/mlmgr.c @@ -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; +} blob - /dev/null blob + 5bb98a298fdc03cc921558cace2fb1fd456f8b36 (mode 644) --- /dev/null +++ src/cmd/upas/ml/mlowner.c @@ -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); +} blob - /dev/null blob + 82025558dd41fff37b8ac46693e7e9b92f314b26 (mode 644) --- /dev/null +++ src/cmd/upas/ned/mkfile @@ -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 + blob - /dev/null blob + 4fa7a88f8b29e568e0dd49fc8ee5458d7931745d (mode 644) --- /dev/null +++ src/cmd/upas/ned/nedmail.c @@ -0,0 +1,2586 @@ +#include "common.h" +#include +#include +#include <9pclient.h> +#include + +typedef struct Message Message; +typedef struct Ctype Ctype; +typedef struct Cmd Cmd; + +char root[Pathlen]; +char mbname[Elemlen]; +int rootlen; +int didopen; +char *user; +char wd[2048]; +String *mbpath; +int natural; +int doflush; + +int interrupted; + +struct Message { + Message *next; + Message *prev; + Message *cmd; + Message *child; + Message *parent; + String *path; + int id; + int len; + int fileno; // number of directory + String *info; + char *from; + char *to; + char *cc; + char *replyto; + char *date; + char *subject; + char *type; + char *disposition; + char *filename; + char deleted; + char stored; +}; + +Message top; + +struct Ctype { + char *type; + char *ext; + int display; + char *plumbdest; + Ctype *next; +}; + +Ctype ctype[] = { + { "text/plain", "txt", 1, 0 }, + { "text/html", "htm", 1, 0 }, + { "text/html", "html", 1, 0 }, + { "text/tab-separated-values", "tsv", 1, 0 }, + { "text/richtext", "rtx", 1, 0 }, + { "text/rtf", "rtf", 1, 0 }, + { "text", "txt", 1, 0 }, + { "message/rfc822", "msg", 0, 0 }, + { "image/bmp", "bmp", 0, "image" }, + { "image/jpeg", "jpg", 0, "image" }, + { "image/gif", "gif", 0, "image" }, + { "application/pdf", "pdf", 0, "postscript" }, + { "application/postscript", "ps", 0, "postscript" }, + { "application/", 0, 0, 0 }, + { "image/", 0, 0, 0 }, + { "multipart/", "mul", 0, 0 }, + +}; + +Message* acmd(Cmd*, Message*); +Message* bcmd(Cmd*, Message*); +Message* dcmd(Cmd*, Message*); +Message* eqcmd(Cmd*, Message*); +Message* hcmd(Cmd*, Message*); +Message* Hcmd(Cmd*, Message*); +Message* helpcmd(Cmd*, Message*); +Message* icmd(Cmd*, Message*); +Message* pcmd(Cmd*, Message*); +Message* qcmd(Cmd*, Message*); +Message* rcmd(Cmd*, Message*); +Message* scmd(Cmd*, Message*); +Message* ucmd(Cmd*, Message*); +Message* wcmd(Cmd*, Message*); +Message* xcmd(Cmd*, Message*); +Message* ycmd(Cmd*, Message*); +Message* pipecmd(Cmd*, Message*); +Message* rpipecmd(Cmd*, Message*); +Message* bangcmd(Cmd*, Message*); +Message* Pcmd(Cmd*, Message*); +Message* mcmd(Cmd*, Message*); +Message* fcmd(Cmd*, Message*); +Message* quotecmd(Cmd*, Message*); + +struct { + char *cmd; + int args; + Message* (*f)(Cmd*, Message*); + char *help; +} cmdtab[] = { + { "a", 1, acmd, "a reply to sender and recipients" }, + { "A", 1, acmd, "A reply to sender and recipients with copy" }, + { "b", 0, bcmd, "b print the next 10 headers" }, + { "d", 0, dcmd, "d mark for deletion" }, + { "f", 0, fcmd, "f file message by from address" }, + { "h", 0, hcmd, "h print elided message summary (,h for all)" }, + { "help", 0, helpcmd, "help print this info" }, + { "H", 0, Hcmd, "H print message's MIME structure " }, + { "i", 0, icmd, "i incorporate new mail" }, + { "m", 1, mcmd, "m addr forward mail" }, + { "M", 1, mcmd, "M addr forward mail with message" }, + { "p", 0, pcmd, "p print the processed message" }, + { "P", 0, Pcmd, "P print the raw message" }, + { "\"", 0, quotecmd, "\" print a quoted version of msg" }, + { "q", 0, qcmd, "q exit and remove all deleted mail" }, + { "r", 1, rcmd, "r [addr] reply to sender plus any addrs specified" }, + { "rf", 1, rcmd, "rf [addr]file message and reply" }, + { "R", 1, rcmd, "R [addr] reply including copy of message" }, + { "Rf", 1, rcmd, "Rf [addr]file message and reply with copy" }, + { "s", 1, scmd, "s file append raw message to file" }, + { "u", 0, ucmd, "u remove deletion mark" }, + { "w", 1, wcmd, "w file store message contents as file" }, + { "x", 0, xcmd, "x exit without flushing deleted messages" }, + { "y", 0, ycmd, "y synchronize with mail box" }, + { "=", 1, eqcmd, "= print current message number" }, + { "|", 1, pipecmd, "|cmd pipe message body to a command" }, + { "||", 1, rpipecmd, "||cmd pipe raw message to a command" }, + { "!", 1, bangcmd, "!cmd run a command" }, + { nil, 0, nil, nil }, +}; + +enum +{ + NARG= 32, +}; + +struct Cmd { + Message *msgs; + Message *(*f)(Cmd*, Message*); + int an; + char *av[NARG]; + int delete; +}; + +Biobuf out; +int startedfs; +int reverse; +int longestfrom = 12; + +String* file2string(String*, char*); +int dir2message(Message*, int); +int filelen(String*, char*); +String* extendpath(String*, char*); +void snprintheader(char*, int, Message*); +void cracktime(char*, char*, int); +int cistrncmp(char*, char*, int); +int cistrcmp(char*, char*); +Reprog* parsesearch(char**); +char* parseaddr(char**, Message*, Message*, Message*, Message**); +char* parsecmd(char*, Cmd*, Message*, Message*); +char* readline(char*, char*, int); +void messagecount(Message*); +void system9(char*, char**, int); +void mkid(String*, Message*); +int switchmb(char*, char*); +void closemb(void); +int lineize(char*, char**, int); +int rawsearch(Message*, Reprog*); +Message* dosingleton(Message*, char*); +String* rooted(String*); +int plumb(Message*, Ctype*); +String* addrecolon(char*); +void exitfs(char*); +Message* flushdeleted(Message*); + +CFsys *upasfs; + +void +usage(void) +{ + fprint(2, "usage: %s [-nr] [-f mailfile] [-s mailfile]\n", argv0); + fprint(2, " %s -c dir\n", argv0); + threadexits("usage"); +} + +void +catchnote(void* dummy, char *note) +{ + if(strstr(note, "interrupt") != nil){ + interrupted = 1; + noted(NCONT); + } + noted(NDFLT); +} + +char * +plural(int n) +{ + if (n == 1) + return ""; + + return "s"; +} + +void +threadmain(int argc, char **argv) +{ + Message *cur, *m, *x; + char cmdline[4*1024]; + Cmd cmd; + Ctype *cp; + char *err; + int n, cflag; + char *av[4]; + String *prompt; + char *file, *singleton; + char *fscmd; + + Binit(&out, 1, OWRITE); + + file = nil; + singleton = nil; + reverse = 1; + cflag = 0; + ARGBEGIN { + case 'c': + cflag = 1; + break; + case 'f': + file = EARGF(usage()); + break; + case 's': + singleton = EARGF(usage()); + break; + case 'r': + reverse = 0; + break; + case 'n': + natural = 1; + reverse = 0; + break; + default: + usage(); + break; + } ARGEND; + + user = getlog(); + if(user == nil || *user == 0) + sysfatal("can't read user name"); + + if(cflag){ + if(argc > 0) + creatembox(user, argv[0]); + else + creatembox(user, nil); + threadexits(0); + } + + if(argc) + usage(); + +#if 0 /* jpc */ + if(access("/mail/fs/ctl", 0) < 0){ + startedfs = 1; + av[0] = "fs"; + av[1] = "-p"; + av[2] = 0; + system9("/bin/upas/fs", av, -1); + } +#endif + if( (upasfs = nsmount("upasfs", nil)) == nil ) { + startedfs = 1; + av[0] = "fs"; + av[1] = "-p"; + av[2] = 0; + fscmd = unsharp("#9/bin/upas/fs"); + system9(fscmd, av, -1); + } + +fprint(2,"switchmb\n"); + switchmb(file, singleton); +fprint(2,"switchmb2\n"); + + top.path = s_copy(root); + + for(cp = ctype; cp < ctype+nelem(ctype)-1; cp++) + cp->next = cp+1; + + if(singleton != nil){ + cur = dosingleton(&top, singleton); + if(cur == nil){ + Bprint(&out, "no message\n"); + exitfs(0); + } + pcmd(nil, cur); + } else { + cur = ⊤ + n = dir2message(&top, reverse); + if(n < 0) + sysfatal("can't read %s", s_to_c(top.path)); + Bprint(&out, "%d message%s\n", n, plural(n)); + } + + + notify(catchnote); + prompt = s_new(); + for(;;){ + s_reset(prompt); + if(cur == &top) + s_append(prompt, ": "); + else { + mkid(prompt, cur); + s_append(prompt, ": "); + } + + // leave space at the end of cmd line in case parsecmd needs to + // add a space after a '|' or '!' + if(readline(s_to_c(prompt), cmdline, sizeof(cmdline)-1) == nil) + break; + err = parsecmd(cmdline, &cmd, top.child, cur); + if(err != nil){ + Bprint(&out, "!%s\n", err); + continue; + } + if(singleton != nil && cmd.f == icmd){ + Bprint(&out, "!illegal command\n"); + continue; + } + interrupted = 0; + if(cmd.msgs == nil || cmd.msgs == &top){ + x = (*cmd.f)(&cmd, &top); + if(x != nil) + cur = x; + } else for(m = cmd.msgs; m != nil; m = m->cmd){ + x = m; + if(cmd.delete){ + dcmd(&cmd, x); + + // dp acts differently than all other commands + // since its an old lesk idiom that people love. + // it deletes the current message, moves the current + // pointer ahead one and prints. + if(cmd.f == pcmd){ + if(x->next == nil){ + Bprint(&out, "!address\n"); + cur = x; + break; + } else + x = x->next; + } + } + x = (*cmd.f)(&cmd, x); + if(x != nil) + cur = x; + if(interrupted) + break; + if(singleton != nil && (cmd.delete || cmd.f == dcmd)) + qcmd(nil, nil); + } + if(doflush) + cur = flushdeleted(cur); + } + qcmd(nil, nil); +} + +// +// read the message info +// +Message* +file2message(Message *parent, char *name) +{ + Message *m; + String *path; + char *f[10]; + + m = mallocz(sizeof(Message), 1); + if(m == nil) + return nil; + m->path = path = extendpath(parent->path, name); + m->fileno = atoi(name); + m->info = file2string(path, "info"); + lineize(s_to_c(m->info), f, nelem(f)); + m->from = f[0]; + m->to = f[1]; + m->cc = f[2]; + m->replyto = f[3]; + m->date = f[4]; + m->subject = f[5]; + m->type = f[6]; + m->disposition = f[7]; + m->filename = f[8]; + m->len = filelen(path, "raw"); + if(strstr(m->type, "multipart") != nil || strcmp(m->type, "message/rfc822") == 0) + dir2message(m, 0); + m->parent = parent; + + return m; +} + +void +freemessage(Message *m) +{ + Message *nm, *next; + + for(nm = m->child; nm != nil; nm = next){ + next = nm->next; + freemessage(nm); + } + s_free(m->path); + s_free(m->info); + free(m); +} + +// +// read a directory into a list of messages +// +int +dir2message(Message *parent, int reverse) +{ +//jpc int i, n, fd, highest, newmsgs; + int i, n, highest, newmsgs; + CFid *fid; + + Dir *d; + Message *first, *last, *m; + +/* fd = open(s_to_c(parent->path), OREAD); + if(fd < 0) + return -1; jpc */ + fid = fsopen(upasfs, s_to_c(parent->path), OREAD); + if(fid == nil) + return -1; + + // count current entries + first = parent->child; + highest = newmsgs = 0; + for(last = parent->child; last != nil && last->next != nil; last = last->next) + if(last->fileno > highest) + highest = last->fileno; + if(last != nil) + if(last->fileno > highest) + highest = last->fileno; + + n = fsdirreadall(fid, &d); + fprint(2,"read %d messages\n", n); + for(i = 0; i < n; i++){ + if((d[i].qid.type & QTDIR) == 0) + continue; + if(atoi(d[i].name) <= highest) + continue; + fprint(2,"calling file2message %d\n", i); + m = file2message(parent, d[i].name); + // fprint(2,"returned from file2message\n"); + if(m == nil) + break; + newmsgs++; + if(reverse){ + m->next = first; + if(first != nil) + first->prev = m; + first = m; + } else { + if(first == nil) + first = m; + else + last->next = m; + m->prev = last; + last = m; + } + } + fprint(2,"exiting loop\n"); + free(d); + fprint(2,"close fid\n"); + fsclose(fid); + fprint(2,"fid closed\n"); + parent->child = first; + + // renumber and file longest from + i = 1; + longestfrom = 12; + for(m = first; m != nil; m = m->next){ + fprint(2,"m:%x from: %s\n", m, m->from); + m->id = natural ? m->fileno : i++; + n = strlen(m->from); + fprint(2,"in loop\n"); + if(n > longestfrom) + longestfrom = n; + } + fprint(2,"exiting dir2message\n"); + + return newmsgs; +} + +// +// point directly to a message +// +Message* +dosingleton(Message *parent, char *path) +{ + char *p, *np; + Message *m; + + // walk down to message and read it + if(strlen(path) < rootlen) + return nil; + if(path[rootlen] != '/') + return nil; + p = path+rootlen+1; + np = strchr(p, '/'); + if(np != nil) + *np = 0; + m = file2message(parent, p); + if(m == nil) + return nil; + parent->child = m; + m->id = 1; + + // walk down to requested component + while(np != nil){ + *np = '/'; + np = strchr(np+1, '/'); + if(np != nil) + *np = 0; + for(m = m->child; m != nil; m = m->next) + if(strcmp(path, s_to_c(m->path)) == 0) + return m; + if(m == nil) + return nil; + } + return m; +} + +// +// read a file into a string +// +String* +file2string(String *dir, char *file) +{ + String *s; +//jpc int fd, n, m; + int n, m; + CFid *fid; + + s = extendpath(dir, file); +// jpc fd = open(s_to_c(s), OREAD); + fid = fsopen(upasfs,s_to_c(s), OREAD); + s_grow(s, 512); /* avoid multiple reads on info files */ + s_reset(s); +//jpc if(fd < 0) + if(fid == nil) + return s; + + for(;;){ + n = s->end - s->ptr; + if(n == 0){ + s_grow(s, 128); + continue; + } +//jpc m = read(fd, s->ptr, n); + m = fsread(fid, s->ptr, n); + if(m <= 0) + break; + s->ptr += m; + if(m < n) + break; + } + s_terminate(s); +//jpc close(fd); + fsclose(fid); + + return s; +} + +// +// get the length of a file +// +int +filelen(String *dir, char *file) +{ + String *path; + Dir *d; + int rv; + + path = extendpath(dir, file); +//jpc d = dirstat(s_to_c(path)); + d = fsdirstat(upasfs,s_to_c(path)); + if(d == nil){ + s_free(path); + return -1; + } + s_free(path); + rv = d->length; + free(d); + return rv; +} + +// +// walk the path name an element +// +String* +extendpath(String *dir, char *name) +{ + String *path; + + if(strcmp(s_to_c(dir), ".") == 0) + path = s_new(); + else { + path = s_copy(s_to_c(dir)); + s_append(path, "/"); + } + s_append(path, name); + return path; +} + +int +cistrncmp(char *a, char *b, int n) +{ + while(n-- > 0){ + if(tolower(*a++) != tolower(*b++)) + return -1; + } + return 0; +} + +int +cistrcmp(char *a, char *b) +{ + for(;;){ + if(tolower(*a) != tolower(*b++)) + return -1; + if(*a++ == 0) + break; + } + return 0; +} + +char* +nosecs(char *t) +{ + char *p; + + p = strchr(t, ':'); + if(p == nil) + return t; + p = strchr(p+1, ':'); + if(p != nil) + *p = 0; + return t; +} + +char *months[12] = +{ + "jan", "feb", "mar", "apr", "may", "jun", + "jul", "aug", "sep", "oct", "nov", "dec" +}; + +int +month(char *m) +{ + int i; + + for(i = 0; i < 12; i++) + if(cistrcmp(m, months[i]) == 0) + return i+1; + return 1; +} + +enum +{ + Yearsecs= 365*24*60*60 +}; + +void +cracktime(char *d, char *out, int len) +{ + char in[64]; + char *f[6]; + int n; + Tm tm; + long now, then; + char *dtime; + + *out = 0; + if(d == nil) + return; + strncpy(in, d, sizeof(in)); + in[sizeof(in)-1] = 0; + n = getfields(in, f, 6, 1, " \t\r\n"); + if(n != 6){ + // unknown style + snprint(out, 16, "%10.10s", d); + return; + } + now = time(0); + memset(&tm, 0, sizeof tm); + if(strchr(f[0], ',') != nil && strchr(f[4], ':') != nil){ + // 822 style + tm.year = atoi(f[3])-1900; + tm.mon = month(f[2]); + tm.mday = atoi(f[1]); + dtime = nosecs(f[4]); + then = tm2sec(&tm); + } else if(strchr(f[3], ':') != nil){ + // unix style + tm.year = atoi(f[5])-1900; + tm.mon = month(f[1]); + tm.mday = atoi(f[2]); + dtime = nosecs(f[3]); + then = tm2sec(&tm); + } else { + then = now; + tm = *localtime(now); + dtime = ""; + } + + if(now - then < Yearsecs/2) + snprint(out, len, "%2d/%2.2d %s", tm.mon, tm.mday, dtime); + else + snprint(out, len, "%2d/%2.2d %4d", tm.mon, tm.mday, tm.year+1900); +} + +Ctype* +findctype(Message *m) +{ + char *p; + char ftype[128]; + int n, pfd[2]; + Ctype *a, *cp; + /* static Ctype nulltype = { "", 0, 0, 0 }; jpc */ + static Ctype bintype = { "application/octet-stream", "bin", 0, 0 }; + + for(cp = ctype; cp; cp = cp->next) + if(strncmp(cp->type, m->type, strlen(cp->type)) == 0) + return cp; + +/* use file(1) for any unknown mimetypes + * + * if (strcmp(m->type, bintype.type) != 0) + * return &nulltype; + */ + if(pipe(pfd) < 0) + return &bintype; + + *ftype = 0; + switch(fork()){ + case -1: + break; + case 0: + close(pfd[1]); + close(0); + dup(pfd[0], 0); + close(1); + dup(pfd[0], 1); +//jpc execl("/bin/file", "file", "-m", s_to_c(extendpath(m->path, "body")), nil); + execl(unsharp("#9/bin/file"), "file", "-m", s_to_c(extendpath(m->path, "body")), nil); + threadexits(0); + default: + close(pfd[0]); + n = read(pfd[1], ftype, sizeof(ftype)); + if(n > 0) + ftype[n] = 0; + close(pfd[1]); + waitpid(); + break; + } + + if (*ftype=='\0' || (p = strchr(ftype, '/')) == nil) + return &bintype; + *p++ = 0; + + a = mallocz(sizeof(Ctype), 1); + a->type = strdup(ftype); + a->ext = strdup(p); + a->display = 0; + a->plumbdest = strdup(ftype); + for(cp = ctype; cp->next; cp = cp->next) + continue; + cp->next = a; + a->next = nil; + return a; +} + +void +mkid(String *s, Message *m) +{ + char buf[32]; + + if(m->parent != &top){ + mkid(s, m->parent); + s_append(s, "."); + } + sprint(buf, "%d", m->id); + s_append(s, buf); +} + +void +snprintheader(char *buf, int len, Message *m) +{ + char timebuf[32]; + String *id; + char *p, *q;; + + // create id + id = s_new(); + mkid(id, m); + + if(*m->from == 0){ + // no from + snprint(buf, len, "%-3s %s %6d %s", + s_to_c(id), + m->type, + m->len, + m->filename); + } else if(*m->subject){ + q = p = strdup(m->subject); + while(*p == ' ') + p++; + if(strlen(p) > 50) + p[50] = 0; + cracktime(m->date, timebuf, sizeof(timebuf)); + snprint(buf, len, "%-3s %c%c%c %6d %11.11s %-*.*s %s", + s_to_c(id), + m->child ? 'H' : ' ', + m->deleted ? 'd' : ' ', + m->stored ? 's' : ' ', + m->len, + timebuf, + longestfrom, longestfrom, m->from, + p); + free(q); + } else { + cracktime(m->date, timebuf, sizeof(timebuf)); + snprint(buf, len, "%-3s %c%c%c %6d %11.11s %s", + s_to_c(id), + m->child ? 'H' : ' ', + m->deleted ? 'd' : ' ', + m->stored ? 's' : ' ', + m->len, + timebuf, + m->from); + } + s_free(id); +} + +char *spaces = " "; + +void +snprintHeader(char *buf, int len, int indent, Message *m) +{ + String *id; + char typeid[64]; + char *p, *e; + + // create id + id = s_new(); + mkid(id, m); + + e = buf + len; + + snprint(typeid, sizeof typeid, "%s %s", s_to_c(id), m->type); + if(indent < 6) + p = seprint(buf, e, "%-32s %-6d ", typeid, m->len); + else + p = seprint(buf, e, "%-64s %-6d ", typeid, m->len); + if(m->filename && *m->filename) + p = seprint(p, e, "(file,%s)", m->filename); + if(m->from && *m->from) + p = seprint(p, e, "(from,%s)", m->from); + if(m->subject && *m->subject) + seprint(p, e, "(subj,%s)", m->subject); + + s_free(id); +} + +char sstring[256]; + +// cmd := range cmd ' ' arg-list ; +// range := address +// | address ',' address +// | 'g' search ; +// address := msgno +// | search ; +// msgno := number +// | number '/' msgno ; +// search := '/' string '/' +// | '%' string '%' ; +// +Reprog* +parsesearch(char **pp) +{ + char *p, *np; + int c, n; + + p = *pp; + c = *p++; + np = strchr(p, c); + if(np != nil){ + *np++ = 0; + *pp = np; + } else { + n = strlen(p); + *pp = p + n; + } + if(*p == 0) + p = sstring; + else{ + strncpy(sstring, p, sizeof(sstring)); + sstring[sizeof(sstring)-1] = 0; + } + return regcomp(p); +} + +char* +parseaddr(char **pp, Message *first, Message *cur, Message *unspec, Message **mp) +{ + int n; + Message *m; + char *p; + Reprog *prog; + int c, sign; + char buf[256]; + + *mp = nil; + p = *pp; + + if(*p == '+'){ + sign = 1; + p++; + *pp = p; + } else if(*p == '-'){ + sign = -1; + p++; + *pp = p; + } else + sign = 0; + + switch(*p){ + default: + if(sign){ + n = 1; + goto number; + } + *mp = unspec; + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + n = strtoul(p, pp, 10); + if(n == 0){ + if(sign) + *mp = cur; + else + *mp = ⊤ + break; + } + number: + m = nil; + switch(sign){ + case 0: + for(m = first; m != nil; m = m->next) + if(m->id == n) + break; + break; + case -1: + if(cur != &top) + for(m = cur; m != nil && n > 0; n--) + m = m->prev; + break; + case 1: + if(cur == &top){ + n--; + cur = first; + } + for(m = cur; m != nil && n > 0; n--) + m = m->next; + break; + } + if(m == nil) + return "address"; + *mp = m; + break; + case '%': + case '/': + case '?': + c = *p; + prog = parsesearch(pp); + if(prog == nil) + return "badly formed regular expression"; + m = nil; + switch(c){ + case '%': + for(m = cur == &top ? first : cur->next; m != nil; m = m->next){ + if(rawsearch(m, prog)) + break; + } + break; + case '/': + for(m = cur == &top ? first : cur->next; m != nil; m = m->next){ + snprintheader(buf, sizeof(buf), m); + if(regexec(prog, buf, nil, 0)) + break; + } + break; + case '?': + for(m = cur == &top ? nil : cur->prev; m != nil; m = m->prev){ + snprintheader(buf, sizeof(buf), m); + if(regexec(prog, buf, nil, 0)) + break; + } + break; + } + if(m == nil) + return "search"; + *mp = m; + free(prog); + break; + case '$': + for(m = first; m != nil && m->next != nil; m = m->next) + ; + *mp = m; + *pp = p+1; + break; + case '.': + *mp = cur; + *pp = p+1; + break; + case ',': + *mp = first; + *pp = p; + break; + } + + if(*mp != nil && **pp == '.'){ + (*pp)++; + if((*mp)->child == nil) + return "no sub parts"; + return parseaddr(pp, (*mp)->child, (*mp)->child, (*mp)->child, mp); + } + if(**pp == '+' || **pp == '-' || **pp == '/' || **pp == '%') + return parseaddr(pp, first, *mp, *mp, mp); + + return nil; +} + +// +// search a message for a regular expression match +// +int +rawsearch(Message *m, Reprog *prog) +{ + char buf[4096+1]; +//jpc int i, fd, rv; + int i, rv; + CFid *fid; + String *path; + + path = extendpath(m->path, "raw"); +//jpc fd = open(s_to_c(path), OREAD); +//jpc if(fd < 0) +//jpc return 0; + fid = fsopen(upasfs,s_to_c(path), OREAD); + if(fid == nil) + return 0; + + // march through raw message 4096 bytes at a time + // with a 128 byte overlap to chain the re search. + rv = 0; + for(;;){ +//jpc i = read(fd, buf, sizeof(buf)-1); + i = fsread(fid, buf, sizeof(buf)-1); + if(i <= 0) + break; + buf[i] = 0; + if(regexec(prog, buf, nil, 0)){ + rv = 1; + break; + } + if(i < sizeof(buf)-1) + break; +//jpc if(seek(fd, -128LL, 1) < 0) + if(fsseek(fid, -128LL, 1) < 0) + break; + } + + fsclose(fid); + s_free(path); + return rv; +} + + +char* +parsecmd(char *p, Cmd *cmd, Message *first, Message *cur) +{ + Reprog *prog; + Message *m, *s, *e, **l, *last; + char buf[256]; + char *err; + int i, c; + char *q; + static char errbuf[Errlen]; + + cmd->delete = 0; + l = &cmd->msgs; + *l = nil; + + // eat white space + while(*p == ' ') + p++; + + // null command is a special case (advance and print) + if(*p == 0){ + if(cur == &top){ + // special case + m = first; + } else { + // walk to the next message even if we have to go up + m = cur->next; + while(m == nil && cur->parent != nil){ + cur = cur->parent; + m = cur->next; + } + } + if(m == nil) + return "address"; + *l = m; + m->cmd = nil; + cmd->an = 0; + cmd->f = pcmd; + return nil; + } + + // global search ? + if(*p == 'g'){ + p++; + + // no search string means all messages + if(*p != '/' && *p != '%'){ + for(m = first; m != nil; m = m->next){ + *l = m; + l = &m->cmd; + *l = nil; + } + } else { + // mark all messages matching this search string + c = *p; + prog = parsesearch(&p); + if(prog == nil) + return "badly formed regular expression"; + if(c == '%'){ + for(m = first; m != nil; m = m->next){ + if(rawsearch(m, prog)){ + *l = m; + l = &m->cmd; + *l = nil; + } + } + } else { + for(m = first; m != nil; m = m->next){ + snprintheader(buf, sizeof(buf), m); + if(regexec(prog, buf, nil, 0)){ + *l = m; + l = &m->cmd; + *l = nil; + } + } + } + free(prog); + } + } else { + + // parse an address + s = e = nil; + err = parseaddr(&p, first, cur, cur, &s); + if(err != nil) + return err; + if(*p == ','){ + // this is an address range + if(s == &top) + s = first; + p++; + for(last = s; last != nil && last->next != nil; last = last->next) + ; + err = parseaddr(&p, first, cur, last, &e); + if(err != nil) + return err; + + // select all messages in the range + for(; s != nil; s = s->next){ + *l = s; + l = &s->cmd; + *l = nil; + if(s == e) + break; + } + if(s == nil) + return "null address range"; + } else { + // single address + if(s != &top){ + *l = s; + s->cmd = nil; + } + } + } + + // insert a space after '!'s and '|'s + for(q = p; *q; q++) + if(*q != '!' && *q != '|') + break; + if(q != p && *q != ' '){ + memmove(q+1, q, strlen(q)+1); + *q = ' '; + } + + cmd->an = getfields(p, cmd->av, nelem(cmd->av) - 1, 1, " \t\r\n"); + if(cmd->an == 0 || *cmd->av[0] == 0) + cmd->f = pcmd; + else { + // hack to allow all messages to start with 'd' + if(*(cmd->av[0]) == 'd' && *(cmd->av[0]+1) != 0){ + cmd->delete = 1; + cmd->av[0]++; + } + + // search command table + for(i = 0; cmdtab[i].cmd != nil; i++) + if(strcmp(cmd->av[0], cmdtab[i].cmd) == 0) + break; + if(cmdtab[i].cmd == nil) + return "illegal command"; + if(cmdtab[i].args == 0 && cmd->an > 1){ + snprint(errbuf, sizeof(errbuf), "%s doesn't take an argument", cmdtab[i].cmd); + return errbuf; + } + cmd->f = cmdtab[i].f; + } + return nil; +} + +// inefficient read from standard input +char* +readline(char *prompt, char *line, int len) +{ + char *p, *e; + int n; + +retry: + interrupted = 0; + Bprint(&out, "%s", prompt); + Bflush(&out); + e = line + len; + for(p = line; p < e; p++){ + n = read(0, p, 1); + if(n < 0){ + if(interrupted) + goto retry; + return nil; + } + if(n == 0) + return nil; + if(*p == '\n') + break; + } + *p = 0; + return line; +} + +void +messagecount(Message *m) +{ + int i; + + i = 0; + for(; m != nil; m = m->next) + i++; + Bprint(&out, "%d message%s\n", i, plural(i)); +} + +Message* +aichcmd(Message *m, int indent) +{ + char hdr[256]; + + if(m == &top) + return nil; + + snprintHeader(hdr, sizeof(hdr), indent, m); + Bprint(&out, "%s\n", hdr); + for(m = m->child; m != nil; m = m->next) + aichcmd(m, indent+1); + return nil; +} + +Message* +Hcmd(Cmd* dummy, Message *m) +{ + if(m == &top) + return nil; + aichcmd(m, 0); + return nil; +} + +Message* +hcmd(Cmd* dummy, Message *m) +{ + char hdr[256]; + + if(m == &top) + return nil; + + snprintheader(hdr, sizeof(hdr), m); + Bprint(&out, "%s\n", hdr); + return nil; +} + +Message* +bcmd(Cmd* dummy, Message *m) +{ + int i; + Message *om = m; + + if(m == &top) + m = top.child; + for(i = 0; i < 10 && m != nil; i++){ + hcmd(nil, m); + om = m; + m = m->next; + } + + return om; +} + +Message* +ncmd(Cmd* dummy, Message *m) +{ + if(m == &top) + return m->child; + return m->next; +} + +int +printpart(String *s, char *part) +{ + char buf[4096]; +//jpc int n, fd, tot; + int n, tot; + CFid *fid; + String *path; + + path = extendpath(s, part); +//jpc fd = open(s_to_c(path), OREAD); + fid = fsopen(upasfs,s_to_c(path), OREAD); + s_free(path); +//jpc if(fd < 0){ + if(fid == nil){ + fprint(2, "!message dissappeared\n"); + return 0; + } + tot = 0; +//jpc while((n = read(fd, buf, sizeof(buf))) > 0){ + while((n = fsread(fid, buf, sizeof(buf))) > 0){ + if(interrupted) + break; + if(Bwrite(&out, buf, n) <= 0) + break; + tot += n; + } + fsclose(fid); + return tot; +} + +int +printhtml(Message *m) +{ + Cmd c; + + c.an = 3; + c.av[1] = unsharp("#9/bin/htmlfmt"); + c.av[2] = "-l 40 -cutf-8"; + Bprint(&out, "!%s\n", c.av[1]); + Bflush(&out); + pipecmd(&c, m); + return 0; +} + +Message* +Pcmd(Cmd* dummy, Message *m) +{ + if(m == &top) + return ⊤ + if(m->parent == &top) + printpart(m->path, "unixheader"); + printpart(m->path, "raw"); + return m; +} + +void +compress(char *p) +{ + char *np; + int last; + + last = ' '; + for(np = p; *p; p++){ + if(*p != ' ' || last != ' '){ + last = *p; + *np++ = last; + } + } + *np = 0; +} + +Message* +pcmd(Cmd* dummy, Message *m) +{ + Message *nm; + Ctype *cp; + String *s; + char buf[128]; + + if(m == &top) + return ⊤ + if(m->parent == &top) + printpart(m->path, "unixheader"); + if(printpart(m->path, "header") > 0) + Bprint(&out, "\n"); + cp = findctype(m); + if(cp->display){ + if(strcmp(m->type, "text/html") == 0) + printhtml(m); + else + printpart(m->path, "body"); + } else if(strcmp(m->type, "multipart/alternative") == 0){ + for(nm = m->child; nm != nil; nm = nm->next){ + cp = findctype(nm); + if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0) + break; + } + if(nm == nil) + for(nm = m->child; nm != nil; nm = nm->next){ + cp = findctype(nm); + if(cp->display) + break; + } + if(nm != nil) + pcmd(nil, nm); + else + hcmd(nil, m); + } else if(strncmp(m->type, "multipart/", 10) == 0){ + nm = m->child; + if(nm != nil){ + // always print first part + pcmd(nil, nm); + + for(nm = nm->next; nm != nil; nm = nm->next){ + s = rooted(s_clone(nm->path)); + cp = findctype(nm); + snprintHeader(buf, sizeof buf, -1, nm); + compress(buf); + if(strcmp(nm->disposition, "inline") == 0){ + if(cp->ext != nil) + Bprint(&out, "\n--- %s %s/body.%s\n\n", + buf, s_to_c(s), cp->ext); + else + Bprint(&out, "\n--- %s %s/body\n\n", + buf, s_to_c(s)); + pcmd(nil, nm); + } else { + if(cp->ext != nil) + Bprint(&out, "\n!--- %s %s/body.%s\n", + buf, s_to_c(s), cp->ext); + else + Bprint(&out, "\n!--- %s %s/body\n", + buf, s_to_c(s)); + } + s_free(s); + } + } else { + hcmd(nil, m); + } + } else if(strcmp(m->type, "message/rfc822") == 0){ + pcmd(nil, m->child); + } else if(plumb(m, cp) >= 0) + Bprint(&out, "\n!--- using plumber to display message of type %s\n", m->type); + else + Bprint(&out, "\n!--- cannot display messages of type %s\n", m->type); + + return m; +} + +void +printpartindented(String *s, char *part, char *indent) +{ + char *p; + String *path; + Biobuf *b; + + fprint(2,"printpartindented: fixme\n"); + path = extendpath(s, part); + b = Bopen(s_to_c(path), OREAD); + s_free(path); + if(b == nil){ + fprint(2, "!message dissappeared\n"); + return; + } + while((p = Brdline(b, '\n')) != nil){ + if(interrupted) + break; + p[Blinelen(b)-1] = 0; + if(Bprint(&out, "%s%s\n", indent, p) <= 0) + break; + } + Bprint(&out, "\n"); + Bterm(b); +} + +Message* +quotecmd(Cmd* dummy, Message *m) +{ + Message *nm; + Ctype *cp; + + if(m == &top) + return ⊤ + Bprint(&out, "\n"); + if(m->from != nil && *m->from) + Bprint(&out, "On %s, %s wrote:\n", m->date, m->from); + cp = findctype(m); + if(cp->display){ + printpartindented(m->path, "body", "> "); + } else if(strcmp(m->type, "multipart/alternative") == 0){ + for(nm = m->child; nm != nil; nm = nm->next){ + cp = findctype(nm); + if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0) + break; + } + if(nm == nil) + for(nm = m->child; nm != nil; nm = nm->next){ + cp = findctype(nm); + if(cp->display) + break; + } + if(nm != nil) + quotecmd(nil, nm); + } else if(strncmp(m->type, "multipart/", 10) == 0){ + nm = m->child; + if(nm != nil){ + cp = findctype(nm); + if(cp->display || strncmp(m->type, "multipart/", 10) == 0) + quotecmd(nil, nm); + } + } + return m; +} + +// really delete messages +Message* +flushdeleted(Message *cur) +{ + Message *m, **l; + char buf[1024], *p, *e, *msg; +//jpc int deld, n, fd; + int deld, n; + CFid *fid; + int i; + + doflush = 0; + deld = 0; + +//jpc fd = open("/mail/fs/ctl", ORDWR); +//jpc if(fd < 0){ +//jpc fprint(2, "!can't delete mail, opening /mail/fs/ctl: %r\n"); +//jpc exitfs(0); +//jpc } + fid = fsopen(upasfs,"ctl", ORDWR); + if(fid == nil){ + fprint(2, "!can't delete mail, opening /mail/fs/ctl: %r\n"); + exitfs(0); + } + e = &buf[sizeof(buf)]; + p = seprint(buf, e, "delete %s", mbname); + n = 0; + for(l = &top.child; *l != nil;){ + m = *l; + if(!m->deleted){ + l = &(*l)->next; + continue; + } + + // don't return a pointer to a deleted message + if(m == cur) + cur = m->next; + + deld++; + msg = strrchr(s_to_c(m->path), '/'); + if(msg == nil) + msg = s_to_c(m->path); + else + msg++; + if(e-p < 10){ +//jpc write(fd, buf, p-buf); + fswrite(fid, buf, p-buf); + n = 0; + p = seprint(buf, e, "delete %s", mbname); + } + p = seprint(p, e, " %s", msg); + n++; + + // unchain and free + *l = m->next; + if(m->next) + m->next->prev = m->prev; + freemessage(m); + } + if(n) + fswrite(fid, buf, p-buf); +//jpc write(fd, buf, p-buf); + +//jpc close(fd); + fsclose(fid); + + if(deld) + Bprint(&out, "!%d message%s deleted\n", deld, plural(deld)); + + // renumber + i = 1; + for(m = top.child; m != nil; m = m->next) + m->id = natural ? m->fileno : i++; + + // if we're out of messages, go back to first + // if no first, return the fake first + if(cur == nil){ + if(top.child) + return top.child; + else + return ⊤ + } + return cur; +} + +Message* +qcmd(Cmd* dummy, Message* dummy2) +{ + flushdeleted(nil); + + if(didopen) + closemb(); + Bflush(&out); + + exitfs(0); + return nil; // not reached +} + +Message* +ycmd(Cmd* dummy, Message *m) +{ + doflush = 1; + + return icmd(nil, m); +} + +Message* +xcmd(Cmd* dummy, Message* dummy2) +{ + exitfs(0); + return nil; // not reached +} + +Message* +eqcmd(Cmd* dummy, Message *m) +{ + if(m == &top) + Bprint(&out, "0\n"); + else + Bprint(&out, "%d\n", m->id); + return nil; +} + +Message* +dcmd(Cmd* dummy, Message *m) +{ + if(m == &top){ + Bprint(&out, "!address\n"); + return nil; + } + while(m->parent != &top) + m = m->parent; + m->deleted = 1; + return m; +} + +Message* +ucmd(Cmd* dummy, Message *m) +{ + if(m == &top) + return nil; + while(m->parent != &top) + m = m->parent; + if(m->deleted < 0) + Bprint(&out, "!can't undelete, already flushed\n"); + m->deleted = 0; + return m; +} + + +Message* +icmd(Cmd* dummy, Message *m) +{ + int n; + + n = dir2message(&top, reverse); + if(n > 0) + Bprint(&out, "%d new message%s\n", n, plural(n)); + return m; +} + +Message* +helpcmd(Cmd* dummy, Message *m) +{ + int i; + + Bprint(&out, "Commands are of the form [] [args]\n"); + Bprint(&out, " := | ','| 'g'\n"); + Bprint(&out, " := '.' | '$' | '^' | | | '+' | '-'\n"); + Bprint(&out, " := '/''/' | '?''?' | '%%''%%'\n"); + Bprint(&out, " :=\n"); + for(i = 0; cmdtab[i].cmd != nil; i++) + Bprint(&out, "%s\n", cmdtab[i].help); + return m; +} + +int +tomailer(char **av) +{ + Waitmsg *w; + int pid, i; + + // start the mailer and get out of the way + switch(pid = fork()){ + case -1: + fprint(2, "can't fork: %r\n"); + return -1; + case 0: +//jpc Bprint(&out, "!/bin/upas/marshal"); + Bprint(&out, "!%s",unsharp("#9/bin/upas/marshal")); + for(i = 1; av[i]; i++){ + if(strchr(av[i], ' ') != nil) + Bprint(&out, " '%s'", av[i]); + else + Bprint(&out, " %s", av[i]); + } + Bprint(&out, "\n"); + Bflush(&out); + av[0] = "marshal"; + chdir(wd); +//jpc exec("/bin/upas/marshal", av); +//jpc fprint(2, "couldn't exec /bin/upas/marshal\n"); + exec(unsharp("#9/bin/upas/marshal"), av); + fprint(2, "couldn't exec %s\n",unsharp("#9/bin/upas/marshal")); + threadexits(0); + default: + w = wait(); + if(w == nil){ + if(interrupted) + postnote(PNPROC, pid, "die"); + waitpid(); + return -1; + } + if(w->msg[0]){ + fprint(2, "mailer failed: %s\n", w->msg); + free(w); + return -1; + } + free(w); + Bprint(&out, "!\n"); + break; + } + return 0; +} + +// +// 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 */ +} + +Message* +rcmd(Cmd *c, Message *m) +{ + char *av[128]; + int i, ai = 1; + Message *nm; + char *addr; + String *path = nil; + String *rpath; + String *subject = nil; + String *from; + + if(m == &top){ + Bprint(&out, "!address\n"); + return nil; + } + + addr = nil; + for(nm = m; nm != ⊤ nm = nm->parent){ + if(*nm->replyto != 0){ + addr = nm->replyto; + break; + } + } + if(addr == nil){ + Bprint(&out, "!no reply address\n"); + return nil; + } + + if(nm == &top){ + print("!noone to reply to\n"); + return nil; + } + + for(nm = m; nm != ⊤ nm = nm->parent){ + if(*nm->subject){ + av[ai++] = "-s"; + subject = addrecolon(nm->subject); + av[ai++] = s_to_c(subject);; + break; + } + } + + av[ai++] = "-R"; + rpath = rooted(s_clone(m->path)); + av[ai++] = s_to_c(rpath); + + if(strchr(c->av[0], 'f') != nil){ + fcmd(c, m); + av[ai++] = "-F"; + } + + if(strchr(c->av[0], 'R') != nil){ + av[ai++] = "-t"; + av[ai++] = "message/rfc822"; + av[ai++] = "-A"; + path = rooted(extendpath(m->path, "raw")); + av[ai++] = s_to_c(path); + } + + for(i = 1; i < c->an && ai < nelem(av)-1; i++) + av[ai++] = c->av[i]; + from = s_copy(addr); + ai += tokenize822(s_to_c(from), &av[ai], nelem(av) - ai); + av[ai] = 0; + if(tomailer(av) < 0) + m = nil; + s_free(path); + s_free(rpath); + s_free(subject); + s_free(from); + return m; +} + +Message* +mcmd(Cmd *c, Message *m) +{ + char **av; + int i, ai; + String *path; + + if(m == &top){ + Bprint(&out, "!address\n"); + return nil; + } + + if(c->an < 2){ + fprint(2, "!usage: M list-of addresses\n"); + return nil; + } + + ai = 1; + av = malloc(sizeof(char*)*(c->an + 8)); + + av[ai++] = "-t"; + if(m->parent == &top) + av[ai++] = "message/rfc822"; + else + av[ai++] = "mime"; + + av[ai++] = "-A"; + path = rooted(extendpath(m->path, "raw")); + av[ai++] = s_to_c(path); + + if(strchr(c->av[0], 'M') == nil) + av[ai++] = "-n"; + + for(i = 1; i < c->an; i++) + av[ai++] = c->av[i]; + av[ai] = 0; + + if(tomailer(av) < 0) + m = nil; + if(path != nil) + s_free(path); + free(av); + return m; +} + +Message* +acmd(Cmd *c, Message *m) +{ + char *av[128]; + int i, ai; + String *from, *to, *cc, *path = nil, *subject = nil; + + if(m == &top){ + Bprint(&out, "!address\n"); + return nil; + } + + ai = 1; + if(*m->subject){ + av[ai++] = "-s"; + subject = addrecolon(m->subject); + av[ai++] = s_to_c(subject); + } + + if(strchr(c->av[0], 'A') != nil){ + av[ai++] = "-t"; + av[ai++] = "message/rfc822"; + av[ai++] = "-A"; + path = rooted(extendpath(m->path, "raw")); + av[ai++] = s_to_c(path); + } + + for(i = 1; i < c->an && ai < nelem(av)-1; i++) + av[ai++] = c->av[i]; + from = s_copy(m->from); + ai += tokenize822(s_to_c(from), &av[ai], nelem(av) - ai); + to = s_copy(m->to); + ai += tokenize822(s_to_c(to), &av[ai], nelem(av) - ai); + cc = s_copy(m->cc); + ai += tokenize822(s_to_c(cc), &av[ai], nelem(av) - ai); + av[ai] = 0; + if(tomailer(av) < 0) + return nil; + s_free(from); + s_free(to); + s_free(cc); + s_free(subject); + s_free(path); + return m; +} + +String * +relpath(char *path, String *to) +{ + if (*path=='/' || strncmp(path, "./", 2) == 0 + || strncmp(path, "../", 3) == 0) { + to = s_append(to, path); + } else if(mbpath) { + to = s_append(to, s_to_c(mbpath)); + to->ptr = strrchr(to->base, '/')+1; + s_append(to, path); + } + return to; +} + +int +appendtofile(Message *m, char *part, char *base, int mbox) +{ + String *file, *h; + int in, out, rv; + + file = extendpath(m->path, part); + in = open(s_to_c(file), OREAD); + if(in < 0){ + fprint(2, "!message disappeared\n"); + return -1; + } + + s_reset(file); + + relpath(base, file); + if(sysisdir(s_to_c(file))){ + s_append(file, "/"); + if(m->filename && strchr(m->filename, '/') == nil) + s_append(file, m->filename); + else { + s_append(file, "att.XXXXXXXXXXX"); + mktemp(s_to_c(file)); + } + } + if(mbox) + out = open(s_to_c(file), OWRITE); + else + out = open(s_to_c(file), OWRITE|OTRUNC); + if(out < 0){ + out = create(s_to_c(file), OWRITE, 0666); + if(out < 0){ + fprint(2, "!can't open %s: %r\n", s_to_c(file)); + close(in); + s_free(file); + return -1; + } + } + if(mbox) + seek(out, 0, 2); + + // put on a 'From ' line + if(mbox){ + while(m->parent != &top) + m = m->parent; + h = file2string(m->path, "unixheader"); + fprint(out, "%s", s_to_c(h)); + s_free(h); + } + + // copy the message escaping what we have to ad adding newlines if we have to + if(mbox) + rv = appendfiletombox(in, out); + else + rv = appendfiletofile(in, out); + + close(in); + close(out); + + if(rv >= 0) + print("!saved in %s\n", s_to_c(file)); + s_free(file); + return rv; +} + +Message* +scmd(Cmd *c, Message *m) +{ + char *file; + + if(m == &top){ + Bprint(&out, "!address\n"); + return nil; + } + + switch(c->an){ + case 1: + file = "stored"; + break; + case 2: + file = c->av[1]; + break; + default: + fprint(2, "!usage: s filename\n"); + return nil; + } + + if(appendtofile(m, "raw", file, 1) < 0) + return nil; + + m->stored = 1; + return m; +} + +Message* +wcmd(Cmd *c, Message *m) +{ + char *file; + + if(m == &top){ + Bprint(&out, "!address\n"); + return nil; + } + + switch(c->an){ + case 2: + file = c->av[1]; + break; + case 1: + if(*m->filename == 0){ + fprint(2, "!usage: w filename\n"); + return nil; + } + file = strrchr(m->filename, '/'); + if(file != nil) + file++; + else + file = m->filename; + break; + default: + fprint(2, "!usage: w filename\n"); + return nil; + } + + if(appendtofile(m, "body", file, 0) < 0) + return nil; + m->stored = 1; + return m; +} + +char *specialfile[] = +{ + "pipeto", + "pipefrom", + "L.mbox", + "forward", + "names" +}; + +// return 1 if this is a special file +static int +special(String *s) +{ + char *p; + int i; + + p = strrchr(s_to_c(s), '/'); + if(p == nil) + p = s_to_c(s); + else + p++; + for(i = 0; i < nelem(specialfile); i++) + if(strcmp(p, specialfile[i]) == 0) + return 1; + return 0; +} + +// open the folder using the recipients account name +static String* +foldername(char *rcvr) +{ + char *p; + int c; + String *file; + Dir *d; + int scarey; + + file = s_new(); + mboxpath("f", user, file, 0); + d = dirstat(s_to_c(file)); + + // if $mail/f exists, store there, otherwise in $mail + s_restart(file); + if(d && d->qid.type == QTDIR){ + scarey = 0; + s_append(file, "f/"); + } else { + scarey = 1; + } + free(d); + + p = strrchr(rcvr, '!'); + if(p != nil) + rcvr = p+1; + + while(*rcvr && *rcvr != '@'){ + c = *rcvr++; + if(c == '/') + c = '_'; + s_putc(file, c); + } + s_terminate(file); + + if(scarey && special(file)){ + fprint(2, "!won't overwrite %s\n", s_to_c(file)); + s_free(file); + return nil; + } + + return file; +} + +Message* +fcmd(Cmd *c, Message *m) +{ + String *folder; + + if(c->an > 1){ + fprint(2, "!usage: f takes no arguments\n"); + return nil; + } + + if(m == &top){ + Bprint(&out, "!address\n"); + return nil; + } + + folder = foldername(m->from); + if(folder == nil) + return nil; + + if(appendtofile(m, "raw", s_to_c(folder), 1) < 0){ + s_free(folder); + return nil; + } + s_free(folder); + + m->stored = 1; + return m; +} + +void +system9(char *cmd, char **av, int in) +{ + int pid; + + switch(pid=fork()){ + case -1: + return; + case 0: + if(in >= 0){ + close(0); + dup(in, 0); + close(in); + } + if(wd[0] != 0) + chdir(wd); + exec(cmd, av); + fprint(2, "!couldn't exec %s\n", cmd); + threadexits(0); + default: + if(in >= 0) + close(in); + while(waitpid() < 0){ + if(!interrupted) + break; + postnote(PNPROC, pid, "die"); + continue; + } + break; + } +} + +Message* +bangcmd(Cmd *c, Message *m) +{ + char cmd[4*1024]; + char *p, *e; + char *av[4]; + int i; + + cmd[0] = 0; + p = cmd; + e = cmd+sizeof(cmd); + for(i = 1; i < c->an; i++) + p = seprint(p, e, "%s ", c->av[i]); + av[0] = "rc"; + av[1] = "-c"; + av[2] = cmd; + av[3] = 0; + system9(unsharp("#9/bin/rc"), av, -1); + Bprint(&out, "!\n"); + return m; +} + +Message* +xpipecmd(Cmd *c, Message *m, char *part) +{ + char cmd[128]; + char *p, *e; + char *av[4]; + String *path; +//jpc int i, fd; + int i; + CFid *fid; + + if(c->an < 2){ + Bprint(&out, "!usage: | cmd\n"); + return nil; + } + + if(m == &top){ + Bprint(&out, "!address\n"); + return nil; + } + + path = extendpath(m->path, part); +//jpc fd = open(s_to_c(path), OREAD); + fid = fsopen(upasfs,s_to_c(path), OREAD); + s_free(path); +//jpc if(fd < 0){ // compatibility with older upas/fs + if(fid == nil){ // compatibility with older upas/fs + path = extendpath(m->path, "raw"); +//jpc fd = open(s_to_c(path), OREAD); + fid = fsopen(upasfs,s_to_c(path), OREAD); + s_free(path); + } + if(fid < 0){ + fprint(2, "!message disappeared\n"); + return nil; + } + + p = cmd; + e = cmd+sizeof(cmd); + cmd[0] = 0; + for(i = 1; i < c->an; i++) + p = seprint(p, e, "%s ", c->av[i]); + av[0] = "rc"; + av[1] = "-c"; + av[2] = cmd; + av[3] = 0; +// system9("/bin/rc", av, fd); /* system closes fd */ + system9(unsharp("#9/bin/rc"), av, 0); + fsclose(fid); + Bprint(&out, "!\n"); + return m; +} + +Message* +pipecmd(Cmd *c, Message *m) +{ + return xpipecmd(c, m, "body"); +} + +Message* +rpipecmd(Cmd *c, Message *m) +{ + return xpipecmd(c, m, "rawunix"); +} + +#if 0 /* jpc */ +void +closemb(void) +{ + int fd; + + fd = open("/mail/fs/ctl", ORDWR); + if(fd < 0) + sysfatal("can't open /mail/fs/ctl: %r"); + + // close current mailbox + if(*mbname && strcmp(mbname, "mbox") != 0) + fprint(fd, "close %s", mbname); + + close(fd); +} +#endif +void +closemb(void) +{ + CFid *fid; + char s[256]; + + fid = fsopen(upasfs,"ctl", ORDWR); + if(fid == nil) + sysfatal("can't open upasfs/ctl: %r"); + + // close current mailbox + if(*mbname && strcmp(mbname, "mbox") != 0) { + snprint(s, 256, "close %s", mbname); + fswrite(fid,s,strlen(s)); + } + +//jpc close(fd); + fsclose(fid); +} + +int +switchmb(char *file, char *singleton) +{ + char *p; + int n, fd; + String *path; + char buf[256]; + + // if the user didn't say anything and there + // is an mbox mounted already, use that one + // so that the upas/fs -fdefault default is honored. + if(file + || (singleton && access(singleton, 0)<0) +/* || (!singleton && access("/mail/fs/mbox", 0)<0)){ jpc */ + || (!singleton && fsdirstat(upasfs, "upasfs/mbox") )){ + fprint(2,"can't access /mail/fs/mbox\n"); + if(file == nil) + file = "mbox"; + + // close current mailbox + closemb(); + didopen = 1; + + fd = open("/mail/fs/ctl", ORDWR); + if(fd < 0) + sysfatal("can't open /mail/fs/ctl: %r"); + + path = s_new(); + + // get an absolute path to the mail box + if(strncmp(file, "./", 2) == 0){ + // resolve path here since upas/fs doesn't know + // our working directory + if(getwd(buf, sizeof(buf)-strlen(file)) == nil){ + fprint(2, "!can't get working directory: %s\n", buf); + return -1; + } + s_append(path, buf); + s_append(path, file+1); + } else { + mboxpath(file, user, path, 0); + } + + // make up a handle to use when talking to fs + p = strrchr(file, '/'); + if(p == nil){ + // if its in the mailbox directory, just use the name + strncpy(mbname, file, sizeof(mbname)); + mbname[sizeof(mbname)-1] = 0; + } else { + // make up a mailbox name + p = strrchr(s_to_c(path), '/'); + p++; + if(*p == 0){ + fprint(2, "!bad mbox name"); + return -1; + } + strncpy(mbname, p, sizeof(mbname)); + mbname[sizeof(mbname)-1] = 0; + n = strlen(mbname); + if(n > Elemlen-12) + n = Elemlen-12; + sprint(mbname+n, "%ld", time(0)); + } + + if(fprint(fd, "open %s %s", s_to_c(path), mbname) < 0){ + fprint(2, "!can't 'open %s %s': %r\n", file, mbname); + s_free(path); + return -1; + } + close(fd); + }else + if (singleton && access(singleton, 0)==0 + && strncmp(singleton, "/mail/fs/", 9) == 0){ + if ((p = strchr(singleton +10, '/')) == nil){ + fprint(2, "!bad mbox name"); + return -1; + } + n = p-(singleton+9); + strncpy(mbname, singleton+9, n); + mbname[n+1] = 0; + path = s_reset(nil); + mboxpath(mbname, user, path, 0); + }else{ + path = s_reset(nil); + mboxpath("mbox", user, path, 0); + strcpy(mbname, "mbox"); + } + + sprint(root, "%s", mbname); + if(getwd(wd, sizeof(wd)) == 0) + wd[0] = 0; + if(singleton == nil && chdir(root) >= 0) + strcpy(root, "."); + rootlen = strlen(root); + + if(mbpath != nil) + s_free(mbpath); + mbpath = path; + return 0; +} + +// like tokenize but for into lines +int +lineize(char *s, char **f, int n) +{ + int i; + + for(i = 0; *s && i < n; i++){ + f[i] = s; + s = strchr(s, '\n'); + if(s == nil) + break; + *s++ = 0; + } + return i; +} + + + +String* +rooted(String *s) +{ + static char buf[256]; + + if(strcmp(root, ".") != 0) + return s; + snprint(buf, sizeof(buf), "/mail/fs/%s/%s", mbname, s_to_c(s)); + s_free(s); + return s_copy(buf); +} + +int +plumb(Message *m, Ctype *cp) +{ + String *s; + Plumbmsg *pm; + static int fd = -2; + + if(cp->plumbdest == nil) + return -1; + + if(fd < -1) + fd = plumbopen("send", OWRITE); + if(fd < 0) + return -1; + + pm = mallocz(sizeof(Plumbmsg), 1); + pm->src = strdup("mail"); + if(*cp->plumbdest) + pm->dst = strdup(cp->plumbdest); + pm->wdir = nil; + pm->type = strdup("text"); + pm->ndata = -1; + s = rooted(extendpath(m->path, "body")); + if(cp->ext != nil){ + s_append(s, "."); + s_append(s, cp->ext); + } + pm->data = strdup(s_to_c(s)); + s_free(s); + plumbsend(fd, pm); + plumbfree(pm); + return 0; +} + +void +regerror(char* dummy) +{ +} + +String* +addrecolon(char *s) +{ + String *str; + + if(cistrncmp(s, "re:", 3) != 0){ + str = s_copy("Re: "); + s_append(str, s); + } else + str = s_copy(s); + return str; +} + +void +exitfs(char *rv) +{ + if(startedfs) { + fsunmount(upasfs); + /* unmount(nil, "/mail/fs"); jpc */ + } +//jpc chdir("/sys/src/cmd/upas/ned"); + threadexits(rv); +} blob - /dev/null blob + 3f3df413487d6c5c93d1de0306a60c0224aeb0a2 (mode 644) --- /dev/null +++ src/cmd/upas/pop3/mkfile @@ -0,0 +1,16 @@ + +#include +#include + +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; iupasnum = 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 = ∈ + + 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 || 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 || 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 || 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= 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); +} + blob - /dev/null blob + 0aa5e52c410b337aa69114c85bf7875b30ffe45c (mode 644) --- /dev/null +++ src/cmd/upas/q/mkfile @@ -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 blob - /dev/null blob + 108828960645e06a1ea11d6d259d1c6ab68c8dba (mode 644) --- /dev/null +++ src/cmd/upas/q/qer.c @@ -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; +} blob - /dev/null blob + 7ab055ed84f83bb06937185fdf8419b817ffbbe1 (mode 644) --- /dev/null +++ src/cmd/upas/q/runq.c @@ -0,0 +1,766 @@ +#include "common.h" +#include + +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 0){ + for(i=0; ifd = 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); +} blob - /dev/null blob + b6ea720d1fccfce7245cf1d2560388a3c9d26d62 (mode 644) --- /dev/null +++ src/cmd/upas/scanmail/common.c @@ -0,0 +1,667 @@ +#include +#include +#include +#include +#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 (). 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, +}; blob - /dev/null blob + 5f0db8556d50d27e5ac57aebab2a7dae82927b3a (mode 644) --- /dev/null +++ src/cmd/upas/scanmail/mkfile @@ -0,0 +1,24 @@ += 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; +} blob - /dev/null blob + f1d24b2e0a6b13abe9565a47d94d30c712d76235 (mode 644) --- /dev/null +++ src/cmd/upas/scanmail/spam.h @@ -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*); blob - /dev/null blob + e5ea59ad5788f5bdcf96c0d9d316f5b1d14abe38 (mode 644) --- /dev/null +++ src/cmd/upas/scanmail/testscan.c @@ -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 \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; +} blob - /dev/null blob + 6fa12c57cc5aab62cb0deda75b0c51da495e3284 (mode 644) --- /dev/null +++ src/cmd/upas/send/authorize.c @@ -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); +} blob - /dev/null blob + 8a8fc8eacf1d390698cf7666c354847aec0ac7cd (mode 644) --- /dev/null +++ src/cmd/upas/send/bind.c @@ -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; +} + blob - /dev/null blob + cdd16ecedfebfda86c0769b1614f2c3d1ef43389 (mode 644) --- /dev/null +++ src/cmd/upas/send/cat_mail.c @@ -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; +} blob - /dev/null blob + fa538547a746ab8916bd903855cc53f67102ae39 (mode 644) --- /dev/null +++ src/cmd/upas/send/dest.c @@ -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; +} blob - /dev/null blob + cfee5253532ce34be0d38305c781bfde25583529 (mode 644) --- /dev/null +++ src/cmd/upas/send/filter.c @@ -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; +} blob - /dev/null blob + f3b2b36dc8e7cf7bab54d98a81619774519e0a51 (mode 644) --- /dev/null +++ src/cmd/upas/send/gateway.c @@ -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); + } +} blob - /dev/null blob + 93136b7c67c4383bdc2ac5b034c12ba386503e6a (mode 644) --- /dev/null +++ src/cmd/upas/send/local.c @@ -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; +} blob - /dev/null blob + 52df3800105f2c132bc10990c9bfbde935b33ced (mode 644) --- /dev/null +++ src/cmd/upas/send/log.c @@ -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 && cpsender = 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; +} blob - /dev/null blob + f0abcf0c1bed4f992f5183d36457abe4f8c50f49 (mode 644) --- /dev/null +++ src/cmd/upas/send/makefile @@ -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 blob - /dev/null blob + eab6160ca3a00cd874065781e3492c0905860bac (mode 644) --- /dev/null +++ src/cmd/upas/send/message.c @@ -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); +} blob - /dev/null blob + a49fde1762c2aaf323fcda25732192a4f92e7247 (mode 644) --- /dev/null +++ src/cmd/upas/send/mkfile @@ -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 +# } blob - /dev/null blob + 52e31258e3c3ae73b4481571e0683b45eb2ffd74 (mode 644) --- /dev/null +++ src/cmd/upas/send/regtest.c @@ -0,0 +1,36 @@ +#include +#include +#include +#include + +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> "); + } +} blob - /dev/null blob + 4f40b29338b9003991ee364bc2704feff8d202da (mode 644) --- /dev/null +++ src/cmd/upas/send/rewrite.c @@ -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: + * [] + */ +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:"..."); + } +} + blob - /dev/null blob + 6caa37b25c0b646ac15c7453dac47f39c2b78513 (mode 644) --- /dev/null +++ src/cmd/upas/send/send.h @@ -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); blob - /dev/null blob + f40181ad58d56b659bcb3a19657c752d9cf711ac (mode 644) --- /dev/null +++ src/cmd/upas/send/skipequiv.c @@ -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; +} blob - /dev/null blob + 0332659c0af00c7444c87c53e6318b6492b0ba6d (mode 644) --- /dev/null +++ src/cmd/upas/send/translate.c @@ -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; +} blob - /dev/null blob + fed3a2a60fe4b6d068773abe4366fb70371f6155 (mode 644) --- /dev/null +++ src/cmd/upas/send/tryit @@ -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 <>>test.local<<<" +cat /usr/spool/mail/test.local +echo ">>>test.mail<<<" +cat /tmp/test.mail blob - /dev/null blob + 915e688e3c5deb94b40dbc346901d743bee9cdb3 (mode 644) --- /dev/null +++ src/cmd/upas/smtp/greylist.c @@ -0,0 +1,274 @@ +#include "common.h" +#include "smtpd.h" +#include "smtp.h" +#include +#include +#include + +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"); + } +} blob - /dev/null blob + 722f1357b364678cf8dd2920c1e2a1ec7f52a53b (mode 644) --- /dev/null +++ src/cmd/upas/smtp/mkfile @@ -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 + } blob - /dev/null blob + aea5f2566f6e6992bec36461dc60fe655cf7c7d9 (mode 644) --- /dev/null +++ src/cmd/upas/smtp/mxdial.c @@ -0,0 +1,333 @@ +#include "common.h" +#include +#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); + +} blob - /dev/null blob + 1f12e48e78c6f7058b874912ecfa6780e808290b (mode 644) --- /dev/null +++ src/cmd/upas/smtp/rfc822.tab.c @@ -0,0 +1,1260 @@ + +#line 2 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +#include "common.h" +#include "smtp.h" +#include + +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; +extern int yyerrflag; +#ifndef YYMAXDEPTH +#define YYMAXDEPTH 150 +#endif +#ifndef YYSTYPE +#define YYSTYPE int +#endif +YYSTYPE yylval; +YYSTYPE yyval; +#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 +#define YYEOFCODE 1 +#define YYERRCODE 2 + +#line 246 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" + + +/* + * 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; +} +static const short yyexca[] = +{-1, 1, + 1, -1, + -2, 0, +-1, 47, + 1, 4, + -2, 0, +-1, 112, + 29, 72, + 31, 72, + 32, 72, + 35, 72, + -2, 74, +}; +#define YYNPROD 122 +#define YYPRIVATE 57344 +#define YYLAST 608 +static const short yyact[] = +{ + 112, 133, 136, 53, 121, 111, 134, 55, 109, 118, + 119, 116, 162, 171, 35, 48, 166, 54, 5, 166, + 179, 114, 115, 155, 49, 101, 100, 99, 95, 94, + 93, 92, 98, 91, 132, 90, 123, 89, 122, 88, + 87, 86, 85, 84, 83, 82, 97, 81, 80, 106, + 47, 46, 110, 117, 153, 168, 108, 2, 56, 57, + 58, 59, 60, 61, 62, 63, 64, 65, 73, 66, + 67, 68, 69, 70, 71, 72, 74, 75, 76, 77, + 78, 79, 124, 124, 49, 55, 177, 131, 110, 52, + 110, 110, 138, 137, 140, 141, 124, 124, 51, 120, + 124, 124, 124, 50, 102, 104, 135, 154, 31, 32, + 107, 157, 105, 14, 55, 55, 156, 13, 161, 117, + 117, 139, 158, 124, 142, 143, 144, 145, 146, 147, + 163, 164, 160, 12, 148, 149, 11, 157, 150, 151, + 152, 10, 156, 9, 8, 7, 3, 1, 0, 124, + 124, 124, 124, 124, 0, 169, 0, 0, 110, 165, + 0, 0, 170, 117, 0, 0, 0, 0, 173, 176, + 178, 0, 0, 0, 172, 0, 0, 0, 180, 0, + 0, 182, 183, 0, 0, 165, 165, 165, 165, 165, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 174, 56, 57, 58, 59, 60, 61, 62, + 63, 64, 65, 73, 66, 67, 68, 69, 70, 71, + 72, 74, 75, 76, 77, 78, 79, 0, 0, 128, + 130, 129, 125, 126, 127, 15, 0, 36, 16, 17, + 19, 103, 20, 18, 23, 22, 21, 30, 24, 26, + 28, 25, 27, 29, 0, 34, 37, 38, 39, 33, + 40, 0, 4, 0, 45, 44, 41, 42, 43, 56, + 57, 58, 59, 60, 61, 62, 63, 64, 65, 73, + 66, 67, 68, 69, 70, 71, 72, 74, 75, 76, + 77, 78, 79, 0, 0, 96, 45, 44, 41, 42, + 43, 15, 0, 36, 16, 17, 19, 6, 20, 18, + 23, 22, 21, 30, 24, 26, 28, 25, 27, 29, + 0, 34, 37, 38, 39, 33, 40, 0, 4, 0, + 45, 44, 41, 42, 43, 15, 0, 36, 16, 17, + 19, 103, 20, 18, 23, 22, 21, 30, 24, 26, + 28, 25, 27, 29, 0, 34, 37, 38, 39, 33, + 40, 0, 0, 0, 45, 44, 41, 42, 43, 56, + 57, 58, 59, 60, 61, 62, 63, 64, 65, 73, + 66, 67, 68, 69, 70, 71, 72, 74, 75, 76, + 77, 78, 79, 0, 0, 0, 0, 175, 113, 0, + 52, 56, 57, 58, 59, 60, 61, 62, 63, 64, + 65, 73, 66, 67, 68, 69, 70, 71, 72, 74, + 75, 76, 77, 78, 79, 0, 0, 0, 0, 0, + 113, 0, 52, 56, 57, 58, 59, 60, 61, 62, + 63, 64, 65, 73, 66, 67, 68, 69, 70, 71, + 72, 74, 75, 76, 77, 78, 79, 0, 0, 0, + 0, 0, 0, 159, 52, 56, 57, 58, 59, 60, + 61, 62, 63, 64, 65, 73, 66, 67, 68, 69, + 70, 71, 72, 74, 75, 76, 77, 78, 79, 0, + 0, 0, 0, 0, 0, 0, 52, 56, 57, 58, + 59, 60, 61, 62, 63, 64, 65, 73, 66, 67, + 68, 69, 70, 71, 72, 74, 75, 76, 77, 78, + 79, 0, 0, 167, 0, 0, 113, 56, 57, 58, + 59, 60, 61, 62, 63, 64, 65, 73, 66, 67, + 68, 69, 70, 71, 72, 74, 75, 76, 77, 78, + 79, 0, 0, 0, 0, 0, 113, 56, 57, 58, + 59, 60, 61, 62, 63, 64, 65, 73, 66, 67, + 68, 69, 70, 71, 72, 74, 75, 76, 77, 78, + 79, 0, 0, 181, 56, 57, 58, 59, 60, 61, + 62, 63, 64, 65, 73, 66, 67, 68, 69, 70, + 71, 72, 74, 75, 76, 77, 78, 79 +}; +static const short yypact[] = +{ + 299,-1000,-1000, 22,-1000, 21, 54,-1000,-1000,-1000, +-1000,-1000,-1000,-1000,-1000, 19, 17, 15, 14, 13, + 12, 11, 10, 9, 7, 5, 3, 1, 0, -1, + -2, 265, -3, -4, -5,-1000,-1000,-1000,-1000,-1000, +-1000,-1000,-1000,-1000,-1000,-1000, 233, 233, 580, 397, + -9,-1000, 580, -26, -25,-1000,-1000,-1000,-1000,-1000, +-1000,-1000,-1000,-1000,-1000,-1000,-1000,-1000,-1000,-1000, +-1000,-1000,-1000,-1000,-1000,-1000,-1000,-1000,-1000,-1000, + 333, 199, 199, 397, 461, 397, 397, 397, 397, 397, + 397, 397, 397, 397, 397, 199, 199,-1000,-1000, 199, + 199, 199,-1000, -6,-1000, 33, 580, -8,-1000,-1000, + 523,-1000,-1000, 429, 580, -23,-1000,-1000, 580, 580, +-1000,-1000, 199,-1000,-1000,-1000,-1000,-1000,-1000,-1000, +-1000,-1000, -15,-1000,-1000,-1000, 493,-1000,-1000, -15, +-1000,-1000, -15, -15, -15, -15, -15, -15, 199, 199, + 199, 199, 199, 47, 580, 397,-1000,-1000, -21,-1000, + -25, -26, 580,-1000,-1000,-1000, 397, 365, 580, 580, +-1000,-1000,-1000,-1000, -12,-1000,-1000, 553,-1000,-1000, + 580, 580,-1000,-1000 +}; +static const short yypgo[] = +{ + 0, 147, 57, 146, 18, 145, 144, 143, 141, 136, + 133, 117, 113, 8, 112, 0, 34, 110, 6, 4, + 38, 109, 108, 1, 106, 2, 5, 103, 17, 98, + 11, 3, 36, 86, 14 +}; +static const short yyr1[] = +{ + 0, 1, 1, 2, 2, 2, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 3, 6, 6, 6, 6, + 6, 6, 6, 5, 5, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 8, 8, 11, + 11, 12, 12, 10, 10, 21, 21, 21, 21, 9, + 9, 16, 16, 23, 23, 24, 24, 17, 17, 18, + 18, 18, 26, 26, 13, 13, 27, 27, 29, 29, + 28, 28, 31, 30, 25, 25, 20, 20, 32, 32, + 32, 32, 32, 32, 32, 19, 14, 33, 33, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 22, 22, 22, 22, 34, 34, 34, + 34, 34 +}; +static const short yyr2[] = +{ + 0, 1, 3, 1, 2, 3, 1, 1, 1, 1, + 1, 1, 1, 1, 3, 6, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 2, 3, 2, 3, 2, + 3, 2, 3, 2, 3, 2, 3, 3, 2, 3, + 2, 3, 2, 3, 2, 1, 1, 1, 1, 3, + 2, 1, 3, 1, 1, 4, 3, 1, 3, 1, + 2, 1, 3, 2, 3, 1, 2, 4, 1, 1, + 3, 3, 1, 1, 1, 2, 1, 2, 1, 1, + 1, 1, 1, 1, 1, 1, 6, 1, 3, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, + 1, 1 +}; +static const short yychk[] = +{ +-1000, -1, -2, -3, 29, -4, 8, -5, -6, -7, + -8, -9, -10, -11, -12, 2, 5, 6, 10, 7, + 9, 13, 12, 11, 15, 18, 16, 19, 17, 20, + 14, -22, -21, 26, 22, -34, 4, 23, 24, 25, + 27, 33, 34, 35, 32, 31, 29, 29, -13, 30, + -27, -29, 35, -31, -28, -15, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, + 19, 20, 21, 14, 22, 23, 24, 25, 26, 27, + 29, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, -34, -15, 30, + 30, 30, -2, 8, -2, -14, -15, -17, -18, -13, + -25, -26, -15, 33, 30, 31, -30, -15, 35, 35, + -4, -19, -20, -32, -15, 33, 34, 35, 30, 32, + 31, -19, -16, -23, -18, -24, -25, -13, -18, -16, + -18, -18, -16, -16, -16, -16, -16, -16, -20, -20, + -20, -20, -20, 21, -15, 31, -26, -15, -13, 34, + -28, -31, 35, -30, -30, -32, 31, 30, 8, -15, + -18, 34, -30, -23, -16, 32, -15, -33, -15, 32, + -15, 30, -15, -15 +}; +static const short yydef[] = +{ + 0, -2, 1, 0, 3, 0, 0, 6, 7, 8, + 9, 10, 11, 12, 13, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 113, 114, 45, 46, 47, + 48, 117, 118, 119, 120, 121, 0, -2, 0, 0, + 0, 65, 0, 68, 69, 72, 89, 90, 91, 92, + 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, + 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, + 27, 29, 31, 33, 35, 38, 50, 115, 116, 44, + 40, 42, 2, 0, 5, 0, 0, 18, 57, 59, + 0, 61, -2, 0, 0, 0, 66, 73, 0, 0, + 14, 23, 85, 76, 78, 79, 80, 81, 82, 83, + 84, 24, 16, 51, 53, 54, 0, 17, 19, 20, + 21, 22, 26, 28, 30, 32, 34, 36, 37, 49, + 43, 39, 41, 0, 0, 0, 60, 75, 0, 63, + 64, 0, 0, 70, 71, 77, 0, 0, 0, 0, + 58, 62, 67, 52, 0, 56, 15, 0, 87, 55, + 0, 0, 86, 88 +}; +static const short yytok1[] = +{ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 31, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 30, 32, + 33, 0, 34, 0, 35 +}; +static const short yytok2[] = +{ + 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28 +}; +static const long yytok3[] = +{ + 0 +}; +#define YYFLAG -1000 +#define YYERROR goto yyerrlab +#define YYACCEPT return(0) +#define YYABORT return(1) +#define yyclearin yychar = -1 +#define yyerrok yyerrflag = 0 + +#ifdef yydebug +#include "y.debug" +#else +#define yydebug 0 +static const char* yytoknames[1]; /* for debugging */ +static const char* yystates[1]; /* for debugging */ +#endif + +/* parser for yacc output */ +#ifdef YYARG +#define yynerrs yyarg->yynerrs +#define yyerrflag yyarg->yyerrflag +#define yyval yyarg->yyval +#define yylval yyarg->yylval +#else +int yynerrs = 0; /* number of errors */ +int yyerrflag = 0; /* error recovery flag */ +#endif + +extern int fprint(int, char*, ...); +extern int sprint(char*, char*, ...); + +static const char* +yytokname(int yyc) +{ + static char x[10]; + + if(yyc > 0 && yyc <= sizeof(yytoknames)/sizeof(yytoknames[0])) + if(yytoknames[yyc-1]) + return yytoknames[yyc-1]; + sprint(x, "<%d>", yyc); + return x; +} + +static const char* +yystatname(int yys) +{ + static char x[10]; + + if(yys >= 0 && yys < sizeof(yystates)/sizeof(yystates[0])) + if(yystates[yys]) + return yystates[yys]; + sprint(x, "<%d>\n", yys); + return x; +} + +static long +#ifdef YYARG +yylex1(struct Yyarg *yyarg) +#else +yylex1(void) +#endif +{ + long yychar; + const long *t3p; + int c; + +#ifdef YYARG + yychar = yylex(yyarg); +#else + yychar = yylex(); +#endif + if(yychar <= 0) { + c = yytok1[0]; + goto out; + } + if(yychar < sizeof(yytok1)/sizeof(yytok1[0])) { + c = yytok1[yychar]; + goto out; + } + if(yychar >= YYPRIVATE) + if(yychar < YYPRIVATE+sizeof(yytok2)/sizeof(yytok2[0])) { + c = yytok2[yychar-YYPRIVATE]; + goto out; + } + for(t3p=yytok3;; t3p+=2) { + c = t3p[0]; + if(c == yychar) { + c = t3p[1]; + goto out; + } + if(c == 0) + break; + } + c = 0; + +out: + if(c == 0) + c = yytok2[1]; /* unknown char */ + if(yydebug >= 3) + fprint(2, "lex %.4lux %s\n", yychar, yytokname(c)); + return c; +} + +int +#ifdef YYARG +yyparse(struct Yyarg *yyarg) +#else +yyparse(void) +#endif +{ + struct + { + YYSTYPE yyv; + int yys; + } yys[YYMAXDEPTH], *yyp, *yypt; + const short *yyxi; + int yyj, yym, yystate, yyn, yyg; + long yychar; +#ifndef YYARG + YYSTYPE save1, save2; + int save3, save4; + + save1 = yylval; + save2 = yyval; + save3 = yynerrs; + save4 = yyerrflag; +#endif + + yystate = 0; + yychar = -1; + yynerrs = 0; + yyerrflag = 0; + yyp = &yys[-1]; + goto yystack; + +ret0: + yyn = 0; + goto ret; + +ret1: + yyn = 1; + goto ret; + +ret: +#ifndef YYARG + yylval = save1; + yyval = save2; + yynerrs = save3; + yyerrflag = save4; +#endif + return yyn; + +yystack: + /* put a state and value onto the stack */ + if(yydebug >= 4) + fprint(2, "char %s in %s", yytokname(yychar), yystatname(yystate)); + + yyp++; + if(yyp >= &yys[YYMAXDEPTH]) { + yyerror("yacc stack overflow"); + goto ret1; + } + yyp->yys = yystate; + yyp->yyv = yyval; + +yynewstate: + yyn = yypact[yystate]; + if(yyn <= YYFLAG) + goto yydefault; /* simple state */ + if(yychar < 0) +#ifdef YYARG + yychar = yylex1(yyarg); +#else + yychar = yylex1(); +#endif + yyn += yychar; + if(yyn < 0 || yyn >= YYLAST) + goto yydefault; + yyn = yyact[yyn]; + if(yychk[yyn] == yychar) { /* valid shift */ + yychar = -1; + yyval = yylval; + yystate = yyn; + if(yyerrflag > 0) + yyerrflag--; + goto yystack; + } + +yydefault: + /* default state action */ + yyn = yydef[yystate]; + if(yyn == -2) { + if(yychar < 0) +#ifdef YYARG + yychar = yylex1(yyarg); +#else + yychar = yylex1(); +#endif + + /* look through exception table */ + for(yyxi=yyexca;; yyxi+=2) + if(yyxi[0] == -1 && yyxi[1] == yystate) + break; + for(yyxi += 2;; yyxi += 2) { + yyn = yyxi[0]; + if(yyn < 0 || yyn == yychar) + break; + } + yyn = yyxi[1]; + if(yyn < 0) + goto ret0; + } + if(yyn == 0) { + /* error ... attempt to resume parsing */ + switch(yyerrflag) { + case 0: /* brand new error */ + yyerror("syntax error"); + if(yydebug >= 1) { + fprint(2, "%s", yystatname(yystate)); + fprint(2, "saw %s\n", yytokname(yychar)); + } + goto yyerrlab; + yyerrlab: + yynerrs++; + + case 1: + case 2: /* incompletely recovered error ... try again */ + yyerrflag = 3; + + /* find a state where "error" is a legal shift action */ + while(yyp >= yys) { + yyn = yypact[yyp->yys] + YYERRCODE; + if(yyn >= 0 && yyn < YYLAST) { + yystate = yyact[yyn]; /* simulate a shift of "error" */ + if(yychk[yystate] == YYERRCODE) + goto yystack; + } + + /* the current yyp has no shift onn "error", pop stack */ + if(yydebug >= 2) + fprint(2, "error recovery pops state %d, uncovers %d\n", + yyp->yys, (yyp-1)->yys ); + yyp--; + } + /* there is no state on the stack with an error shift ... abort */ + goto ret1; + + case 3: /* no shift yet; clobber input char */ + if(yydebug >= 2) + fprint(2, "error recovery discards %s\n", yytokname(yychar)); + if(yychar == YYEOFCODE) + goto ret1; + yychar = -1; + goto yynewstate; /* try again in the same state */ + } + } + + /* reduction by production yyn */ + if(yydebug >= 2) + fprint(2, "reduce %d in:\n\t%s", yyn, yystatname(yystate)); + + yypt = yyp; + yyp -= yyr2[yyn]; + yyval = (yyp+1)->yyv; + yym = yyn; + + /* consult goto table to find next state */ + yyn = yyr1[yyn]; + yyg = yypgo[yyn]; + yyj = yyg + yyp->yys + 1; + + if(yyj >= YYLAST || yychk[yystate=yyact[yyj]] != -yyn) + yystate = yyact[yyg]; + switch(yym) { + +case 3: +#line 56 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yydone = 1; } break; +case 6: +#line 61 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ date = 1; } break; +case 7: +#line 63 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ originator = 1; } break; +case 8: +#line 65 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ destination = 1; } break; +case 15: +#line 74 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ freenode(yypt[-5].yyv); freenode(yypt[-2].yyv); freenode(yypt[-1].yyv); + usender = yypt[-4].yyv; udate = yypt[-3].yyv; usys = yypt[-0].yyv; + } break; +case 16: +#line 79 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 1); } break; +case 17: +#line 81 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 1); } break; +case 18: +#line 83 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 1); } break; +case 19: +#line 85 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 1); } break; +case 20: +#line 87 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 1); } break; +case 21: +#line 89 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 1); } break; +case 22: +#line 91 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 1); } break; +case 23: +#line 94 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 24: +#line 96 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 25: +#line 99 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 26: +#line 101 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 27: +#line 103 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 28: +#line 105 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 29: +#line 107 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 30: +#line 109 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 31: +#line 111 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 32: +#line 113 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 33: +#line 115 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 34: +#line 117 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 35: +#line 119 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 36: +#line 121 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 37: +#line 124 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 38: +#line 126 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 39: +#line 129 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); received++; } break; +case 40: +#line 131 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); received++; } break; +case 41: +#line 134 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 42: +#line 136 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 43: +#line 139 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 44: +#line 141 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 47: +#line 143 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ messageid = 1; } break; +case 49: +#line 146 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ /* hack to allow same lex for field names and the rest */ + if(badfieldname(yypt[-2].yyv)){ + freenode(yypt[-2].yyv); + freenode(yypt[-1].yyv); + freenode(yypt[-0].yyv); + return 1; + } + newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); + } break; +case 50: +#line 156 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ /* hack to allow same lex for field names and the rest */ + if(badfieldname(yypt[-1].yyv)){ + freenode(yypt[-1].yyv); + freenode(yypt[-0].yyv); + return 1; + } + newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); + } break; +case 52: +#line 167 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv); } break; +case 55: +#line 173 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = link2(yypt[-3].yyv, link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv)); } break; +case 56: +#line 175 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv); } break; +case 58: +#line 179 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv); } break; +case 60: +#line 183 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = link2(yypt[-1].yyv, yypt[-0].yyv); } break; +case 62: +#line 187 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv); } break; +case 63: +#line 189 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = nobody(yypt[-0].yyv); freenode(yypt[-1].yyv); } break; +case 64: +#line 192 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = address(concat(yypt[-2].yyv, concat(yypt[-1].yyv, yypt[-0].yyv))); } break; +case 66: +#line 196 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = concat(yypt[-1].yyv, yypt[-0].yyv); } break; +case 67: +#line 198 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = concat(yypt[-3].yyv, concat(yypt[-2].yyv, concat(yypt[-1].yyv, yypt[-0].yyv))); } break; +case 68: +#line 201 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = address(yypt[-0].yyv); } break; +case 70: +#line 205 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = address(concat(yypt[-2].yyv, concat(yypt[-1].yyv, yypt[-0].yyv)));} break; +case 71: +#line 207 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = address(concat(yypt[-2].yyv, concat(yypt[-1].yyv, yypt[-0].yyv)));} break; +case 75: +#line 215 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = link2(yypt[-1].yyv, yypt[-0].yyv); } break; +case 77: +#line 219 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = link2(yypt[-1].yyv, yypt[-0].yyv); } break; +case 86: +#line 226 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = link3(yypt[-5].yyv, yypt[-3].yyv, link3(yypt[-4].yyv, yypt[-0].yyv, link2(yypt[-2].yyv, yypt[-1].yyv))); } break; +case 88: +#line 230 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv); } break; +case 115: +#line 240 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = link2(yypt[-1].yyv, yypt[-0].yyv); } break; +case 116: +#line 242 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = link2(yypt[-1].yyv, yypt[-0].yyv); } break; + } + goto yystack; /* stack new state and value */ +} blob - /dev/null blob + cddbbc0fae671d8a9ff7a56ae8d6267461491a8a (mode 644) --- /dev/null +++ src/cmd/upas/smtp/rfc822.tab.h @@ -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; + + + blob - /dev/null blob + be77d2bd38efdd36a151fd44ba601ada57451cb2 (mode 644) --- /dev/null +++ src/cmd/upas/smtp/rfc822.y @@ -0,0 +1,778 @@ +%{ +#include "common.h" +#include "smtp.h" +#include + +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; +} blob - /dev/null blob + 54b679bb9249bdd7a34af2bafb2f53e6fd4441a3 (mode 644) --- /dev/null +++ src/cmd/upas/smtp/rmtdns.c @@ -0,0 +1,58 @@ +#include "common.h" +#include + +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]));} + +*/ blob - /dev/null blob + e88154f78a2ae2ec215a72aeb575949cafb1f7da (mode 644) --- /dev/null +++ src/cmd/upas/smtp/smtp.c @@ -0,0 +1,1122 @@ +#include "common.h" +#include "smtp.h" +#include +#include +#include +#include +#include + +static char* connect(char*); +static char* dotls(char*); +static char* doauth(char*); +char* hello(char*, int); +char* mailfrom(char*); +char* rcptto(char*); +char* data(String*, Biobuf*); +void quit(char*); +int getreply(void); +void addhostdom(String*, char*); +String* bangtoat(char*); +String* convertheader(String*); +int printheader(void); +char* domainify(char*, char*); +void putcrnl(char*, int); +char* getcrnl(String*); +int printdate(Node*); +char *rewritezone(char *); +int dBprint(char*, ...); +int dBputc(int); +String* fixrouteaddr(String*, Node*, Node*); +char* expand_addr(char* a); +int ping; +int insecure; + +#define Retry "Retry, Temporary Failure" +#define Giveup "Permanent Failure" + +int debug; /* true if we're debugging */ +String *reply; /* last reply */ +String *toline; +int alarmscale; +int last = 'n'; /* last character sent by putcrnl() */ +int filter; +int trysecure; /* Try to use TLS if the other side supports it */ +int tryauth; /* Try to authenticate, if supported */ +int quitting; /* when error occurs in quit */ +char *quitrv; /* deferred return value when in quit */ +char ddomain[1024]; /* domain name of destination machine */ +char *gdomain; /* domain name of gateway */ +char *uneaten; /* first character after rfc822 headers */ +char *farend; /* system we are trying to send to */ +char *user; /* user we are authenticating as, if authenticating */ +char hostdomain[256]; +Biobuf bin; +Biobuf bout; +Biobuf berr; +Biobuf bfile; + +void +usage(void) +{ + fprint(2, "usage: smtp [-adips] [-uuser] [-hhost] [.domain] net!host[!service] sender rcpt-list\n"); + exits(Giveup); +} + +int +timeout(void *x, char *msg) +{ + USED(x); + syslog(0, "smtp.fail", "interrupt: %s: %s", farend, msg); + if(strstr(msg, "alarm")){ + fprint(2, "smtp timeout: connection to %s timed out\n", farend); + if(quitting) + exits(quitrv); + exits(Retry); + } + if(strstr(msg, "closed pipe")){ + /* call _exits() to prevent Bio from trying to flush closed pipe */ + fprint(2, "smtp timeout: connection closed to %s\n", farend); + if(quitting){ + syslog(0, "smtp.fail", "closed pipe to %s", farend); + _exits(quitrv); + } + _exits(Retry); + } + return 0; +} + +void +removenewline(char *p) +{ + int n = strlen(p)-1; + + if(n < 0) + return; + if(p[n] == '\n') + p[n] = 0; +} + +void +threadmain(int argc, char **argv) +{ + char hellodomain[256]; + char *host, *domain; + String *from; + String *fromm; + String *sender; + char *addr; + char *rv, *trv; + int i, ok, rcvrs; + char **errs; + + alarmscale = 60*1000; /* minutes */ + quotefmtinstall(); + errs = malloc(argc*sizeof(char*)); + reply = s_new(); + host = 0; + ARGBEGIN{ + case 'a': + tryauth = 1; + trysecure = 1; + break; + case 'f': + filter = 1; + break; + case 'd': + debug = 1; + break; + case 'g': + gdomain = ARGF(); + break; + case 'h': + host = ARGF(); + break; + case 'i': + insecure = 1; + break; + case 'p': + alarmscale = 10*1000; /* tens of seconds */ + ping = 1; + break; + case 's': + trysecure = 1; + break; + case 'u': + user = ARGF(); + break; + default: + usage(); + break; + }ARGEND; + + Binit(&berr, 2, OWRITE); + Binit(&bfile, 0, OREAD); + + /* + * get domain and add to host name + */ + if(*argv && **argv=='.') { + domain = *argv; + argv++; argc--; + } else + domain = domainname_read(); + if(host == 0) + host = sysname_read(); + strcpy(hostdomain, domainify(host, domain)); + strcpy(hellodomain, domainify(sysname_read(), domain)); + + /* + * get destination address + */ + if(*argv == 0) + usage(); + addr = *argv++; argc--; + // expand $smtp if necessary + addr = expand_addr(addr); + farend = addr; + + /* + * get sender's machine. + * get sender in internet style. domainify if necessary. + */ + if(*argv == 0) + usage(); + sender = unescapespecial(s_copy(*argv++)); + argc--; + fromm = s_clone(sender); + rv = strrchr(s_to_c(fromm), '!'); + if(rv) + *rv = 0; + else + *s_to_c(fromm) = 0; + from = bangtoat(s_to_c(sender)); + + /* + * send the mail + */ + if(filter){ + Binit(&bout, 1, OWRITE); + rv = data(from, &bfile); + if(rv != 0) + goto error; + exits(0); + } + + /* 10 minutes to get through the initial handshake */ + atnotify(timeout, 1); + + alarm(10*alarmscale); + if((rv = connect(addr)) != 0) + exits(rv); + alarm(10*alarmscale); + if((rv = hello(hellodomain, 0)) != 0) + goto error; + alarm(10*alarmscale); + if((rv = mailfrom(s_to_c(from))) != 0) + goto error; + + ok = 0; + rcvrs = 0; + /* if any rcvrs are ok, we try to send the message */ + for(i = 0; i < argc; i++){ + if((trv = rcptto(argv[i])) != 0){ + /* remember worst error */ + if(rv != Giveup) + rv = trv; + errs[rcvrs] = strdup(s_to_c(reply)); + removenewline(errs[rcvrs]); + } else { + ok++; + errs[rcvrs] = 0; + } + rcvrs++; + } + + /* if no ok rcvrs or worst error is retry, give up */ + if(ok == 0 || rv == Retry) + goto error; + + if(ping){ + quit(0); + exits(0); + } + + rv = data(from, &bfile); + if(rv != 0) + goto error; + quit(0); + if(rcvrs == ok) + exits(0); + + /* + * here when some but not all rcvrs failed + */ + fprint(2, "%s connect to %s:\n", thedate(), addr); + for(i = 0; i < rcvrs; i++){ + if(errs[i]){ + syslog(0, "smtp.fail", "delivery to %s at %s failed: %s", argv[i], addr, errs[i]); + fprint(2, " mail to %s failed: %s", argv[i], errs[i]); + } + } + exits(Giveup); + + /* + * here when all rcvrs failed + */ +error: + removenewline(s_to_c(reply)); + syslog(0, "smtp.fail", "%s to %s failed: %s", + ping ? "ping" : "delivery", + addr, s_to_c(reply)); + fprint(2, "%s connect to %s:\n%s\n", thedate(), addr, s_to_c(reply)); + if(!filter) + quit(rv); + exits(rv); +} + +/* + * connect to the remote host + */ +static char * +connect(char* net) +{ + char buf[256]; + int fd; + + fd = mxdial(net, ddomain, gdomain); + + if(fd < 0){ + rerrstr(buf, sizeof(buf)); + Bprint(&berr, "smtp: %s (%s)\n", buf, net); + syslog(0, "smtp.fail", "%s (%s)", buf, net); + if(strstr(buf, "illegal") + || strstr(buf, "unknown") + || strstr(buf, "can't translate")) + return Giveup; + else + return Retry; + } + Binit(&bin, fd, OREAD); + fd = dup(fd, -1); + Binit(&bout, fd, OWRITE); + return 0; +} + +static char smtpthumbs[] = "/sys/lib/tls/smtp"; +static char smtpexclthumbs[] = "/sys/lib/tls/smtp.exclude"; + +/* + * exchange names with remote host, attempt to + * enable encryption and optionally authenticate. + * not fatal if we can't. + */ +static char * +dotls(char *me) +{ + TLSconn *c; + Thumbprint *goodcerts; + char *h; + int fd; + uchar hash[SHA1dlen]; + + c = mallocz(sizeof(*c), 1); /* Note: not freed on success */ + if (c == nil) + return Giveup; + + dBprint("STARTTLS\r\n"); + if (getreply() != 2) + return Giveup; + + fd = tlsClient(Bfildes(&bout), c); + if (fd < 0) { + syslog(0, "smtp", "tlsClient to %q: %r", ddomain); + return Giveup; + } + goodcerts = initThumbprints(smtpthumbs, smtpexclthumbs); + if (goodcerts == nil) { + free(c); + close(fd); + syslog(0, "smtp", "bad thumbprints in %s", smtpthumbs); + return Giveup; /* how to recover? TLS is started */ + } + + /* compute sha1 hash of remote's certificate, see if we know it */ + sha1(c->cert, c->certlen, hash, nil); + if (!okThumbprint(hash, goodcerts)) { + /* TODO? if not excluded, add hash to thumb list */ + free(c); + close(fd); + h = malloc(2*sizeof hash + 1); + if (h != nil) { + enc16(h, 2*sizeof hash + 1, hash, sizeof hash); + // print("x509 sha1=%s", h); + syslog(0, "smtp", + "remote cert. has bad thumbprint: x509 sha1=%s server=%q", + h, ddomain); + free(h); + } + return Giveup; /* how to recover? TLS is started */ + } + freeThumbprints(goodcerts); + Bterm(&bin); + Bterm(&bout); + + /* + * set up bin & bout to use the TLS fd, i/o upon which generates + * i/o on the original, underlying fd. + */ + Binit(&bin, fd, OREAD); + fd = dup(fd, -1); + Binit(&bout, fd, OWRITE); + + syslog(0, "smtp", "started TLS to %q", ddomain); + return(hello(me, 1)); +} + +static char * +doauth(char *methods) +{ + char *buf, *base64; + int n; + DS ds; + UserPasswd *p; + + dial_string_parse(ddomain, &ds); + + if(user != nil) + p = auth_getuserpasswd(nil, + "proto=pass service=smtp server=%q user=%q", ds.host, user); + else + p = auth_getuserpasswd(nil, + "proto=pass service=smtp server=%q", ds.host); + if (p == nil) + return Giveup; + + if (strstr(methods, "LOGIN")){ + dBprint("AUTH LOGIN\r\n"); + if (getreply() != 3) + return Retry; + + n = strlen(p->user); + base64 = malloc(2*n); + if (base64 == nil) + return Retry; /* Out of memory */ + enc64(base64, 2*n, (uchar *)p->user, n); + dBprint("%s\r\n", base64); + if (getreply() != 3) + return Retry; + + n = strlen(p->passwd); + base64 = malloc(2*n); + if (base64 == nil) + return Retry; /* Out of memory */ + enc64(base64, 2*n, (uchar *)p->passwd, n); + dBprint("%s\r\n", base64); + if (getreply() != 2) + return Retry; + + free(base64); + } + else + if (strstr(methods, "PLAIN")){ + n = strlen(p->user) + strlen(p->passwd) + 3; + buf = malloc(n); + base64 = malloc(2 * n); + if (buf == nil || base64 == nil) { + free(buf); + return Retry; /* Out of memory */ + } + snprint(buf, n, "%c%s%c%s", 0, p->user, 0, p->passwd); + enc64(base64, 2 * n, (uchar *)buf, n - 1); + free(buf); + dBprint("AUTH PLAIN %s\r\n", base64); + free(base64); + if (getreply() != 2) + return Retry; + } + else + return "No supported AUTH method"; + return(0); +} + +char * +hello(char *me, int encrypted) +{ + int ehlo; + String *r; + char *ret, *s, *t; + + if (!encrypted) + switch(getreply()){ + case 2: + break; + case 5: + return Giveup; + default: + return Retry; + } + + ehlo = 1; + Again: + if(ehlo) + dBprint("EHLO %s\r\n", me); + else + dBprint("HELO %s\r\n", me); + switch (getreply()) { + case 2: + break; + case 5: + if(ehlo){ + ehlo = 0; + goto Again; + } + return Giveup; + default: + return Retry; + } + r = s_clone(reply); + if(r == nil) + return Retry; /* Out of memory or couldn't get string */ + + /* Invariant: every line has a newline, a result of getcrlf() */ + for(s = s_to_c(r); (t = strchr(s, '\n')) != nil; s = t + 1){ + *t = '\0'; + for (t = s; *t != '\0'; t++) + *t = toupper(*t); + if(!encrypted && trysecure && + (strcmp(s, "250-STARTTLS") == 0 || + strcmp(s, "250 STARTTLS") == 0)){ + s_free(r); + return(dotls(me)); + } + if(tryauth && (encrypted || insecure) && + (strncmp(s, "250 AUTH", strlen("250 AUTH")) == 0 || + strncmp(s, "250-AUTH", strlen("250 AUTH")) == 0)){ + ret = doauth(s + strlen("250 AUTH ")); + s_free(r); + return ret; + } + } + s_free(r); + return 0; +} + +/* + * report sender to remote + */ +char * +mailfrom(char *from) +{ + if(!returnable(from)) + dBprint("MAIL FROM:<>\r\n"); + else + if(strchr(from, '@')) + dBprint("MAIL FROM:<%s>\r\n", from); + else + dBprint("MAIL FROM:<%s@%s>\r\n", from, hostdomain); + switch(getreply()){ + case 2: + break; + case 5: + return Giveup; + default: + return Retry; + } + return 0; +} + +/* + * report a recipient to remote + */ +char * +rcptto(char *to) +{ + String *s; + + s = unescapespecial(bangtoat(to)); + if(toline == 0) + toline = s_new(); + else + s_append(toline, ", "); + s_append(toline, s_to_c(s)); + if(strchr(s_to_c(s), '@')) + dBprint("RCPT TO:<%s>\r\n", s_to_c(s)); + else { + s_append(toline, "@"); + s_append(toline, ddomain); + dBprint("RCPT TO:<%s@%s>\r\n", s_to_c(s), ddomain); + } + alarm(10*alarmscale); + switch(getreply()){ + case 2: + break; + case 5: + return Giveup; + default: + return Retry; + } + return 0; +} + +static char hex[] = "0123456789abcdef"; + +/* + * send the damn thing + */ +char * +data(String *from, Biobuf *b) +{ + char *buf, *cp; + int i, n, nbytes, bufsize, eof, r; + String *fromline; + char errmsg[Errlen]; + char id[40]; + + /* + * input the header. + */ + + buf = malloc(1); + if(buf == 0){ + s_append(s_restart(reply), "out of memory"); + return Retry; + } + n = 0; + eof = 0; + for(;;){ + cp = Brdline(b, '\n'); + if(cp == nil){ + eof = 1; + break; + } + nbytes = Blinelen(b); + buf = realloc(buf, n+nbytes+1); + if(buf == 0){ + s_append(s_restart(reply), "out of memory"); + return Retry; + } + strncpy(buf+n, cp, nbytes); + n += nbytes; + if(nbytes == 1) /* end of header */ + break; + } + buf[n] = 0; + bufsize = n; + + /* + * parse the header, turn all addresses into @ format + */ + yyinit(buf, n); + yyparse(); + + /* + * print message observing '.' escapes and using \r\n for \n + */ + alarm(20*alarmscale); + if(!filter){ + dBprint("DATA\r\n"); + switch(getreply()){ + case 3: + break; + case 5: + free(buf); + return Giveup; + default: + free(buf); + return Retry; + } + } + /* + * send header. add a message-id, a sender, and a date if there + * isn't one + */ + nbytes = 0; + fromline = convertheader(from); + uneaten = buf; + + srand(truerand()); + if(messageid == 0){ + for(i=0; i<16; i++){ + r = rand()&0xFF; + id[2*i] = hex[r&0xF]; + id[2*i+1] = hex[(r>>4)&0xF]; + } + id[2*i] = '\0'; + nbytes += Bprint(&bout, "Message-ID: <%s@%s>\r\n", id, hostdomain); + if(debug) + Bprint(&berr, "Message-ID: <%s@%s>\r\n", id, hostdomain); + } + + if(originator==0){ + nbytes += Bprint(&bout, "From: %s\r\n", s_to_c(fromline)); + if(debug) + Bprint(&berr, "From: %s\r\n", s_to_c(fromline)); + } + s_free(fromline); + + if(destination == 0 && toline) + if(*s_to_c(toline) == '@'){ /* route addr */ + nbytes += Bprint(&bout, "To: <%s>\r\n", s_to_c(toline)); + if(debug) + Bprint(&berr, "To: <%s>\r\n", s_to_c(toline)); + } else { + nbytes += Bprint(&bout, "To: %s\r\n", s_to_c(toline)); + if(debug) + Bprint(&berr, "To: %s\r\n", s_to_c(toline)); + } + + if(date==0 && udate) + nbytes += printdate(udate); + if (usys) + uneaten = usys->end + 1; + nbytes += printheader(); + if (*uneaten != '\n') + putcrnl("\n", 1); + + /* + * send body + */ + + putcrnl(uneaten, buf+n - uneaten); + nbytes += buf+n - uneaten; + if(eof == 0){ + for(;;){ + n = Bread(b, buf, bufsize); + if(n < 0){ + rerrstr(errmsg, sizeof(errmsg)); + s_append(s_restart(reply), errmsg); + free(buf); + return Retry; + } + if(n == 0) + break; + alarm(10*alarmscale); + putcrnl(buf, n); + nbytes += n; + } + } + free(buf); + if(!filter){ + if(last != '\n') + dBprint("\r\n.\r\n"); + else + dBprint(".\r\n"); + alarm(10*alarmscale); + switch(getreply()){ + case 2: + break; + case 5: + return Giveup; + default: + return Retry; + } + syslog(0, "smtp", "%s sent %d bytes to %s", s_to_c(from), + nbytes, s_to_c(toline));/**/ + } + return 0; +} + +/* + * we're leaving + */ +void +quit(char *rv) +{ + /* 60 minutes to quit */ + quitting = 1; + quitrv = rv; + alarm(60*alarmscale); + dBprint("QUIT\r\n"); + getreply(); + Bterm(&bout); + Bterm(&bfile); +} + +/* + * read a reply into a string, return the reply code + */ +int +getreply(void) +{ + char *line; + int rv; + + reply = s_reset(reply); + for(;;){ + line = getcrnl(reply); + if(line == 0) + return -1; + if(!isdigit(line[0]) || !isdigit(line[1]) || !isdigit(line[2])) + return -1; + if(line[3] != '-') + break; + } + if(debug) + Bflush(&berr); + rv = atoi(line)/100; + return rv; +} +void +addhostdom(String *buf, char *host) +{ + s_append(buf, "@"); + s_append(buf, host); +} + +/* + * Convert from `bang' to `source routing' format. + * + * a.x.y!b.p.o!c!d -> @a.x.y:c!d@b.p.o + */ +String * +bangtoat(char *addr) +{ + String *buf; + register int i; + int j, d; + char *field[128]; + + /* parse the '!' format address */ + buf = s_new(); + for(i = 0; addr; i++){ + field[i] = addr; + addr = strchr(addr, '!'); + if(addr) + *addr++ = 0; + } + if (i==1) { + s_append(buf, field[0]); + return buf; + } + + /* + * count leading domain fields (non-domains don't count) + */ + for(d = 0; d 1){ + addhostdom(buf, field[0]); + for(j=1; j"); + from = a; + } else { + from = s_copy(s_to_c(from)); + addhostdom(from, hostdomain); + } + } else + from = s_copy(s_to_c(from)); + for(f = firstfield; f; f = f->next){ + lastp = 0; + for(p = f->node; p; lastp = p, p = p->next){ + if(!p->addr) + continue; + a = bangtoat(s_to_c(p->s)); + s_free(p->s); + if(strchr(s_to_c(a), '@') == 0) + addhostdom(a, hostdomain); + else if(*s_to_c(a) == '@') + a = fixrouteaddr(a, p->next, lastp); + p->s = a; + } + } + return from; +} +/* + * ensure route addr has brackets around it + */ +String* +fixrouteaddr(String *raddr, Node *next, Node *last) +{ + String *a; + + if(last && last->c == '<' && next && next->c == '>') + return raddr; /* properly formed already */ + + a = s_new(); + s_append(a, "<"); + s_append(a, s_to_c(raddr)); + s_append(a, ">"); + s_free(raddr); + return a; +} + +/* + * print out the parsed header + */ +int +printheader(void) +{ + int n, len; + Field *f; + Node *p; + char *cp; + char c[1]; + + n = 0; + for(f = firstfield; f; f = f->next){ + for(p = f->node; p; p = p->next){ + if(p->s) + n += dBprint("%s", s_to_c(p->s)); + else { + c[0] = p->c; + putcrnl(c, 1); + n++; + } + if(p->white){ + cp = s_to_c(p->white); + len = strlen(cp); + putcrnl(cp, len); + n += len; + } + uneaten = p->end; + } + putcrnl("\n", 1); + n++; + uneaten++; /* skip newline */ + } + return n; +} + +/* + * add a domain onto an name, return the new name + */ +char * +domainify(char *name, char *domain) +{ + static String *s; + char *p; + + if(domain==0 || strchr(name, '.')!=0) + return name; + + s = s_reset(s); + s_append(s, name); + p = strchr(domain, '.'); + if(p == 0){ + s_append(s, "."); + p = domain; + } + s_append(s, p); + return s_to_c(s); +} + +/* + * print message observing '.' escapes and using \r\n for \n + */ +void +putcrnl(char *cp, int n) +{ + int c; + + for(; n; n--, cp++){ + c = *cp; + if(c == '\n') + dBputc('\r'); + else if(c == '.' && last=='\n') + dBputc('.'); + dBputc(c); + last = c; + } +} + +/* + * Get a line including a crnl into a string. Convert crnl into nl. + */ +char * +getcrnl(String *s) +{ + int c; + int count; + + count = 0; + for(;;){ + c = Bgetc(&bin); + if(debug) + Bputc(&berr, c); + switch(c){ + case -1: + s_append(s, "connection closed unexpectedly by remote system"); + s_terminate(s); + return 0; + case '\r': + c = Bgetc(&bin); + if(c == '\n'){ + s_putc(s, c); + if(debug) + Bputc(&berr, c); + count++; + s_terminate(s); + return s->ptr - count; + } + Bungetc(&bin); + s_putc(s, '\r'); + if(debug) + Bputc(&berr, '\r'); + count++; + break; + default: + s_putc(s, c); + count++; + break; + } + } + return 0; +} + +/* + * print out a parsed date + */ +int +printdate(Node *p) +{ + int n, sep = 0; + + n = dBprint("Date: %s,", s_to_c(p->s)); + for(p = p->next; p; p = p->next){ + if(p->s){ + if(sep == 0) { + dBputc(' '); + n++; + } + if (p->next) + n += dBprint("%s", s_to_c(p->s)); + else + n += dBprint("%s", rewritezone(s_to_c(p->s))); + sep = 0; + } else { + dBputc(p->c); + n++; + sep = 1; + } + } + n += dBprint("\r\n"); + return n; +} + +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; +} + +/* + * stolen from libc/port/print.c + */ +#define SIZE 4096 +int +dBprint(char *fmt, ...) +{ + char buf[SIZE], *out; + va_list arg; + int n; + + va_start(arg, fmt); + out = vseprint(buf, buf+SIZE, fmt, arg); + va_end(arg); + if(debug){ + Bwrite(&berr, buf, (long)(out-buf)); + Bflush(&berr); + } + n = Bwrite(&bout, buf, (long)(out-buf)); + Bflush(&bout); + return n; +} + +int +dBputc(int x) +{ + if(debug) + Bputc(&berr, x); + return Bputc(&bout, x); +} + +char* +expand_addr(char* a) +{ + Ndb *db; + Ndbs s; + char *sys, *ret, *proto, *host; + + proto = strtok(a,"!"); + if ( strcmp(proto,"net") != 0 ) { + fprint(2,"unknown proto %s\n",proto); + } + host = strtok(0,"!"); + if ( strcmp(host,"$smtp") == 0 ) { + sys = sysname(); + db = ndbopen(unsharp("#9/ndb/local")); + host = ndbgetvalue(db, &s, "sys", sys, "smtp", nil); + } + ret = malloc(strlen(proto)+strlen(host)+2); + sprint(ret,"%s!%s",proto,host); + + return ret; + +} blob - /dev/null blob + 836192237d97db9a6e5935617c67cd18d117eb8c (mode 644) --- /dev/null +++ src/cmd/upas/smtp/smtp.h @@ -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*); blob - /dev/null blob + 7042c37c3bf3b21105adf9408a1c05bb8b198b4c (mode 644) --- /dev/null +++ src/cmd/upas/smtp/smtpd.c @@ -0,0 +1,1494 @@ +#include "common.h" +#include "smtpd.h" +#include "smtp.h" +#include +#include +#include +#include +#include +#include +#include "../smtp/y.tab.h" + +#define DBGMX 1 + +char *me; +char *him=""; +char *dom; +process *pp; +String *mailer; +NetConnInfo *nci; + +int filterstate = ACCEPT; +int trusted; +int logged; +int rejectcount; +int hardreject; + +Biobuf bin; + +int debug; +int Dflag; +int fflag; +int gflag; +int rflag; +int sflag; +int authenticate; +int authenticated; +int passwordinclear; +char *tlscert; + +List senders; +List rcvers; + +char pipbuf[ERRMAX]; +char *piperror; +int pipemsg(int*); +String* startcmd(void); +int rejectcheck(void); +String* mailerpath(char*); + +static int +catchalarm(void *a, char *msg) +{ + int rv = 1; + + USED(a); + + /* log alarms but continue */ + if(strstr(msg, "alarm")){ + if(senders.first && rcvers.first) + syslog(0, "smtpd", "note: %s->%s: %s", s_to_c(senders.first->p), + s_to_c(rcvers.first->p), msg); + else + syslog(0, "smtpd", "note: %s", msg); + rv = 0; + } + + /* kill the children if there are any */ + if(pp) + syskillpg(pp->pid); + + return rv; +} + + /* override string error functions to do something reasonable */ +void +s_error(char *f, char *status) +{ + char errbuf[Errlen]; + + errbuf[0] = 0; + rerrstr(errbuf, sizeof(errbuf)); + if(f && *f) + reply("452 out of memory %s: %s\r\n", f, errbuf); + else + reply("452 out of memory %s\r\n", errbuf); + syslog(0, "smtpd", "++Malloc failure %s [%s]", him, nci->rsys); + exits(status); +} + +void +main(int argc, char **argv) +{ + char *p, buf[1024]; + char *netdir; + + netdir = nil; + quotefmtinstall(); + ARGBEGIN{ + case 'D': + Dflag++; + break; + case 'd': + debug++; + break; + case 'n': /* log peer ip address */ + netdir = ARGF(); + break; + case 'f': /* disallow relaying */ + fflag = 1; + break; + case 'g': + gflag = 1; + break; + case 'h': /* default domain name */ + dom = ARGF(); + break; + case 'k': /* prohibited ip address */ + p = ARGF(); + if (p) + addbadguy(p); + break; + case 'm': /* set mail command */ + p = ARGF(); + if(p) + mailer = mailerpath(p); + break; + case 'r': + rflag = 1; /* verify sender's domain */ + break; + case 's': /* save blocked messages */ + sflag = 1; + break; + case 'a': + authenticate = 1; + break; + case 'p': + passwordinclear = 1; + break; + case 'c': + tlscert = ARGF(); + break; + case 't': + fprint(2, "%s: the -t option is no longer supported, see -c\n", argv0); + tlscert = "/sys/lib/ssl/smtpd-cert.pem"; + break; + default: + fprint(2, "usage: smtpd [-dfhrs] [-n net] [-c cert]\n"); + exits("usage"); + }ARGEND; + + nci = getnetconninfo(netdir, 0); + if(nci == nil) + sysfatal("can't get remote system's address"); + + if(mailer == nil) + mailer = mailerpath("send"); + + if(debug){ + close(2); + snprint(buf, sizeof(buf), "%s/smtpd", UPASLOG); + if (open(buf, OWRITE) >= 0) { + seek(2, 0, 2); + fprint(2, "%d smtpd %s\n", getpid(), thedate()); + } else + debug = 0; + } + getconf(); + Binit(&bin, 0, OREAD); + + chdir(UPASLOG); + me = sysname_read(); + if(dom == 0 || dom[0] == 0) + dom = domainname_read(); + if(dom == 0 || dom[0] == 0) + dom = me; + sayhi(); + parseinit(); + /* allow 45 minutes to parse the header */ + atnotify(catchalarm, 1); + alarm(45*60*1000); + zzparse(); + exits(0); +} + +void +listfree(List *l) +{ + Link *lp; + Link *next; + + for(lp = l->first; lp; lp = next){ + next = lp->next; + s_free(lp->p); + free(lp); + } + l->first = l->last = 0; +} + +void +listadd(List *l, String *path) +{ + Link *lp; + + lp = (Link *)malloc(sizeof(Link)); + lp->p = path; + lp->next = 0; + + if(l->last) + l->last->next = lp; + else + l->first = lp; + l->last = lp; +} + +#define SIZE 4096 +int +reply(char *fmt, ...) +{ + char buf[SIZE], *out; + va_list arg; + int n; + + va_start(arg, fmt); + out = vseprint(buf, buf+SIZE, fmt, arg); + va_end(arg); + n = (long)(out-buf); + if(debug) { + seek(2, 0, 2); + write(2, buf, n); + } + write(1, buf, n); + return n; +} + +void +reset(void) +{ + if(rejectcheck()) + return; + listfree(&rcvers); + listfree(&senders); + if(filterstate != DIALUP){ + logged = 0; + filterstate = ACCEPT; + } + reply("250 ok\r\n"); +} + +void +sayhi(void) +{ + reply("220 %s SMTP\r\n", dom); +} + +void +hello(String *himp, int extended) +{ + char **mynames; + + him = s_to_c(himp); + syslog(0, "smtpd", "%s from %s as %s", extended ? "ehlo" : "helo", nci->rsys, him); + if(rejectcheck()) + return; + + if(strchr(him, '.') && nci && !trusted && fflag && strcmp(nci->rsys, nci->lsys) != 0){ + /* + * We don't care if he lies about who he is, but it is + * not okay to pretend to be us. Many viruses do this, + * just parroting back what we say in the greeting. + */ + if(strcmp(him, dom) == 0) + goto Liarliar; + for(mynames=sysnames_read(); mynames && *mynames; mynames++){ + if(cistrcmp(*mynames, him) == 0){ + Liarliar: + syslog(0, "smtpd", "Hung up on %s; claimed to be %s", + nci->rsys, him); + reply("554 Liar!\r\n"); + exits("client pretended to be us"); + return; + } + } + } + /* + * it is never acceptable to claim to be "localhost", + * "localhost.localdomain" or "localhost.example.com"; only spammers + * do this. it should be unacceptable to claim any string that doesn't + * look like a domain name (e.g., has at least one dot in it), but + * Microsoft mail software gets this wrong. + */ + if (strcmp(him, "localhost") == 0 || + strcmp(him, "localhost.localdomain") == 0 || + strcmp(him, "localhost.example.com") == 0) + goto Liarliar; + if(strchr(him, '.') == 0 && nci != nil && strchr(nci->rsys, '.') != nil) + him = nci->rsys; + + if(Dflag) + sleep(15*1000); + reply("250%c%s you are %s\r\n", extended ? '-' : ' ', dom, him); + if (extended) { + if(tlscert != nil) + reply("250-STARTTLS\r\n"); + if (passwordinclear) + reply("250 AUTH CRAM-MD5 PLAIN LOGIN\r\n"); + else + reply("250 AUTH CRAM-MD5\r\n"); + } +} + +void +sender(String *path) +{ + String *s; + static char *lastsender; + + if(rejectcheck()) + return; + if (authenticate && !authenticated) { + rejectcount++; + reply("530 Authentication required\r\n"); + return; + } + if(him == 0 || *him == 0){ + rejectcount++; + reply("503 Start by saying HELO, please.\r\n", s_to_c(path)); + return; + } + + /* don't add the domain onto black holes or we will loop */ + if(strchr(s_to_c(path), '!') == 0 && strcmp(s_to_c(path), "/dev/null") != 0){ + s = s_new(); + s_append(s, him); + s_append(s, "!"); + s_append(s, s_to_c(path)); + s_terminate(s); + s_free(path); + path = s; + } + if(shellchars(s_to_c(path))){ + rejectcount++; + reply("503 Bad character in sender address %s.\r\n", s_to_c(path)); + return; + } + + /* + * if the last sender address resulted in a rejection because the sending + * domain didn't exist and this sender has the same domain, reject immediately. + */ + if(lastsender){ + if (strncmp(lastsender, s_to_c(path), strlen(lastsender)) == 0){ + filterstate = REFUSED; + rejectcount++; + reply("554 Sender domain must exist: %s\r\n", s_to_c(path)); + return; + } + free(lastsender); /* different sender domain */ + lastsender = 0; + } + + /* + * see if this ip address, domain name, user name or account is blocked + */ + filterstate = blocked(path); + + logged = 0; + listadd(&senders, path); + reply("250 sender is %s\r\n", s_to_c(path)); +} + +enum { Rcpt, Domain, Ntoks }; + +typedef struct Sender Sender; +struct Sender { + Sender *next; + char *rcpt; + char *domain; +}; +static Sender *sendlist, *sendlast; +static uchar rsysip[IPaddrlen]; + +static int +rdsenders(void) +{ + int lnlen, nf, ok = 1; + char *line, *senderfile; + char *toks[Ntoks]; + Biobuf *sf; + Sender *snd; + static int beenhere = 0; + + if (beenhere) + return 1; + beenhere = 1; + + fmtinstall('I', eipfmt); + parseip(rsysip, nci->rsys); + + /* + * we're sticking with a system-wide sender list because + * per-user lists would require fully resolving recipient + * addresses to determine which users they correspond to + * (barring syntactic conventions). + */ + senderfile = smprint("%s/senders", UPASLIB); + sf = Bopen(senderfile, OREAD); + free(senderfile); + if (sf == nil) + return 1; + while ((line = Brdline(sf, '\n')) != nil) { + if (line[0] == '#' || line[0] == '\n') + continue; + lnlen = Blinelen(sf); + line[lnlen-1] = '\0'; /* clobber newline */ + nf = tokenize(line, toks, nelem(toks)); + if (nf != nelem(toks)) + continue; /* malformed line */ + + snd = malloc(sizeof *snd); + if (snd == nil) + sysfatal("out of memory: %r"); + memset(snd, 0, sizeof *snd); + snd->next = nil; + + if (sendlast == nil) + sendlist = snd; + else + sendlast->next = snd; + sendlast = snd; + snd->rcpt = strdup(toks[Rcpt]); + snd->domain = strdup(toks[Domain]); + } + Bterm(sf); + return ok; +} + +/* + * read (recipient, sender's DNS) pairs from /mail/lib/senders. + * Only allow mail to recipient from any of sender's IPs. + * A recipient not mentioned in the file is always permitted. + */ +static int +senderok(char *rcpt) +{ + int mentioned = 0, matched = 0; + uchar dnsip[IPaddrlen]; + Sender *snd; + Ndbtuple *nt, *next, *first; + + rdsenders(); + for (snd = sendlist; snd != nil; snd = snd->next) { + if (strcmp(rcpt, snd->rcpt) != 0) + continue; + /* + * see if this domain's ips match nci->rsys. + * if not, perhaps a later entry's domain will. + */ + mentioned = 1; + if (parseip(dnsip, snd->domain) != -1 && + memcmp(rsysip, dnsip, IPaddrlen) == 0) + return 1; + /* + * NB: nt->line links form a circular list(!). + * we need to make one complete pass over it to free it all. + */ + first = nt = dnsquery(nci->root, snd->domain, "ip"); + if (first == nil) + continue; + do { + if (strcmp(nt->attr, "ip") == 0 && + parseip(dnsip, nt->val) != -1 && + memcmp(rsysip, dnsip, IPaddrlen) == 0) + matched = 1; + next = nt->line; + free(nt); + nt = next; + } while (nt != first); + } + if (matched) + return 1; + else + return !mentioned; +} + +void +receiver(String *path) +{ + char *sender, *rcpt; + + if(rejectcheck()) + return; + if(him == 0 || *him == 0){ + rejectcount++; + reply("503 Start by saying HELO, please\r\n"); + return; + } + if(senders.last) + sender = s_to_c(senders.last->p); + else + sender = ""; + + if(!recipok(s_to_c(path))){ + rejectcount++; + syslog(0, "smtpd", "Disallowed %s (%s/%s) to blocked name %s", + sender, him, nci->rsys, s_to_c(path)); + reply("550 %s ... user unknown\r\n", s_to_c(path)); + return; + } + rcpt = s_to_c(path); + if (!senderok(rcpt)) { + rejectcount++; + syslog(0, "smtpd", "Disallowed sending IP of %s (%s/%s) to %s", + sender, him, nci->rsys, rcpt); + reply("550 %s ... sending system not allowed\r\n", rcpt); + return; + } + + logged = 0; + /* forwarding() can modify 'path' on loopback request */ + if(filterstate == ACCEPT && (fflag && !authenticated) && forwarding(path)) { + syslog(0, "smtpd", "Bad Forward %s (%s/%s) (%s)", + s_to_c(senders.last->p), him, nci->rsys, s_to_c(path)); + rejectcount++; + reply("550 we don't relay. send to your-path@[] for loopback.\r\n"); + return; + } + listadd(&rcvers, path); + reply("250 receiver is %s\r\n", s_to_c(path)); +} + +void +quit(void) +{ + reply("221 Successful termination\r\n"); + close(0); + exits(0); +} + +void +turn(void) +{ + if(rejectcheck()) + return; + reply("502 TURN unimplemented\r\n"); +} + +void +noop(void) +{ + if(rejectcheck()) + return; + reply("250 Stop wasting my time!\r\n"); +} + +void +help(String *cmd) +{ + if(rejectcheck()) + return; + if(cmd) + s_free(cmd); + reply("250 Read rfc821 and stop wasting my time\r\n"); +} + +void +verify(String *path) +{ + char *p, *q; + char *av[4]; + + if(rejectcheck()) + return; + if(shellchars(s_to_c(path))){ + reply("503 Bad character in address %s.\r\n", s_to_c(path)); + return; + } + av[0] = s_to_c(mailer); + av[1] = "-x"; + av[2] = s_to_c(path); + av[3] = 0; + + pp = noshell_proc_start(av, (stream *)0, outstream(), (stream *)0, 1, 0); + if (pp == 0) { + reply("450 We're busy right now, try later\r\n"); + return; + } + + p = Brdline(pp->std[1]->fp, '\n'); + if(p == 0){ + reply("550 String does not match anything.\r\n"); + } else { + p[Blinelen(pp->std[1]->fp)-1] = 0; + if(strchr(p, ':')) + reply("550 String does not match anything.\r\n"); + else{ + q = strrchr(p, '!'); + if(q) + p = q+1; + reply("250 %s <%s@%s>\r\n", s_to_c(path), p, dom); + } + } + proc_wait(pp); + proc_free(pp); + pp = 0; +} + +/* + * get a line that ends in crnl or cr, turn terminating crnl into a nl + * + * return 0 on EOF + */ +static int +getcrnl(String *s, Biobuf *fp) +{ + int c; + + for(;;){ + c = Bgetc(fp); + if(debug) { + seek(2, 0, 2); + fprint(2, "%c", c); + } + switch(c){ + case -1: + goto out; + case '\r': + c = Bgetc(fp); + if(c == '\n'){ + if(debug) { + seek(2, 0, 2); + fprint(2, "%c", c); + } + s_putc(s, '\n'); + goto out; + } + Bungetc(fp); + s_putc(s, '\r'); + break; + case '\n': + s_putc(s, c); + goto out; + default: + s_putc(s, c); + break; + } + } +out: + s_terminate(s); + return s_len(s); +} + +void +logcall(int nbytes) +{ + Link *l; + String *to, *from; + + to = s_new(); + from = s_new(); + for(l = senders.first; l; l = l->next){ + if(l != senders.first) + s_append(from, ", "); + s_append(from, s_to_c(l->p)); + } + for(l = rcvers.first; l; l = l->next){ + if(l != rcvers.first) + s_append(to, ", "); + s_append(to, s_to_c(l->p)); + } + syslog(0, "smtpd", "[%s/%s] %s sent %d bytes to %s", him, nci->rsys, + s_to_c(from), nbytes, s_to_c(to)); + s_free(to); + s_free(from); +} + +static void +logmsg(char *action) +{ + Link *l; + + if(logged) + return; + + logged = 1; + for(l = rcvers.first; l; l = l->next) + syslog(0, "smtpd", "%s %s (%s/%s) (%s)", action, + s_to_c(senders.last->p), him, nci->rsys, s_to_c(l->p)); +} + +static int +optoutall(int filterstate) +{ + Link *l; + + switch(filterstate){ + case ACCEPT: + case TRUSTED: + return filterstate; + } + + for(l = rcvers.first; l; l = l->next) + if(!optoutofspamfilter(s_to_c(l->p))) + return filterstate; + + return ACCEPT; +} + +String* +startcmd(void) +{ + int n; + Link *l; + char **av; + String *cmd; + char *filename; + + /* + * ignore the filterstate if the all the receivers prefer it. + */ + filterstate = optoutall(filterstate); + + switch (filterstate){ + case BLOCKED: + case DELAY: + rejectcount++; + logmsg("Blocked"); + filename = dumpfile(s_to_c(senders.last->p)); + cmd = s_new(); + s_append(cmd, "cat > "); + s_append(cmd, filename); + pp = proc_start(s_to_c(cmd), instream(), 0, outstream(), 0, 0); + break; + case DIALUP: + logmsg("Dialup"); + rejectcount++; + reply("554 We don't accept mail from dial-up ports.\r\n"); + /* + * we could exit here, because we're never going to accept mail from this + * ip address, but it's unclear that RFC821 allows that. Instead we set + * the hardreject flag and go stupid. + */ + hardreject = 1; + return 0; + case DENIED: + logmsg("Denied"); + rejectcount++; + reply("554-We don't accept mail from %s.\r\n", s_to_c(senders.last->p)); + reply("554 Contact postmaster@%s for more information.\r\n", dom); + return 0; + case REFUSED: + logmsg("Refused"); + rejectcount++; + reply("554 Sender domain must exist: %s\r\n", s_to_c(senders.last->p)); + return 0; + default: + case NONE: + logmsg("Confused"); + rejectcount++; + reply("554-We have had an internal mailer error classifying your message.\r\n"); + reply("554-Filterstate is %d\r\n", filterstate); + reply("554 Contact postmaster@%s for more information.\r\n", dom); + return 0; + case ACCEPT: + case TRUSTED: + /* + * now that all other filters have been passed, + * do grey-list processing. + */ + if(gflag) + vfysenderhostok(); + + /* + * set up mail command + */ + cmd = s_clone(mailer); + n = 3; + for(l = rcvers.first; l; l = l->next) + n++; + av = malloc(n*sizeof(char*)); + if(av == nil){ + reply("450 We're busy right now, try later\n"); + s_free(cmd); + return 0; + } + + n = 0; + av[n++] = s_to_c(cmd); + av[n++] = "-r"; + for(l = rcvers.first; l; l = l->next) + av[n++] = s_to_c(l->p); + av[n] = 0; + /* + * start mail process + */ + pp = noshell_proc_start(av, instream(), outstream(), outstream(), 0, 0); + free(av); + break; + } + if(pp == 0) { + reply("450 We're busy right now, try later\n"); + s_free(cmd); + return 0; + } + return cmd; +} + +/* + * print out a header line, expanding any domainless addresses into + * address@him + */ +char* +bprintnode(Biobuf *b, Node *p) +{ + if(p->s){ + if(p->addr && strchr(s_to_c(p->s), '@') == nil){ + if(Bprint(b, "%s@%s", s_to_c(p->s), him) < 0) + return nil; + } else { + if(Bwrite(b, s_to_c(p->s), s_len(p->s)) < 0) + return nil; + } + }else{ + if(Bputc(b, p->c) < 0) + return nil; + } + if(p->white) + if(Bwrite(b, s_to_c(p->white), s_len(p->white)) < 0) + return nil; + return p->end+1; +} + +static String* +getaddr(Node *p) +{ + for(; p; p = p->next) + if(p->s && p->addr) + return p->s; + return nil; +} + +/* + * add waring headers of the form + * X-warning: + * for any headers that looked like they might be forged. + * + * return byte count of new headers + */ +static int +forgedheaderwarnings(void) +{ + int nbytes; + Field *f; + + nbytes = 0; + + /* warn about envelope sender */ + if(strcmp(s_to_c(senders.last->p), "/dev/null") != 0 && masquerade(senders.last->p, nil)) + nbytes += Bprint(pp->std[0]->fp, "X-warning: suspect envelope domain\n"); + + /* + * check Sender: field. If it's OK, ignore the others because this is an + * exploded mailing list. + */ + for(f = firstfield; f; f = f->next){ + if(f->node->c == SENDER){ + if(masquerade(getaddr(f->node), him)) + nbytes += Bprint(pp->std[0]->fp, "X-warning: suspect Sender: domain\n"); + else + return nbytes; + } + } + + /* check From: */ + for(f = firstfield; f; f = f->next){ + if(f->node->c == FROM && masquerade(getaddr(f->node), him)) + nbytes += Bprint(pp->std[0]->fp, "X-warning: suspect From: domain\n"); + } + return nbytes; +} + +/* + * pipe message to mailer with the following transformations: + * - change \r\n into \n. + * - add sender's domain to any addrs with no domain + * - add a From: if none of From:, Sender:, or Replyto: exists + * - add a Received: line + */ +int +pipemsg(int *byteswritten) +{ + int status; + char *cp; + String *line; + String *hdr; + int n, nbytes; + int sawdot; + Field *f; + Node *p; + Link *l; + + pipesig(&status); /* set status to 1 on write to closed pipe */ + sawdot = 0; + status = 0; + + /* + * add a 'From ' line as envelope + */ + nbytes = 0; + nbytes += Bprint(pp->std[0]->fp, "From %s %s remote from \n", + s_to_c(senders.first->p), thedate()); + + /* + * add our own Received: stamp + */ + nbytes += Bprint(pp->std[0]->fp, "Received: from %s ", him); + if(nci->rsys) + nbytes += Bprint(pp->std[0]->fp, "([%s]) ", nci->rsys); + nbytes += Bprint(pp->std[0]->fp, "by %s; %s\n", me, thedate()); + + /* + * read first 16k obeying '.' escape. we're assuming + * the header will all be there. + */ + line = s_new(); + hdr = s_new(); + while(sawdot == 0 && s_len(hdr) < 16*1024){ + n = getcrnl(s_reset(line), &bin); + + /* eof or error ends the message */ + if(n <= 0) + break; + + /* a line with only a '.' ends the message */ + cp = s_to_c(line); + if(n == 2 && *cp == '.' && *(cp+1) == '\n'){ + sawdot = 1; + break; + } + + s_append(hdr, *cp == '.' ? cp+1 : cp); + } + + /* + * parse header + */ + yyinit(s_to_c(hdr), s_len(hdr)); + yyparse(); + + /* + * Look for masquerades. Let Sender: trump From: to allow mailing list + * forwarded messages. + */ + if(fflag) + nbytes += forgedheaderwarnings(); + + /* + * add an orginator and/or destination if either is missing + */ + if(originator == 0){ + if(senders.last == nil) + Bprint(pp->std[0]->fp, "From: /dev/null@%s\n", him); + else + Bprint(pp->std[0]->fp, "From: %s\n", s_to_c(senders.last->p)); + } + if(destination == 0){ + Bprint(pp->std[0]->fp, "To: "); + for(l = rcvers.first; l; l = l->next){ + if(l != rcvers.first) + Bprint(pp->std[0]->fp, ", "); + Bprint(pp->std[0]->fp, "%s", s_to_c(l->p)); + } + Bprint(pp->std[0]->fp, "\n"); + } + + /* + * add sender's domain to any domainless addresses + * (to avoid forging local addresses) + */ + cp = s_to_c(hdr); + for(f = firstfield; cp != nil && f; f = f->next){ + for(p = f->node; cp != 0 && p; p = p->next) + cp = bprintnode(pp->std[0]->fp, p); + if(status == 0 && Bprint(pp->std[0]->fp, "\n") < 0){ + piperror = "write error"; + status = 1; + } + } + if(cp == nil){ + piperror = "sender domain"; + status = 1; + } + + /* write anything we read following the header */ + if(status == 0 && Bwrite(pp->std[0]->fp, cp, s_to_c(hdr) + s_len(hdr) - cp) < 0){ + piperror = "write error 2"; + status = 1; + } + s_free(hdr); + + /* + * pass rest of message to mailer. take care of '.' + * escapes. + */ + while(sawdot == 0){ + n = getcrnl(s_reset(line), &bin); + + /* eof or error ends the message */ + if(n <= 0) + break; + + /* a line with only a '.' ends the message */ + cp = s_to_c(line); + if(n == 2 && *cp == '.' && *(cp+1) == '\n'){ + sawdot = 1; + break; + } + nbytes += n; + if(status == 0 && Bwrite(pp->std[0]->fp, *cp == '.' ? cp+1 : cp, n) < 0){ + piperror = "write error 3"; + status = 1; + } + } + s_free(line); + if(sawdot == 0){ + /* message did not terminate normally */ + snprint(pipbuf, sizeof pipbuf, "network eof: %r"); + piperror = pipbuf; + syskillpg(pp->pid); + status = 1; + } + + if(status == 0 && Bflush(pp->std[0]->fp) < 0){ + piperror = "write error 4"; + status = 1; + } + stream_free(pp->std[0]); + pp->std[0] = 0; + *byteswritten = nbytes; + pipesigoff(); + if(status && !piperror) + piperror = "write on closed pipe"; + return status; +} + +char* +firstline(char *x) +{ + static char buf[128]; + char *p; + + strncpy(buf, x, sizeof(buf)); + buf[sizeof(buf)-1] = 0; + p = strchr(buf, '\n'); + if(p) + *p = 0; + return buf; +} + +int +sendermxcheck(void) +{ + char *cp, *senddom, *user; + char *who; + int pid; + Waitmsg *w; + + who = s_to_c(senders.first->p); + if(strcmp(who, "/dev/null") == 0){ + /* /dev/null can only send to one rcpt at a time */ + if(rcvers.first != rcvers.last){ + werrstr("rejected: /dev/null sending to multiple recipients"); + return -1; + } + return 0; + } + + if(access("/mail/lib/validatesender", AEXEC) < 0) + return 0; + + senddom = strdup(who); + if((cp = strchr(senddom, '!')) == nil){ + werrstr("rejected: domainless sender %s", who); + free(senddom); + return -1; + } + *cp++ = 0; + user = cp; + + switch(pid = fork()){ + case -1: + werrstr("deferred: fork: %r"); + return -1; + case 0: + /* + * Could add an option with the remote IP address + * to allow validatesender to implement SPF eventually. + */ + execl("/mail/lib/validatesender", "validatesender", + "-n", nci->root, senddom, user, nil); + _exits("exec validatesender: %r"); + default: + break; + } + + free(senddom); + w = wait(); + if(w == nil){ + werrstr("deferred: wait failed: %r"); + return -1; + } + if(w->pid != pid){ + werrstr("deferred: wait returned wrong pid %d != %d", w->pid, pid); + free(w); + return -1; + } + if(w->msg[0] == 0){ + free(w); + return 0; + } + /* + * skip over validatesender 143123132: prefix from rc. + */ + cp = strchr(w->msg, ':'); + if(cp && *(cp+1) == ' ') + werrstr("%s", cp+2); + else + werrstr("%s", w->msg); + free(w); + return -1; +} + +void +data(void) +{ + String *cmd; + String *err; + int status, nbytes; + char *cp, *ep; + char errx[ERRMAX]; + Link *l; + + if(rejectcheck()) + return; + if(senders.last == 0){ + reply("503 Data without MAIL FROM:\r\n"); + rejectcount++; + return; + } + if(rcvers.last == 0){ + reply("503 Data without RCPT TO:\r\n"); + rejectcount++; + return; + } + if(sendermxcheck()){ + rerrstr(errx, sizeof errx); + if(strncmp(errx, "rejected:", 9) == 0) + reply("554 %s\r\n", errx); + else + reply("450 %s\r\n", errx); + for(l=rcvers.first; l; l=l->next) + syslog(0, "smtpd", "[%s/%s] %s -> %s sendercheck: %s", + him, nci->rsys, s_to_c(senders.first->p), + s_to_c(l->p), errx); + rejectcount++; + return; + } + + cmd = startcmd(); + if(cmd == 0) + return; + + reply("354 Input message; end with .\r\n"); + + /* + * allow 145 more minutes to move the data + */ + alarm(145*60*1000); + + status = pipemsg(&nbytes); + + /* + * read any error messages + */ + err = s_new(); + while(s_read_line(pp->std[2]->fp, err)) + ; + + alarm(0); + atnotify(catchalarm, 0); + + status |= proc_wait(pp); + if(debug){ + seek(2, 0, 2); + fprint(2, "%d status %ux\n", getpid(), status); + if(*s_to_c(err)) + fprint(2, "%d error %s\n", getpid(), s_to_c(err)); + } + + /* + * if process terminated abnormally, send back error message + */ + if(status){ + int code; + + if(strstr(s_to_c(err), "mail refused")){ + syslog(0, "smtpd", "++[%s/%s] %s %s refused: %s", him, nci->rsys, + s_to_c(senders.first->p), s_to_c(cmd), firstline(s_to_c(err))); + code = 554; + } else { + syslog(0, "smtpd", "++[%s/%s] %s %s %s%s%sreturned %#q %s", him, nci->rsys, + s_to_c(senders.first->p), s_to_c(cmd), + piperror ? "error during pipemsg: " : "", + piperror ? piperror : "", + piperror ? "; " : "", + pp->waitmsg->msg, firstline(s_to_c(err))); + code = 450; + } + for(cp = s_to_c(err); ep = strchr(cp, '\n'); cp = ep){ + *ep++ = 0; + reply("%d-%s\r\n", code, cp); + } + reply("%d mail process terminated abnormally\r\n", code); + } else { + if(filterstate == BLOCKED) + reply("554 we believe this is spam. we don't accept it.\r\n"); + else + if(filterstate == DELAY) + reply("554 There will be a delay in delivery of this message.\r\n"); + else { + reply("250 sent\r\n"); + logcall(nbytes); + } + } + proc_free(pp); + pp = 0; + s_free(cmd); + s_free(err); + + listfree(&senders); + listfree(&rcvers); +} + +/* + * when we have blocked a transaction based on IP address, there is nothing + * that the sender can do to convince us to take the message. after the + * first rejection, some spammers continually RSET and give a new MAIL FROM: + * filling our logs with rejections. rejectcheck() limits the retries and + * swiftly rejects all further commands after the first 500-series message + * is issued. + */ +int +rejectcheck(void) +{ + + if(rejectcount > MAXREJECTS){ + syslog(0, "smtpd", "Rejected (%s/%s)", him, nci->rsys); + reply("554 too many errors. transaction failed.\r\n"); + exits("errcount"); + } + if(hardreject){ + rejectcount++; + reply("554 We don't accept mail from dial-up ports.\r\n"); + } + return hardreject; +} + +/* + * create abs path of the mailer + */ +String* +mailerpath(char *p) +{ + String *s; + + if(p == nil) + return nil; + if(*p == '/') + return s_copy(p); + s = s_new(); + s_append(s, UPASBIN); + s_append(s, "/"); + s_append(s, p); + return s; +} + +String * +s_dec64(String *sin) +{ + String *sout; + int lin, lout; + lin = s_len(sin); + + /* + * if the string is coming from smtpd.y, it will have no nl. + * if it is coming from getcrnl below, it will have an nl. + */ + if (*(s_to_c(sin)+lin-1) == '\n') + lin--; + sout = s_newalloc(lin+1); + lout = dec64((uchar *)s_to_c(sout), lin, s_to_c(sin), lin); + if (lout < 0) { + s_free(sout); + return nil; + } + sout->ptr = sout->base + lout; + s_terminate(sout); + return sout; +} + +void +starttls(void) +{ + uchar *cert; + int certlen, fd; + TLSconn *conn; + + conn = mallocz(sizeof *conn, 1); + cert = readcert(tlscert, &certlen); + if (conn == nil || cert == nil) { + if (conn != nil) + free(conn); + reply("454 TLS not available\r\n"); + return; + } + reply("220 Go ahead make my day\r\n"); + conn->cert = cert; + conn->certlen = certlen; + fd = tlsServer(Bfildes(&bin), conn); + if (fd < 0) { + free(cert); + free(conn); + syslog(0, "smtpd", "TLS start-up failed with %s", him); + + /* force the client to hang up */ + close(Bfildes(&bin)); /* probably fd 0 */ + close(1); + exits("tls failed"); + } + Bterm(&bin); + Binit(&bin, fd, OREAD); + if (dup(fd, 1) < 0) + fprint(2, "dup of %d failed: %r\n", fd); + passwordinclear = 1; + syslog(0, "smtpd", "started TLS with %s", him); +} + +void +auth(String *mech, String *resp) +{ + Chalstate *chs = nil; + AuthInfo *ai = nil; + String *s_resp1_64 = nil; + String *s_resp2_64 = nil; + String *s_resp1 = nil; + String *s_resp2 = nil; + char *scratch = nil; + char *user, *pass; + + if (rejectcheck()) + goto bomb_out; + + syslog(0, "smtpd", "auth(%s, %s) from %s", s_to_c(mech), + "(protected)", him); + + if (authenticated) { + bad_sequence: + rejectcount++; + reply("503 Bad sequence of commands\r\n"); + goto bomb_out; + } + if (cistrcmp(s_to_c(mech), "plain") == 0) { + + if (!passwordinclear) { + rejectcount++; + reply("538 Encryption required for requested authentication mechanism\r\n"); + goto bomb_out; + } + s_resp1_64 = resp; + if (s_resp1_64 == nil) { + reply("334 \r\n"); + s_resp1_64 = s_new(); + if (getcrnl(s_resp1_64, &bin) <= 0) { + goto bad_sequence; + } + } + s_resp1 = s_dec64(s_resp1_64); + if (s_resp1 == nil) { + rejectcount++; + reply("501 Cannot decode base64\r\n"); + goto bomb_out; + } + memset(s_to_c(s_resp1_64), 'X', s_len(s_resp1_64)); + user = (s_to_c(s_resp1) + strlen(s_to_c(s_resp1)) + 1); + pass = user + (strlen(user) + 1); + ai = auth_userpasswd(user, pass); + authenticated = ai != nil; + memset(pass, 'X', strlen(pass)); + goto windup; + } + else if (cistrcmp(s_to_c(mech), "login") == 0) { + + if (!passwordinclear) { + rejectcount++; + reply("538 Encryption required for requested authentication mechanism\r\n"); + goto bomb_out; + } + if (resp == nil) { + reply("334 VXNlcm5hbWU6\r\n"); + s_resp1_64 = s_new(); + if (getcrnl(s_resp1_64, &bin) <= 0) + goto bad_sequence; + } + reply("334 UGFzc3dvcmQ6\r\n"); + s_resp2_64 = s_new(); + if (getcrnl(s_resp2_64, &bin) <= 0) + goto bad_sequence; + s_resp1 = s_dec64(s_resp1_64); + s_resp2 = s_dec64(s_resp2_64); + memset(s_to_c(s_resp2_64), 'X', s_len(s_resp2_64)); + if (s_resp1 == nil || s_resp2 == nil) { + rejectcount++; + reply("501 Cannot decode base64\r\n"); + goto bomb_out; + } + ai = auth_userpasswd(s_to_c(s_resp1), s_to_c(s_resp2)); + authenticated = ai != nil; + memset(s_to_c(s_resp2), 'X', s_len(s_resp2)); + windup: + if (authenticated) + reply("235 Authentication successful\r\n"); + else { + rejectcount++; + reply("535 Authentication failed\r\n"); + } + goto bomb_out; + } + else if (cistrcmp(s_to_c(mech), "cram-md5") == 0) { + char *resp; + int chal64n; + char *t; + + chs = auth_challenge("proto=cram role=server"); + if (chs == nil) { + rejectcount++; + reply("501 Couldn't get CRAM-MD5 challenge\r\n"); + goto bomb_out; + } + scratch = malloc(chs->nchal * 2 + 1); + chal64n = enc64(scratch, chs->nchal * 2, (uchar *)chs->chal, chs->nchal); + scratch[chal64n] = 0; + reply("334 %s\r\n", scratch); + s_resp1_64 = s_new(); + if (getcrnl(s_resp1_64, &bin) <= 0) + goto bad_sequence; + s_resp1 = s_dec64(s_resp1_64); + if (s_resp1 == nil) { + rejectcount++; + reply("501 Cannot decode base64\r\n"); + goto bomb_out; + } + /* should be of form */ + resp = s_to_c(s_resp1); + t = strchr(resp, ' '); + if (t == nil) { + rejectcount++; + reply("501 Poorly formed CRAM-MD5 response\r\n"); + goto bomb_out; + } + *t++ = 0; + chs->user = resp; + chs->resp = t; + chs->nresp = strlen(t); + ai = auth_response(chs); + authenticated = ai != nil; + goto windup; + } + rejectcount++; + reply("501 Unrecognised authentication type %s\r\n", s_to_c(mech)); +bomb_out: + if (ai) + auth_freeAI(ai); + if (chs) + auth_freechal(chs); + if (scratch) + free(scratch); + if (s_resp1) + s_free(s_resp1); + if (s_resp2) + s_free(s_resp2); + if (s_resp1_64) + s_free(s_resp1_64); + if (s_resp2_64) + s_free(s_resp2_64); +} blob - /dev/null blob + 3a8e60e275b0da16761a8850620aad310863118d (mode 644) --- /dev/null +++ src/cmd/upas/smtp/smtpd.h @@ -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); blob - /dev/null blob + 57b89a02df7bcba2ee9e61245dcd4e51abd89db7 (mode 644) --- /dev/null +++ src/cmd/upas/smtp/smtpd.y @@ -0,0 +1,317 @@ +%{ +#include "common.h" +#include +#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; +} blob - /dev/null blob + 84a8fccc32ac95965ed12ebba544556ca5097d96 (mode 644) --- /dev/null +++ src/cmd/upas/smtp/spam.c @@ -0,0 +1,591 @@ +#include "common.h" +#include "smtpd.h" +#include + +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 \ */ + 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; +} blob - /dev/null blob + a31a0e67b20868f78bf3afa07852d35e9a22ae77 (mode 644) --- /dev/null +++ src/cmd/upas/smtp/y.tab.h @@ -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 blob - /dev/null blob + 612a5965f05976d52a2c8ce6d2b634a8c1091d93 (mode 644) --- /dev/null +++ src/cmd/upas/unesc/mkfile @@ -0,0 +1,17 @@ + +#include + +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); +} blob - /dev/null blob + b17e4578835c211933263eb0740f5b7c05b586fb (mode 644) --- /dev/null +++ src/cmd/upas/vf/mkfile @@ -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 blob - /dev/null blob + db4f5ce8b539eb157655c14b89d103084f16bc51 (mode 644) --- /dev/null +++ src/cmd/upas/vf/vf.c @@ -0,0 +1,1110 @@ +/* + * this is a filter that changes mime types and names of + * suspect executable attachments. + */ +#include "common.h" +#include + +Biobuf in; +Biobuf out; + +typedef struct Mtype Mtype; +typedef struct Hdef Hdef; +typedef struct Hline Hline; +typedef struct Part Part; + +static int badfile(char *name); +static int badtype(char *type); +static void ctype(Part*, Hdef*, char*); +static void cencoding(Part*, Hdef*, char*); +static void cdisposition(Part*, Hdef*, char*); +static int decquoted(char *out, char *in, char *e); +static char* getstring(char *p, String *s, int dolower); +static void init_hdefs(void); +static int isattribute(char **pp, char *attr); +static int latin1toutf(char *out, char *in, char *e); +static String* mkboundary(void); +static Part* part(Part *pp); +static Part* passbody(Part *p, int dobound); +static void passnotheader(void); +static void passunixheader(void); +static Part* problemchild(Part *p); +static void readheader(Part *p); +static Hline* readhl(void); +static void readmtypes(void); +static int save(Part *p, char *file); +static void setfilename(Part *p, char *name); +static char* skiptosemi(char *p); +static char* skipwhite(char *p); +static String* tokenconvert(String *t); +static void writeheader(Part *p, int); + +enum +{ + // encodings + Enone= 0, + Ebase64, + Equoted, + + // disposition possibilities + Dnone= 0, + Dinline, + Dfile, + Dignore, + + PAD64= '=', +}; + +/* + * a message part; either the whole message or a subpart + */ +struct Part +{ + Part *pp; /* parent part */ + Hline *hl; /* linked list of header lines */ + int disposition; + int encoding; + int badfile; + int badtype; + String *boundary; /* boundary for multiparts */ + int blen; + String *charset; /* character set */ + String *type; /* content type */ + String *filename; /* file name */ + Biobuf *tmpbuf; /* diversion input buffer */ +}; + +/* + * a (multi)line header + */ +struct Hline +{ + Hline *next; + String *s; +}; + +/* + * header definitions for parsing + */ +struct Hdef +{ + char *type; + void (*f)(Part*, Hdef*, char*); + int len; +}; + +Hdef hdefs[] = +{ + { "content-type:", ctype, }, + { "content-transfer-encoding:", cencoding, }, + { "content-disposition:", cdisposition, }, + { 0, }, +}; + +/* + * acceptable content types and their extensions + */ +struct Mtype { + Mtype *next; + char *ext; /* extension */ + char *gtype; /* generic content type */ + char *stype; /* specific content type */ + char class; +}; +Mtype *mtypes; + +int justreject; +char *savefile; + +void +main(int argc, char **argv) +{ + ARGBEGIN{ + case 'r': + justreject = 1; + break; + case 's': + savefile = ARGF(); + if(savefile == nil) + exits("usage"); + break; + }ARGEND; + + Binit(&in, 0, OREAD); + Binit(&out, 1, OWRITE); + + init_hdefs(); + readmtypes(); + + /* pass through our standard 'From ' line */ + passunixheader(); + + /* parse with the top level part */ + part(nil); + + exits(0); +} + +void +refuse(void) +{ + postnote(PNGROUP, getpid(), "mail refused: we don't accept executable attachments"); + exits("mail refused: we don't accept executable attachments"); +} + + +/* + * parse a part; returns the ancestor whose boundary terminated + * this part or nil on EOF. + */ +static Part* +part(Part *pp) +{ + Part *p, *np; + + p = mallocz(sizeof *p, 1); + p->pp = pp; + readheader(p); + + if(p->boundary != nil){ + /* the format of a multipart part is always: + * header + * null or ignored body + * boundary + * header + * body + * boundary + * ... + */ + writeheader(p, 1); + np = passbody(p, 1); + if(np != p) + return np; + for(;;){ + np = part(p); + if(np != p) + return np; + } + } else { + /* no boundary */ + /* may still be multipart if this is a forwarded message */ + if(p->type && cistrcmp(s_to_c(p->type), "message/rfc822") == 0){ + /* the format of forwarded message is: + * header + * header + * body + */ + writeheader(p, 1); + passnotheader(); + return part(p); + } else { + /* + * This is the meat. This may be an executable. + * if so, wrap it and change its type + */ + if(p->badtype || p->badfile){ + if(p->badfile == 2){ + if(savefile != nil) + save(p, savefile); + syslog(0, "vf", "vf rejected %s %s", p->type?s_to_c(p->type):"?", + p->filename?s_to_c(p->filename):"?"); + fprint(2, "The mail contained an executable attachment.\n"); + fprint(2, "We refuse all mail containing such.\n"); + refuse(); + } + np = problemchild(p); + if(np != p) + return np; + /* if problemchild returns p, it turns out p is okay: fall thru */ + } + writeheader(p, 1); + return passbody(p, 1); + } + } +} + +/* + * read and parse a complete header + */ +static void +readheader(Part *p) +{ + Hline *hl, **l; + Hdef *hd; + + l = &p->hl; + for(;;){ + hl = readhl(); + if(hl == nil) + break; + *l = hl; + l = &hl->next; + + for(hd = hdefs; hd->type != nil; hd++){ + if(cistrncmp(s_to_c(hl->s), hd->type, hd->len) == 0){ + (*hd->f)(p, hd, s_to_c(hl->s)); + break; + } + } + } +} + +/* + * read a possibly multiline header line + */ +static Hline* +readhl(void) +{ + Hline *hl; + String *s; + char *p; + int n; + + p = Brdline(&in, '\n'); + if(p == nil) + return nil; + n = Blinelen(&in); + if(memchr(p, ':', n) == nil){ + Bseek(&in, -n, 1); + return nil; + } + s = s_nappend(s_new(), p, n); + for(;;){ + p = Brdline(&in, '\n'); + if(p == nil) + break; + n = Blinelen(&in); + if(*p != ' ' && *p != '\t'){ + Bseek(&in, -n, 1); + break; + } + s = s_nappend(s, p, n); + } + hl = malloc(sizeof *hl); + hl->s = s; + hl->next = nil; + return hl; +} + +/* + * write out a complete header + */ +static void +writeheader(Part *p, int xfree) +{ + Hline *hl, *next; + + for(hl = p->hl; hl != nil; hl = next){ + Bprint(&out, "%s", s_to_c(hl->s)); + if(xfree) + s_free(hl->s); + next = hl->next; + if(xfree) + free(hl); + } + if(xfree) + p->hl = nil; +} + +/* + * pass a body through. return if we hit one of our ancestors' + * boundaries or EOF. if we hit a boundary, return a pointer to + * that ancestor. if we hit EOF, return nil. + */ +static Part* +passbody(Part *p, int dobound) +{ + Part *pp; + Biobuf *b; + char *cp; + + for(;;){ + if(p->tmpbuf){ + b = p->tmpbuf; + cp = Brdline(b, '\n'); + if(cp == nil){ + Bterm(b); + p->tmpbuf = nil; + goto Stdin; + } + }else{ + Stdin: + b = ∈ + cp = Brdline(b, '\n'); + } + if(cp == nil) + return nil; + for(pp = p; pp != nil; pp = pp->pp) + if(pp->boundary != nil + && strncmp(cp, s_to_c(pp->boundary), pp->blen) == 0){ + if(dobound) + Bwrite(&out, cp, Blinelen(b)); + else + Bseek(b, -Blinelen(b), 1); + return pp; + } + Bwrite(&out, cp, Blinelen(b)); + } + return nil; +} + +/* + * save the message somewhere + */ +static vlong bodyoff; /* clumsy hack */ +static int +save(Part *p, char *file) +{ + int fd; + char *cp; + + Bterm(&out); + memset(&out, 0, sizeof(out)); + + fd = open(file, OWRITE); + if(fd < 0) + return -1; + seek(fd, 0, 2); + Binit(&out, fd, OWRITE); + cp = ctime(time(0)); + cp[28] = 0; + Bprint(&out, "From virusfilter %s\n", cp); + writeheader(p, 0); + bodyoff = Boffset(&out); + passbody(p, 1); + Bprint(&out, "\n"); + Bterm(&out); + close(fd); + + memset(&out, 0, sizeof out); + Binit(&out, 1, OWRITE); + return 0; +} + +/* + * write to a file but save the fd for passbody. + */ +static char* +savetmp(Part *p) +{ + char buf[40], *name; + int fd; + + strcpy(buf, "/tmp/vf.XXXXXXXXXXX"); + name = mktemp(buf); + if((fd = create(name, OWRITE|OEXCL, 0666)) < 0){ + fprint(2, "error creating temporary file: %r\n"); + refuse(); + } + close(fd); + if(save(p, name) < 0){ + fprint(2, "error saving temporary file: %r\n"); + refuse(); + } + if(p->tmpbuf){ + fprint(2, "error in savetmp: already have tmp file!\n"); + refuse(); + } + p->tmpbuf = Bopen(name, OREAD|ORCLOSE); + if(p->tmpbuf == nil){ + fprint(2, "error reading tempoary file: %r\n"); + refuse(); + } + Bseek(p->tmpbuf, bodyoff, 0); + return strdup(name); +} + +/* + * XXX save the decoded file, run 9 unzip -tf on it, and then + * look at the file list. + */ +static int +runchecker(Part *p) +{ + int pid; + char *name; + Waitmsg *w; + + if(access("/mail/lib/validateattachment", AEXEC) < 0) + return 0; + + name = savetmp(p); + fprint(2, "run checker %s\n", name); + switch(pid = fork()){ + case -1: + sysfatal("fork: %r"); + case 0: + dup(2, 1); + execl("/mail/lib/validateattachment", "validateattachment", name, nil); + _exits("exec failed"); + } + + /* + * Okay to return on error - will let mail through but wrapped. + */ + w = wait(); + if(w == nil){ + syslog(0, "mail", "vf wait failed: %r"); + return 0; + } + if(w->pid != pid){ + syslog(0, "mail", "vf wrong pid %d != %d", w->pid, pid); + return 0; + } + if(p->filename) + name = s_to_c(p->filename); + if(strstr(w->msg, "discard")){ + syslog(0, "mail", "vf validateattachment rejected %s", name); + refuse(); + } + if(strstr(w->msg, "accept")){ + syslog(0, "mail", "vf validateattachment accepted %s", name); + return 1; + } + free(w); + return 0; +} + +/* + * emit a multipart Part that explains the problem + */ +static Part* +problemchild(Part *p) +{ + Part *np; + Hline *hl; + String *boundary; + char *cp; + + /* + * We don't know whether the attachment is okay. + * If there's an external checker, let it have a crack at it. + */ + if(runchecker(p) > 0) + return p; + +fprint(2, "x\n"); + syslog(0, "mail", "vf wrapped %s %s", p->type?s_to_c(p->type):"?", + p->filename?s_to_c(p->filename):"?"); +fprint(2, "x\n"); + + boundary = mkboundary(); +fprint(2, "x\n"); + /* print out non-mime headers */ + for(hl = p->hl; hl != nil; hl = hl->next) + if(cistrncmp(s_to_c(hl->s), "content-", 8) != 0) + Bprint(&out, "%s", s_to_c(hl->s)); + +fprint(2, "x\n"); + /* add in our own multipart headers and message */ + Bprint(&out, "Content-Type: multipart/mixed;\n"); + Bprint(&out, "\tboundary=\"%s\"\n", s_to_c(boundary)); + Bprint(&out, "Content-Disposition: inline\n"); + Bprint(&out, "\n"); + Bprint(&out, "This is a multi-part message in MIME format.\n"); + Bprint(&out, "--%s\n", s_to_c(boundary)); + Bprint(&out, "Content-Disposition: inline\n"); + Bprint(&out, "Content-Type: text/plain; charset=\"US-ASCII\"\n"); + Bprint(&out, "Content-Transfer-Encoding: 7bit\n"); + Bprint(&out, "\n"); + Bprint(&out, "from postmaster@%s:\n", sysname()); + Bprint(&out, "The following attachment had content that we can't\n"); + Bprint(&out, "prove to be harmless. To avoid possible automatic\n"); + Bprint(&out, "execution, we changed the content headers.\n"); + Bprint(&out, "The original header was:\n\n"); + + /* print out original header lines */ + for(hl = p->hl; hl != nil; hl = hl->next) + if(cistrncmp(s_to_c(hl->s), "content-", 8) == 0) + Bprint(&out, "\t%s", s_to_c(hl->s)); + Bprint(&out, "--%s\n", s_to_c(boundary)); + + /* change file name */ + if(p->filename) + s_append(p->filename, ".suspect"); + else + p->filename = s_copy("file.suspect"); + + /* print out new header */ + Bprint(&out, "Content-Type: application/octet-stream\n"); + Bprint(&out, "Content-Disposition: attachment; filename=\"%s\"\n", s_to_c(p->filename)); + switch(p->encoding){ + case Enone: + break; + case Ebase64: + Bprint(&out, "Content-Transfer-Encoding: base64\n"); + break; + case Equoted: + Bprint(&out, "Content-Transfer-Encoding: quoted-printable\n"); + break; + } + +fprint(2, "z\n"); + /* pass the body */ + np = passbody(p, 0); + +fprint(2, "w\n"); + /* add the new boundary and the original terminator */ + Bprint(&out, "--%s--\n", s_to_c(boundary)); + if(np && np->boundary){ + cp = Brdline(&in, '\n'); + Bwrite(&out, cp, Blinelen(&in)); + } + +fprint(2, "a %p\n", np); + return np; +} + +static int +isattribute(char **pp, char *attr) +{ + char *p; + int n; + + n = strlen(attr); + p = *pp; + if(cistrncmp(p, attr, n) != 0) + return 0; + p += n; + while(*p == ' ') + p++; + if(*p++ != '=') + return 0; + while(*p == ' ') + p++; + *pp = p; + return 1; +} + +/* + * parse content type header + */ +static void +ctype(Part *p, Hdef *h, char *cp) +{ + String *s; + + cp += h->len; + cp = skipwhite(cp); + + p->type = s_new(); + cp = getstring(cp, p->type, 1); + if(badtype(s_to_c(p->type))) + p->badtype = 1; + + while(*cp){ + if(isattribute(&cp, "boundary")){ + s = s_new(); + cp = getstring(cp, s, 0); + p->boundary = s_reset(p->boundary); + s_append(p->boundary, "--"); + s_append(p->boundary, s_to_c(s)); + p->blen = s_len(p->boundary); + s_free(s); + } else if(cistrncmp(cp, "multipart", 9) == 0){ + /* + * the first unbounded part of a multipart message, + * the preamble, is not displayed or saved + */ + } else if(isattribute(&cp, "name")){ + setfilename(p, cp); + } else if(isattribute(&cp, "charset")){ + if(p->charset == nil) + p->charset = s_new(); + cp = getstring(cp, s_reset(p->charset), 0); + } + + cp = skiptosemi(cp); + } +} + +/* + * parse content encoding header + */ +static void +cencoding(Part *m, Hdef *h, char *p) +{ + p += h->len; + p = skipwhite(p); + if(cistrncmp(p, "base64", 6) == 0) + m->encoding = Ebase64; + else if(cistrncmp(p, "quoted-printable", 16) == 0) + m->encoding = Equoted; +} + +/* + * parse content disposition header + */ +static void +cdisposition(Part *p, Hdef *h, char *cp) +{ + cp += h->len; + cp = skipwhite(cp); + while(*cp){ + if(cistrncmp(cp, "inline", 6) == 0){ + p->disposition = Dinline; + } else if(cistrncmp(cp, "attachment", 10) == 0){ + p->disposition = Dfile; + } else if(cistrncmp(cp, "filename=", 9) == 0){ + cp += 9; + setfilename(p, cp); + } + cp = skiptosemi(cp); + } + +} + +static void +setfilename(Part *p, char *name) +{ + if(p->filename == nil) + p->filename = s_new(); + getstring(name, s_reset(p->filename), 0); + p->filename = tokenconvert(p->filename); + p->badfile = badfile(s_to_c(p->filename)); +} + +static char* +skipwhite(char *p) +{ + while(isspace(*p)) + p++; + return p; +} + +static char* +skiptosemi(char *p) +{ + while(*p && *p != ';') + p++; + while(*p == ';' || isspace(*p)) + p++; + return p; +} + +/* + * parse a possibly "'d string from a header. A + * ';' terminates the string. + */ +static char* +getstring(char *p, String *s, int dolower) +{ + s = s_reset(s); + p = skipwhite(p); + if(*p == '"'){ + p++; + for(;*p && *p != '"'; p++) + if(dolower) + s_putc(s, tolower(*p)); + else + s_putc(s, *p); + if(*p == '"') + p++; + s_terminate(s); + + return p; + } + + for(; *p && !isspace(*p) && *p != ';'; p++) + if(dolower) + s_putc(s, tolower(*p)); + else + s_putc(s, *p); + s_terminate(s); + + return p; +} + +static void +init_hdefs(void) +{ + Hdef *hd; + static int already; + + if(already) + return; + already = 1; + + for(hd = hdefs; hd->type != nil; hd++) + hd->len = strlen(hd->type); +} + +/* + * 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); +} + +/* + * skip blank lines till header + */ +static void +passnotheader(void) +{ + char *cp; + int i, n; + + while((cp = Brdline(&in, '\n')) != nil){ + n = Blinelen(&in); + for(i = 0; i < n-1; i++) + if(cp[i] != ' ' && cp[i] != '\t' && cp[i] != '\r'){ + Bseek(&in, -n, 1); + return; + } + Bwrite(&out, cp, n); + } +} + +/* + * pass unix header lines + */ +static void +passunixheader(void) +{ + char *p; + int n; + + while((p = Brdline(&in, '\n')) != nil){ + n = Blinelen(&in); + if(strncmp(p, "From ", 5) != 0){ + Bseek(&in, -n, 1); + break; + } + Bwrite(&out, p, n); + } +} + +/* + * Read mime types + */ +static void +readmtypes(void) +{ + Biobuf *b; + char *p; + char *f[6]; + Mtype *m; + Mtype **l; + + b = Bopen(unsharp("#9/sys/lib/mimetype"), OREAD); + if(b == nil) + return; + + l = &mtypes; + while((p = Brdline(b, '\n')) != nil){ + if(*p == '#') + continue; + p[Blinelen(b)-1] = 0; + if(tokenize(p, f, nelem(f)) < 5) + continue; + m = mallocz(sizeof *m, 1); + if(m == nil) + goto err; + m->ext = strdup(f[0]); + if(m->ext == 0) + goto err; + m->gtype = strdup(f[1]); + if(m->gtype == 0) + goto err; + m->stype = strdup(f[2]); + if(m->stype == 0) + goto err; + m->class = *f[4]; + *l = m; + l = &(m->next); + } + Bterm(b); + return; +err: + if(m == nil) + return; + free(m->ext); + free(m->gtype); + free(m->stype); + free(m); + Bterm(b); +} + +/* + * if the class is 'm' or 'y', accept it + * if the class is 'p' check a previous extension + * otherwise, filename is bad + */ +static int +badfile(char *name) +{ + char *p; + Mtype *m; + int rv; + + p = strrchr(name, '.'); + if(p == nil) + return 0; + + for(m = mtypes; m != nil; m = m->next) + if(cistrcmp(p, m->ext) == 0){ + switch(m->class){ + case 'm': + case 'y': + return 0; + case 'p': + *p = 0; + rv = badfile(name); + *p = '.'; + return rv; + case 'r': + return 2; + } + } + if(justreject) + return 0; + return 1; +} + +/* + * if the class is 'm' or 'y' or 'p', accept it + * otherwise, filename is bad + */ +static int +badtype(char *type) +{ + Mtype *m; + char *s, *fix; + int rv = 1; + + if(justreject) + return 0; + + fix = s = strchr(type, '/'); + if(s != nil) + *s++ = 0; + else + s = "-"; + + for(m = mtypes; m != nil; m = m->next){ + if(cistrcmp(type, m->gtype) != 0) + continue; + if(cistrcmp(s, m->stype) != 0) + continue; + switch(m->class){ + case 'y': + case 'p': + case 'm': + rv = 0; + break; + } + break; + } + + if(fix != nil) + *fix = '/'; + return rv; +} + +/* rfc2047 non-ascii */ +typedef struct Charset Charset; +struct Charset { + char *name; + int len; + int convert; +} charsets[] = +{ + { "us-ascii", 8, 1, }, + { "utf-8", 5, 0, }, + { "iso-8859-1", 10, 1, }, +}; + +/* + * convert to UTF if need be + */ +static String* +tokenconvert(String *t) +{ + String *s; + char decoded[1024]; + char utfbuf[2*1024]; + int i, len; + char *e; + char *token; + + token = s_to_c(t); + len = s_len(t); + + if(token[0] != '=' || token[1] != '?' || + token[len-2] != '?' || token[len-1] != '=') + goto err; + e = token+len-2; + token += 2; + + // bail if we don't understand the character set + for(i = 0; i < nelem(charsets); i++) + if(cistrncmp(charsets[i].name, token, charsets[i].len) == 0) + if(token[charsets[i].len] == '?'){ + token += charsets[i].len + 1; + break; + } + if(i >= nelem(charsets)) + goto err; + + // bail if it doesn't fit + if(strlen(token) > sizeof(decoded)-1) + goto err; + + // bail if we don't understand the encoding + if(cistrncmp(token, "b?", 2) == 0){ + token += 2; + len = dec64((uchar*)decoded, sizeof(decoded), token, e-token); + decoded[len] = 0; + } else if(cistrncmp(token, "q?", 2) == 0){ + token += 2; + len = decquoted(decoded, token, e); + if(len > 0 && decoded[len-1] == '\n') + len--; + decoded[len] = 0; + } else + goto err; + + s = nil; + switch(charsets[i].convert){ + case 0: + s = s_copy(decoded); + break; + case 1: + s = s_new(); + latin1toutf(utfbuf, decoded, decoded+len); + s_append(s, utfbuf); + break; + } + + return s; +err: + return s_clone(t); +} + +/* + * decode quoted + */ +enum +{ + Self= 1, + Hex= 2, +}; +uchar tableqp[256]; + +static void +initquoted(void) +{ + int c; + + memset(tableqp, 0, 256); + for(c = ' '; c <= '<'; c++) + tableqp[c] = Self; + for(c = '>'; c <= '~'; c++) + tableqp[c] = Self; + tableqp['\t'] = Self; + tableqp['='] = Hex; +} + +static int +hex2int(int x) +{ + if(x >= '0' && x <= '9') + return x - '0'; + if(x >= 'A' && x <= 'F') + return (x - 'A') + 10; + if(x >= 'a' && x <= 'f') + return (x - 'a') + 10; + return 0; +} + +static char* +decquotedline(char *out, char *in, char *e) +{ + int c, soft; + + /* dump trailing white space */ + while(e >= in && (*e == ' ' || *e == '\t' || *e == '\r' || *e == '\n')) + e--; + + /* trailing '=' means no newline */ + if(*e == '='){ + soft = 1; + e--; + } else + soft = 0; + + while(in <= e){ + c = (*in++) & 0xff; + switch(tableqp[c]){ + case Self: + *out++ = c; + break; + case Hex: + c = hex2int(*in++)<<4; + c |= hex2int(*in++); + *out++ = c; + break; + } + } + if(!soft) + *out++ = '\n'; + *out = 0; + + return out; +} + +static int +decquoted(char *out, char *in, char *e) +{ + char *p, *nl; + + if(tableqp[' '] == 0) + initquoted(); + + p = out; + while((nl = strchr(in, '\n')) != nil && nl < e){ + p = decquotedline(p, in, nl); + in = nl + 1; + } + if(in < e) + p = decquotedline(p, in, e-1); + + // make sure we end with a new line + if(*(p-1) != '\n'){ + *p++ = '\n'; + *p = 0; + } + + return p - out; +} + +/* translate latin1 directly since it fits neatly in utf */ +static int +latin1toutf(char *out, char *in, char *e) +{ + Rune r; + char *p; + + p = out; + for(; in < e; in++){ + r = (*in) & 0xff; + p += runetochar(p, &r); + } + *p = 0; + return p - out; +}