Thanks to John Cummings.
This commit is contained in:
parent
cd37451963
commit
5cdb17983a
94 changed files with 26853 additions and 0 deletions
274
src/cmd/upas/smtp/greylist.c
Normal file
274
src/cmd/upas/smtp/greylist.c
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
#include "common.h"
|
||||
#include "smtpd.h"
|
||||
#include "smtp.h"
|
||||
#include <ctype.h>
|
||||
#include <ip.h>
|
||||
#include <ndb.h>
|
||||
|
||||
typedef struct {
|
||||
int existed; /* these two are distinct to cope with errors */
|
||||
int created;
|
||||
int noperm;
|
||||
long mtime; /* mod time, iff it already existed */
|
||||
} Greysts;
|
||||
|
||||
/*
|
||||
* There's a bit of a problem with yahoo; they apparently have a vast
|
||||
* pool of machines that all run the same queue(s), so a 451 retry can
|
||||
* come from a different IP address for many, many retries, and it can
|
||||
* take ~5 hours for the same IP to call us back. Various other goofballs,
|
||||
* notably the IEEE, try to send mail just before 9 AM, then refuse to try
|
||||
* again until after 5 PM. Doh!
|
||||
*/
|
||||
enum {
|
||||
Nonspammax = 14*60*60, /* must call back within this time if real */
|
||||
};
|
||||
static char whitelist[] = "/mail/lib/whitelist";
|
||||
|
||||
/*
|
||||
* matches ip addresses or subnets in whitelist against nci->rsys.
|
||||
* ignores comments and blank lines in /mail/lib/whitelist.
|
||||
*/
|
||||
static int
|
||||
onwhitelist(void)
|
||||
{
|
||||
int lnlen;
|
||||
char *line, *parse;
|
||||
char input[128];
|
||||
uchar ip[IPaddrlen], ipmasked[IPaddrlen];
|
||||
uchar mask4[IPaddrlen], addr4[IPaddrlen];
|
||||
uchar mask[IPaddrlen], addr[IPaddrlen], addrmasked[IPaddrlen];
|
||||
Biobuf *wl;
|
||||
static int beenhere;
|
||||
static allzero[IPaddrlen];
|
||||
|
||||
if (!beenhere) {
|
||||
beenhere = 1;
|
||||
fmtinstall('I', eipfmt);
|
||||
}
|
||||
|
||||
parseip(ip, nci->rsys);
|
||||
wl = Bopen(whitelist, OREAD);
|
||||
if (wl == nil)
|
||||
return 1;
|
||||
while ((line = Brdline(wl, '\n')) != nil) {
|
||||
if (line[0] == '#' || line[0] == '\n')
|
||||
continue;
|
||||
lnlen = Blinelen(wl);
|
||||
line[lnlen-1] = '\0'; /* clobber newline */
|
||||
|
||||
/* default mask is /32 (v4) or /128 (v6) for bare IP */
|
||||
parse = line;
|
||||
if (strchr(line, '/') == nil) {
|
||||
strncpy(input, line, sizeof input - 5);
|
||||
if (strchr(line, '.') != nil)
|
||||
strcat(input, "/32");
|
||||
else
|
||||
strcat(input, "/128");
|
||||
parse = input;
|
||||
}
|
||||
/* sorry, dave; where's parsecidr for v4 or v6? */
|
||||
v4parsecidr(addr4, mask4, parse);
|
||||
v4tov6(addr, addr4);
|
||||
v4tov6(mask, mask4);
|
||||
|
||||
maskip(addr, mask, addrmasked);
|
||||
maskip(ip, mask, ipmasked);
|
||||
if (memcmp(ipmasked, addrmasked, IPaddrlen) == 0)
|
||||
break;
|
||||
}
|
||||
Bterm(wl);
|
||||
return line != nil;
|
||||
}
|
||||
|
||||
static int mkdirs(char *);
|
||||
|
||||
/*
|
||||
* if any directories leading up to path don't exist, create them.
|
||||
* modifies but restores path.
|
||||
*/
|
||||
static int
|
||||
mkpdirs(char *path)
|
||||
{
|
||||
int rv = 0;
|
||||
char *sl = strrchr(path, '/');
|
||||
|
||||
if (sl != nil) {
|
||||
*sl = '\0';
|
||||
rv = mkdirs(path);
|
||||
*sl = '/';
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/*
|
||||
* if path or any directories leading up to it don't exist, create them.
|
||||
* modifies but restores path.
|
||||
*/
|
||||
static int
|
||||
mkdirs(char *path)
|
||||
{
|
||||
int fd;
|
||||
|
||||
if (access(path, AEXIST) >= 0)
|
||||
return 0;
|
||||
|
||||
/* make presumed-missing intermediate directories */
|
||||
if (mkpdirs(path) < 0)
|
||||
return -1;
|
||||
|
||||
/* make final directory */
|
||||
fd = create(path, OREAD, 0777|DMDIR);
|
||||
if (fd < 0)
|
||||
/*
|
||||
* we may have lost a race; if the directory now exists,
|
||||
* it's okay.
|
||||
*/
|
||||
return access(path, AEXIST) < 0? -1: 0;
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long
|
||||
getmtime(char *file)
|
||||
{
|
||||
long mtime = -1;
|
||||
Dir *ds = dirstat(file);
|
||||
|
||||
if (ds != nil) {
|
||||
mtime = ds->mtime;
|
||||
free(ds);
|
||||
}
|
||||
return mtime;
|
||||
}
|
||||
|
||||
static void
|
||||
tryaddgrey(char *file, Greysts *gsp)
|
||||
{
|
||||
int fd = create(file, OWRITE|OEXCL, 0444|DMEXCL);
|
||||
|
||||
gsp->created = (fd >= 0);
|
||||
if (fd >= 0) {
|
||||
close(fd);
|
||||
gsp->existed = 0; /* just created; couldn't have existed */
|
||||
} else {
|
||||
/*
|
||||
* why couldn't we create file? it must have existed
|
||||
* (or we were denied perm on parent dir.).
|
||||
* if it existed, fill in gsp->mtime; otherwise
|
||||
* make presumed-missing intermediate directories.
|
||||
*/
|
||||
gsp->existed = access(file, AEXIST) >= 0;
|
||||
if (gsp->existed)
|
||||
gsp->mtime = getmtime(file);
|
||||
else if (mkpdirs(file) < 0)
|
||||
gsp->noperm = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
addgreylist(char *file, Greysts *gsp)
|
||||
{
|
||||
tryaddgrey(file, gsp);
|
||||
if (!gsp->created && !gsp->existed && !gsp->noperm)
|
||||
/* retry the greylist entry with parent dirs created */
|
||||
tryaddgrey(file, gsp);
|
||||
}
|
||||
|
||||
static int
|
||||
recentcall(Greysts *gsp)
|
||||
{
|
||||
long delay = time(0) - gsp->mtime;
|
||||
|
||||
if (!gsp->existed)
|
||||
return 0;
|
||||
/* reject immediate call-back; spammers are doing that now */
|
||||
return delay >= 30 && delay <= Nonspammax;
|
||||
}
|
||||
|
||||
/*
|
||||
* policy: if (caller-IP, my-IP, rcpt) is not on the greylist,
|
||||
* reject this message as "451 temporary failure". if the caller is real,
|
||||
* he'll retry soon, otherwise he's a spammer.
|
||||
* at the first rejection, create a greylist entry for (my-ip, caller-ip,
|
||||
* rcpt, time), where time is the file's mtime. if they call back and there's
|
||||
* already a greylist entry, and it's within the allowed interval,
|
||||
* add their IP to the append-only whitelist.
|
||||
*
|
||||
* greylist files can be removed at will; at worst they'll cause a few
|
||||
* extra retries.
|
||||
*/
|
||||
|
||||
static int
|
||||
isrcptrecent(char *rcpt)
|
||||
{
|
||||
char *user;
|
||||
char file[256];
|
||||
Greysts gs;
|
||||
Greysts *gsp = &gs;
|
||||
|
||||
if (rcpt[0] == '\0' || strchr(rcpt, '/') != nil ||
|
||||
strcmp(rcpt, ".") == 0 || strcmp(rcpt, "..") == 0)
|
||||
return 0;
|
||||
|
||||
/* shorten names to fit pre-fossil or pre-9p2000 file servers */
|
||||
user = strrchr(rcpt, '!');
|
||||
if (user == nil)
|
||||
user = rcpt;
|
||||
else
|
||||
user++;
|
||||
|
||||
/* check & try to update the grey list entry */
|
||||
snprint(file, sizeof file, "/mail/grey/%s/%s/%s",
|
||||
nci->lsys, nci->rsys, user);
|
||||
memset(gsp, 0, sizeof *gsp);
|
||||
addgreylist(file, gsp);
|
||||
|
||||
/* if on greylist already and prior call was recent, add to whitelist */
|
||||
if (gsp->existed && recentcall(gsp)) {
|
||||
syslog(0, "smtpd",
|
||||
"%s/%s was grey; adding IP to white", nci->rsys, rcpt);
|
||||
return 1;
|
||||
} else if (gsp->existed)
|
||||
syslog(0, "smtpd", "call for %s/%s was seconds ago or long ago",
|
||||
nci->rsys, rcpt);
|
||||
else
|
||||
syslog(0, "smtpd", "no call registered for %s/%s; registering",
|
||||
nci->rsys, rcpt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
vfysenderhostok(void)
|
||||
{
|
||||
char *fqdn;
|
||||
int recent = 0;
|
||||
Link *l;
|
||||
|
||||
if (onwhitelist())
|
||||
return;
|
||||
|
||||
for (l = rcvers.first; l; l = l->next)
|
||||
if (isrcptrecent(s_to_c(l->p)))
|
||||
recent = 1;
|
||||
|
||||
/* if on greylist already and prior call was recent, add to whitelist */
|
||||
if (recent) {
|
||||
int fd = create(whitelist, OWRITE, 0666|DMAPPEND);
|
||||
|
||||
if (fd >= 0) {
|
||||
seek(fd, 0, 2); /* paranoia */
|
||||
if ((fqdn = csgetvalue(nil, "ip", nci->rsys, "dom", nil)) != nil)
|
||||
fprint(fd, "# %s\n%s\n\n", fqdn, nci->rsys);
|
||||
else
|
||||
fprint(fd, "# unknown\n%s\n\n", nci->rsys);
|
||||
close(fd);
|
||||
}
|
||||
} else {
|
||||
syslog(0, "smtpd",
|
||||
"no recent call from %s for a rcpt; rejecting with temporary failure",
|
||||
nci->rsys);
|
||||
reply("451 please try again soon from the same IP.\r\n");
|
||||
exits("no recent call for a rcpt");
|
||||
}
|
||||
}
|
||||
54
src/cmd/upas/smtp/mkfile
Normal file
54
src/cmd/upas/smtp/mkfile
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<$PLAN9/src/mkhdr
|
||||
|
||||
TARG = # smtpd\
|
||||
smtp\
|
||||
|
||||
OFILES=
|
||||
|
||||
LIB=../common/libcommon.a\
|
||||
$PLAN9/lib/libthread.a # why do i have to explicitly put this?
|
||||
|
||||
HFILES=../common/common.h\
|
||||
../common/sys.h\
|
||||
smtpd.h\
|
||||
smtp.h\
|
||||
|
||||
BIN=$PLAN9/bin/upas
|
||||
UPDATE=\
|
||||
greylist.c\
|
||||
mkfile\
|
||||
mxdial.c\
|
||||
rfc822.y\
|
||||
rmtdns.c\
|
||||
smtpd.y\
|
||||
spam.c\
|
||||
$HFILES\
|
||||
${OFILES:%.$O=%.c}\
|
||||
${TARG:%=%.c}\
|
||||
|
||||
<$PLAN9/src/mkmany
|
||||
CFLAGS=$CFLAGS -I../common -D'SPOOL="/mail"'
|
||||
|
||||
$O.smtpd: smtpd.tab.$O rmtdns.$O spam.$O rfc822.tab.$O greylist.$O
|
||||
$O.smtp: rfc822.tab.$O mxdial.$O
|
||||
|
||||
smtpd.$O: smtpd.h
|
||||
|
||||
smtp.$O to.$O: smtp.h
|
||||
|
||||
smtpd.tab.c: smtpd.y smtpd.h
|
||||
yacc -o xxx smtpd.y
|
||||
sed 's/yy/zz/g' < xxx > $target
|
||||
rm xxx
|
||||
|
||||
rfc822.tab.c: rfc822.y smtp.h
|
||||
9 yacc -d -o $target rfc822.y
|
||||
|
||||
clean:V:
|
||||
rm -f *.[$OS] [$OS].$TARG smtpd.tab.c rfc822.tab.c y.tab.? y.debug $TARG
|
||||
|
||||
../common/libcommon.a$O:
|
||||
@{
|
||||
cd ../common
|
||||
mk
|
||||
}
|
||||
333
src/cmd/upas/smtp/mxdial.c
Normal file
333
src/cmd/upas/smtp/mxdial.c
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
#include "common.h"
|
||||
#include <ndb.h>
|
||||
#include "smtp.h" /* to publish dial_string_parse */
|
||||
|
||||
enum
|
||||
{
|
||||
Nmx= 16,
|
||||
Maxstring= 256,
|
||||
};
|
||||
|
||||
typedef struct Mx Mx;
|
||||
struct Mx
|
||||
{
|
||||
char host[256];
|
||||
char ip[24];
|
||||
int pref;
|
||||
};
|
||||
static Mx mx[Nmx];
|
||||
|
||||
Ndb *db;
|
||||
extern int debug;
|
||||
|
||||
static int mxlookup(DS*, char*);
|
||||
static int mxlookup1(DS*, char*);
|
||||
static int compar(void*, void*);
|
||||
static int callmx(DS*, char*, char*);
|
||||
static void expand_meta(DS *ds);
|
||||
extern int cistrcmp(char*, char*);
|
||||
|
||||
int
|
||||
mxdial(char *addr, char *ddomain, char *gdomain)
|
||||
{
|
||||
int fd;
|
||||
DS ds;
|
||||
char err[Errlen];
|
||||
|
||||
addr = netmkaddr(addr, 0, "smtp");
|
||||
dial_string_parse(addr, &ds);
|
||||
|
||||
/* try connecting to destination or any of it's mail routers */
|
||||
fd = callmx(&ds, addr, ddomain);
|
||||
|
||||
/* try our mail gateway */
|
||||
rerrstr(err, sizeof(err));
|
||||
if(fd < 0 && gdomain && strstr(err, "can't translate") != 0) {
|
||||
fprint(2,"dialing %s\n",gdomain);
|
||||
fd = dial(netmkaddr(gdomain, 0, "smtp"), 0, 0, 0);
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
/*
|
||||
* take an address and return all the mx entries for it,
|
||||
* most preferred first
|
||||
*/
|
||||
static int
|
||||
callmx(DS *ds, char *dest, char *domain)
|
||||
{
|
||||
int fd, i, nmx;
|
||||
char addr[Maxstring];
|
||||
|
||||
/* get a list of mx entries */
|
||||
nmx = mxlookup(ds, domain);
|
||||
if(nmx < 0){
|
||||
/* dns isn't working, don't just dial */
|
||||
return -1;
|
||||
}
|
||||
if(nmx == 0){
|
||||
if(debug)
|
||||
fprint(2, "mxlookup returns nothing\n");
|
||||
return dial(dest, 0, 0, 0);
|
||||
}
|
||||
|
||||
/* refuse to honor loopback addresses given by dns */
|
||||
for(i = 0; i < nmx; i++){
|
||||
if(strcmp(mx[i].ip, "127.0.0.1") == 0){
|
||||
if(debug)
|
||||
fprint(2, "mxlookup returns loopback\n");
|
||||
werrstr("illegal: domain lists 127.0.0.1 as mail server");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* sort by preference */
|
||||
if(nmx > 1)
|
||||
qsort(mx, nmx, sizeof(Mx), compar);
|
||||
|
||||
/* dial each one in turn */
|
||||
for(i = 0; i < nmx; i++){
|
||||
snprint(addr, sizeof(addr), "%s/%s!%s!%s", ds->netdir, ds->proto,
|
||||
mx[i].host, ds->service);
|
||||
if(debug)
|
||||
fprint(2, "mxdial trying %s\n", addr);
|
||||
fd = dial(addr, 0, 0, 0);
|
||||
if(fd >= 0)
|
||||
return fd;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* call the dns process and have it try to resolve the mx request
|
||||
*
|
||||
* this routine knows about the firewall and tries inside and outside
|
||||
* dns's seperately.
|
||||
*/
|
||||
static int
|
||||
mxlookup(DS *ds, char *domain)
|
||||
{
|
||||
int n;
|
||||
|
||||
/* just in case we find no domain name */
|
||||
strcpy(domain, ds->host);
|
||||
|
||||
if(ds->netdir){
|
||||
n = mxlookup1(ds, domain);
|
||||
} else {
|
||||
ds->netdir = "/net";
|
||||
n = mxlookup1(ds, domain);
|
||||
if(n == 0) {
|
||||
ds->netdir = "/net.alt";
|
||||
n = mxlookup1(ds, domain);
|
||||
}
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
static int
|
||||
mxlookup1(DS *ds, char *domain)
|
||||
{
|
||||
char buf[1024];
|
||||
char dnsname[Maxstring];
|
||||
char *fields[4];
|
||||
int i, n, fd, nmx;
|
||||
|
||||
snprint(dnsname, sizeof dnsname, "%s/dns", ds->netdir);
|
||||
|
||||
fd = open(dnsname, ORDWR);
|
||||
if(fd < 0)
|
||||
return 0;
|
||||
|
||||
nmx = 0;
|
||||
snprint(buf, sizeof(buf), "%s mx", ds->host);
|
||||
if(debug)
|
||||
fprint(2, "sending %s '%s'\n", dnsname, buf);
|
||||
n = write(fd, buf, strlen(buf));
|
||||
if(n < 0){
|
||||
rerrstr(buf, sizeof buf);
|
||||
if(debug)
|
||||
fprint(2, "dns: %s\n", buf);
|
||||
if(strstr(buf, "dns failure")){
|
||||
/* if dns fails for the mx lookup, we have to stop */
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* get any mx entries
|
||||
*/
|
||||
seek(fd, 0, 0);
|
||||
while(nmx < Nmx && (n = read(fd, buf, sizeof(buf)-1)) > 0){
|
||||
buf[n] = 0;
|
||||
if(debug)
|
||||
fprint(2, "dns mx: %s\n", buf);
|
||||
n = getfields(buf, fields, 4, 1, " \t");
|
||||
if(n < 4)
|
||||
continue;
|
||||
|
||||
if(strchr(domain, '.') == 0)
|
||||
strcpy(domain, fields[0]);
|
||||
|
||||
strncpy(mx[nmx].host, fields[3], sizeof(mx[n].host)-1);
|
||||
mx[nmx].pref = atoi(fields[2]);
|
||||
nmx++;
|
||||
}
|
||||
if(debug)
|
||||
fprint(2, "dns mx; got %d entries\n", nmx);
|
||||
}
|
||||
|
||||
/*
|
||||
* no mx record? try name itself.
|
||||
*/
|
||||
/*
|
||||
* BUG? If domain has no dots, then we used to look up ds->host
|
||||
* but return domain instead of ds->host in the list. Now we return
|
||||
* ds->host. What will this break?
|
||||
*/
|
||||
if(nmx == 0){
|
||||
mx[0].pref = 1;
|
||||
strncpy(mx[0].host, ds->host, sizeof(mx[0].host));
|
||||
nmx++;
|
||||
}
|
||||
|
||||
/*
|
||||
* look up all ip addresses
|
||||
*/
|
||||
for(i = 0; i < nmx; i++){
|
||||
seek(fd, 0, 0);
|
||||
snprint(buf, sizeof buf, "%s ip", mx[i].host);
|
||||
mx[i].ip[0] = 0;
|
||||
if(write(fd, buf, strlen(buf)) < 0)
|
||||
goto no;
|
||||
seek(fd, 0, 0);
|
||||
if((n = read(fd, buf, sizeof buf-1)) < 0)
|
||||
goto no;
|
||||
buf[n] = 0;
|
||||
if(getfields(buf, fields, 4, 1, " \t") < 3)
|
||||
goto no;
|
||||
strncpy(mx[i].ip, fields[2], sizeof(mx[i].ip)-1);
|
||||
continue;
|
||||
|
||||
no:
|
||||
/* remove mx[i] and go around again */
|
||||
nmx--;
|
||||
mx[i] = mx[nmx];
|
||||
i--;
|
||||
}
|
||||
return nmx;
|
||||
}
|
||||
|
||||
static int
|
||||
compar(void *a, void *b)
|
||||
{
|
||||
return ((Mx*)a)->pref - ((Mx*)b)->pref;
|
||||
}
|
||||
|
||||
/* break up an address to its component parts */
|
||||
void
|
||||
dial_string_parse(char *str, DS *ds)
|
||||
{
|
||||
char *p, *p2;
|
||||
|
||||
strncpy(ds->buf, str, sizeof(ds->buf));
|
||||
ds->buf[sizeof(ds->buf)-1] = 0;
|
||||
|
||||
p = strchr(ds->buf, '!');
|
||||
if(p == 0) {
|
||||
ds->netdir = 0;
|
||||
ds->proto = "net";
|
||||
ds->host = ds->buf;
|
||||
} else {
|
||||
if(*ds->buf != '/'){
|
||||
ds->netdir = 0;
|
||||
ds->proto = ds->buf;
|
||||
} else {
|
||||
for(p2 = p; *p2 != '/'; p2--)
|
||||
;
|
||||
*p2++ = 0;
|
||||
ds->netdir = ds->buf;
|
||||
ds->proto = p2;
|
||||
}
|
||||
*p = 0;
|
||||
ds->host = p + 1;
|
||||
}
|
||||
ds->service = strchr(ds->host, '!');
|
||||
if(ds->service)
|
||||
*ds->service++ = 0;
|
||||
if(*ds->host == '$')
|
||||
expand_meta(ds);
|
||||
}
|
||||
|
||||
#if 0 /* jpc */
|
||||
static void
|
||||
expand_meta(DS *ds)
|
||||
{
|
||||
char buf[128], cs[128], *net, *p;
|
||||
int fd, n;
|
||||
|
||||
net = ds->netdir;
|
||||
if(!net)
|
||||
net = "/net";
|
||||
|
||||
if(debug)
|
||||
fprint(2, "expanding %s!%s\n", net, ds->host);
|
||||
snprint(cs, sizeof(cs), "%s/cs", net);
|
||||
if((fd = open(cs, ORDWR)) == -1){
|
||||
if(debug)
|
||||
fprint(2, "open %s: %r\n", cs);
|
||||
syslog(0, "smtp", "cannot open %s: %r", cs);
|
||||
return;
|
||||
}
|
||||
|
||||
snprint(buf, sizeof(buf), "!ipinfo %s", ds->host+1); // +1 to skip $
|
||||
if(write(fd, buf, strlen(buf)) <= 0){
|
||||
if(debug)
|
||||
fprint(2, "write %s: %r\n", cs);
|
||||
syslog(0, "smtp", "%s to %s - write failed: %r", buf, cs);
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
seek(fd, 0, 0);
|
||||
if((n = read(fd, ds->expand, sizeof(ds->expand)-1)) < 0){
|
||||
if(debug)
|
||||
fprint(2, "read %s: %r\n", cs);
|
||||
syslog(0, "smtp", "%s - read failed: %r", cs);
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
ds->expand[n] = 0;
|
||||
if((p = strchr(ds->expand, '=')) == nil){
|
||||
if(debug)
|
||||
fprint(2, "response %s: %s\n", cs, ds->expand);
|
||||
syslog(0, "smtp", "%q from %s - bad response: %r", ds->expand, cs);
|
||||
return;
|
||||
}
|
||||
ds->host = p+1;
|
||||
|
||||
/* take only first one returned (quasi-bug) */
|
||||
if((p = strchr(ds->host, ' ')) != nil)
|
||||
*p = 0;
|
||||
}
|
||||
#endif /* jpc */
|
||||
|
||||
static void
|
||||
expand_meta(DS *ds)
|
||||
{
|
||||
Ndb *db;
|
||||
Ndbs s;
|
||||
char *sys, *smtpserver;
|
||||
|
||||
sys = sysname();
|
||||
db = ndbopen(unsharp("#9/ndb/local"));
|
||||
fprint(2,"%s",ds->host);
|
||||
smtpserver = ndbgetvalue(db, &s, "sys", sys, "smtp", nil);
|
||||
snprint(ds->host,128,"%s",smtpserver);
|
||||
fprint(2," exanded to %s\n",ds->host);
|
||||
|
||||
}
|
||||
1260
src/cmd/upas/smtp/rfc822.tab.c
Normal file
1260
src/cmd/upas/smtp/rfc822.tab.c
Normal file
File diff suppressed because it is too large
Load diff
98
src/cmd/upas/smtp/rfc822.tab.h
Normal file
98
src/cmd/upas/smtp/rfc822.tab.h
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
/* A Bison parser, made by GNU Bison 2.0. */
|
||||
|
||||
/* Skeleton parser for Yacc-like parsing with Bison,
|
||||
Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
Boston, MA 02111-1307, USA. */
|
||||
|
||||
/* As a special exception, when this file is copied by Bison into a
|
||||
Bison output file, you may use that output file without restriction.
|
||||
This special exception was added by the Free Software Foundation
|
||||
in version 1.24 of Bison. */
|
||||
|
||||
/* Tokens. */
|
||||
#ifndef YYTOKENTYPE
|
||||
# define YYTOKENTYPE
|
||||
/* Put the tokens into the symbol table, so that GDB and other debuggers
|
||||
know about them. */
|
||||
enum yytokentype {
|
||||
WORD = 258,
|
||||
DATE = 259,
|
||||
RESENT_DATE = 260,
|
||||
RETURN_PATH = 261,
|
||||
FROM = 262,
|
||||
SENDER = 263,
|
||||
REPLY_TO = 264,
|
||||
RESENT_FROM = 265,
|
||||
RESENT_SENDER = 266,
|
||||
RESENT_REPLY_TO = 267,
|
||||
SUBJECT = 268,
|
||||
TO = 269,
|
||||
CC = 270,
|
||||
BCC = 271,
|
||||
RESENT_TO = 272,
|
||||
RESENT_CC = 273,
|
||||
RESENT_BCC = 274,
|
||||
REMOTE = 275,
|
||||
PRECEDENCE = 276,
|
||||
MIMEVERSION = 277,
|
||||
CONTENTTYPE = 278,
|
||||
MESSAGEID = 279,
|
||||
RECEIVED = 280,
|
||||
MAILER = 281,
|
||||
BADTOKEN = 282
|
||||
};
|
||||
#endif
|
||||
#define WORD 258
|
||||
#define DATE 259
|
||||
#define RESENT_DATE 260
|
||||
#define RETURN_PATH 261
|
||||
#define FROM 262
|
||||
#define SENDER 263
|
||||
#define REPLY_TO 264
|
||||
#define RESENT_FROM 265
|
||||
#define RESENT_SENDER 266
|
||||
#define RESENT_REPLY_TO 267
|
||||
#define SUBJECT 268
|
||||
#define TO 269
|
||||
#define CC 270
|
||||
#define BCC 271
|
||||
#define RESENT_TO 272
|
||||
#define RESENT_CC 273
|
||||
#define RESENT_BCC 274
|
||||
#define REMOTE 275
|
||||
#define PRECEDENCE 276
|
||||
#define MIMEVERSION 277
|
||||
#define CONTENTTYPE 278
|
||||
#define MESSAGEID 279
|
||||
#define RECEIVED 280
|
||||
#define MAILER 281
|
||||
#define BADTOKEN 282
|
||||
|
||||
|
||||
|
||||
|
||||
#if ! defined (YYSTYPE) && ! defined (YYSTYPE_IS_DECLARED)
|
||||
typedef int YYSTYPE;
|
||||
# define yystype YYSTYPE /* obsolescent; will be withdrawn */
|
||||
# define YYSTYPE_IS_DECLARED 1
|
||||
# define YYSTYPE_IS_TRIVIAL 1
|
||||
#endif
|
||||
|
||||
extern YYSTYPE yylval;
|
||||
|
||||
|
||||
|
||||
778
src/cmd/upas/smtp/rfc822.y
Normal file
778
src/cmd/upas/smtp/rfc822.y
Normal file
|
|
@ -0,0 +1,778 @@
|
|||
%{
|
||||
#include "common.h"
|
||||
#include "smtp.h"
|
||||
#include <ctype.h>
|
||||
|
||||
char *yylp; /* next character to be lex'd */
|
||||
int yydone; /* tell yylex to give up */
|
||||
char *yybuffer; /* first parsed character */
|
||||
char *yyend; /* end of buffer to be parsed */
|
||||
Node *root;
|
||||
Field *firstfield;
|
||||
Field *lastfield;
|
||||
Node *usender;
|
||||
Node *usys;
|
||||
Node *udate;
|
||||
char *startfield, *endfield;
|
||||
int originator;
|
||||
int destination;
|
||||
int date;
|
||||
int received;
|
||||
int messageid;
|
||||
%}
|
||||
|
||||
%term WORD
|
||||
%term DATE
|
||||
%term RESENT_DATE
|
||||
%term RETURN_PATH
|
||||
%term FROM
|
||||
%term SENDER
|
||||
%term REPLY_TO
|
||||
%term RESENT_FROM
|
||||
%term RESENT_SENDER
|
||||
%term RESENT_REPLY_TO
|
||||
%term SUBJECT
|
||||
%term TO
|
||||
%term CC
|
||||
%term BCC
|
||||
%term RESENT_TO
|
||||
%term RESENT_CC
|
||||
%term RESENT_BCC
|
||||
%term REMOTE
|
||||
%term PRECEDENCE
|
||||
%term MIMEVERSION
|
||||
%term CONTENTTYPE
|
||||
%term MESSAGEID
|
||||
%term RECEIVED
|
||||
%term MAILER
|
||||
%term BADTOKEN
|
||||
%start msg
|
||||
%%
|
||||
|
||||
msg : fields
|
||||
| unixfrom '\n' fields
|
||||
;
|
||||
fields : '\n'
|
||||
{ yydone = 1; }
|
||||
| field '\n'
|
||||
| field '\n' fields
|
||||
;
|
||||
field : dates
|
||||
{ date = 1; }
|
||||
| originator
|
||||
{ originator = 1; }
|
||||
| destination
|
||||
{ destination = 1; }
|
||||
| subject
|
||||
| optional
|
||||
| ignored
|
||||
| received
|
||||
| precedence
|
||||
| error '\n' field
|
||||
;
|
||||
unixfrom : FROM route_addr unix_date_time REMOTE FROM word
|
||||
{ freenode($1); freenode($4); freenode($5);
|
||||
usender = $2; udate = $3; usys = $6;
|
||||
}
|
||||
;
|
||||
originator : REPLY_TO ':' address_list
|
||||
{ newfield(link3($1, $2, $3), 1); }
|
||||
| RETURN_PATH ':' route_addr
|
||||
{ newfield(link3($1, $2, $3), 1); }
|
||||
| FROM ':' mailbox_list
|
||||
{ newfield(link3($1, $2, $3), 1); }
|
||||
| SENDER ':' mailbox
|
||||
{ newfield(link3($1, $2, $3), 1); }
|
||||
| RESENT_REPLY_TO ':' address_list
|
||||
{ newfield(link3($1, $2, $3), 1); }
|
||||
| RESENT_SENDER ':' mailbox
|
||||
{ newfield(link3($1, $2, $3), 1); }
|
||||
| RESENT_FROM ':' mailbox
|
||||
{ newfield(link3($1, $2, $3), 1); }
|
||||
;
|
||||
dates : DATE ':' date_time
|
||||
{ newfield(link3($1, $2, $3), 0); }
|
||||
| RESENT_DATE ':' date_time
|
||||
{ newfield(link3($1, $2, $3), 0); }
|
||||
;
|
||||
destination : TO ':'
|
||||
{ newfield(link2($1, $2), 0); }
|
||||
| TO ':' address_list
|
||||
{ newfield(link3($1, $2, $3), 0); }
|
||||
| RESENT_TO ':'
|
||||
{ newfield(link2($1, $2), 0); }
|
||||
| RESENT_TO ':' address_list
|
||||
{ newfield(link3($1, $2, $3), 0); }
|
||||
| CC ':'
|
||||
{ newfield(link2($1, $2), 0); }
|
||||
| CC ':' address_list
|
||||
{ newfield(link3($1, $2, $3), 0); }
|
||||
| RESENT_CC ':'
|
||||
{ newfield(link2($1, $2), 0); }
|
||||
| RESENT_CC ':' address_list
|
||||
{ newfield(link3($1, $2, $3), 0); }
|
||||
| BCC ':'
|
||||
{ newfield(link2($1, $2), 0); }
|
||||
| BCC ':' address_list
|
||||
{ newfield(link3($1, $2, $3), 0); }
|
||||
| RESENT_BCC ':'
|
||||
{ newfield(link2($1, $2), 0); }
|
||||
| RESENT_BCC ':' address_list
|
||||
{ newfield(link3($1, $2, $3), 0); }
|
||||
;
|
||||
subject : SUBJECT ':' things
|
||||
{ newfield(link3($1, $2, $3), 0); }
|
||||
| SUBJECT ':'
|
||||
{ newfield(link2($1, $2), 0); }
|
||||
;
|
||||
received : RECEIVED ':' things
|
||||
{ newfield(link3($1, $2, $3), 0); received++; }
|
||||
| RECEIVED ':'
|
||||
{ newfield(link2($1, $2), 0); received++; }
|
||||
;
|
||||
precedence : PRECEDENCE ':' things
|
||||
{ newfield(link3($1, $2, $3), 0); }
|
||||
| PRECEDENCE ':'
|
||||
{ newfield(link2($1, $2), 0); }
|
||||
;
|
||||
ignored : ignoredhdr ':' things
|
||||
{ newfield(link3($1, $2, $3), 0); }
|
||||
| ignoredhdr ':'
|
||||
{ newfield(link2($1, $2), 0); }
|
||||
;
|
||||
ignoredhdr : MIMEVERSION | CONTENTTYPE | MESSAGEID { messageid = 1; } | MAILER
|
||||
;
|
||||
optional : fieldwords ':' things
|
||||
{ /* hack to allow same lex for field names and the rest */
|
||||
if(badfieldname($1)){
|
||||
freenode($1);
|
||||
freenode($2);
|
||||
freenode($3);
|
||||
return 1;
|
||||
}
|
||||
newfield(link3($1, $2, $3), 0);
|
||||
}
|
||||
| fieldwords ':'
|
||||
{ /* hack to allow same lex for field names and the rest */
|
||||
if(badfieldname($1)){
|
||||
freenode($1);
|
||||
freenode($2);
|
||||
return 1;
|
||||
}
|
||||
newfield(link2($1, $2), 0);
|
||||
}
|
||||
;
|
||||
address_list : address
|
||||
| address_list ',' address
|
||||
{ $$ = link3($1, $2, $3); }
|
||||
;
|
||||
address : mailbox
|
||||
| group
|
||||
;
|
||||
group : phrase ':' address_list ';'
|
||||
{ $$ = link2($1, link3($2, $3, $4)); }
|
||||
| phrase ':' ';'
|
||||
{ $$ = link3($1, $2, $3); }
|
||||
;
|
||||
mailbox_list : mailbox
|
||||
| mailbox_list ',' mailbox
|
||||
{ $$ = link3($1, $2, $3); }
|
||||
;
|
||||
mailbox : route_addr
|
||||
| phrase brak_addr
|
||||
{ $$ = link2($1, $2); }
|
||||
| brak_addr
|
||||
;
|
||||
brak_addr : '<' route_addr '>'
|
||||
{ $$ = link3($1, $2, $3); }
|
||||
| '<' '>'
|
||||
{ $$ = nobody($2); freenode($1); }
|
||||
;
|
||||
route_addr : route ':' at_addr
|
||||
{ $$ = address(concat($1, concat($2, $3))); }
|
||||
| addr_spec
|
||||
;
|
||||
route : '@' domain
|
||||
{ $$ = concat($1, $2); }
|
||||
| route ',' '@' domain
|
||||
{ $$ = concat($1, concat($2, concat($3, $4))); }
|
||||
;
|
||||
addr_spec : local_part
|
||||
{ $$ = address($1); }
|
||||
| at_addr
|
||||
;
|
||||
at_addr : local_part '@' domain
|
||||
{ $$ = address(concat($1, concat($2, $3)));}
|
||||
| at_addr '@' domain
|
||||
{ $$ = address(concat($1, concat($2, $3)));}
|
||||
;
|
||||
local_part : word
|
||||
;
|
||||
domain : word
|
||||
;
|
||||
phrase : word
|
||||
| phrase word
|
||||
{ $$ = link2($1, $2); }
|
||||
;
|
||||
things : thing
|
||||
| things thing
|
||||
{ $$ = link2($1, $2); }
|
||||
;
|
||||
thing : word | '<' | '>' | '@' | ':' | ';' | ','
|
||||
;
|
||||
date_time : things
|
||||
;
|
||||
unix_date_time : word word word unix_time word word
|
||||
{ $$ = link3($1, $3, link3($2, $6, link2($4, $5))); }
|
||||
;
|
||||
unix_time : word
|
||||
| unix_time ':' word
|
||||
{ $$ = link3($1, $2, $3); }
|
||||
;
|
||||
word : WORD | DATE | RESENT_DATE | RETURN_PATH | FROM | SENDER
|
||||
| REPLY_TO | RESENT_FROM | RESENT_SENDER | RESENT_REPLY_TO
|
||||
| TO | CC | BCC | RESENT_TO | RESENT_CC | RESENT_BCC | REMOTE | SUBJECT
|
||||
| PRECEDENCE | MIMEVERSION | CONTENTTYPE | MESSAGEID | RECEIVED | MAILER
|
||||
;
|
||||
fieldwords : fieldword
|
||||
| WORD
|
||||
| fieldwords fieldword
|
||||
{ $$ = link2($1, $2); }
|
||||
| fieldwords word
|
||||
{ $$ = link2($1, $2); }
|
||||
;
|
||||
fieldword : '<' | '>' | '@' | ';' | ','
|
||||
;
|
||||
%%
|
||||
|
||||
/*
|
||||
* Initialize the parsing. Done once for each header field.
|
||||
*/
|
||||
void
|
||||
yyinit(char *p, int len)
|
||||
{
|
||||
yybuffer = p;
|
||||
yylp = p;
|
||||
yyend = p + len;
|
||||
firstfield = lastfield = 0;
|
||||
received = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* keywords identifying header fields we care about
|
||||
*/
|
||||
typedef struct Keyword Keyword;
|
||||
struct Keyword {
|
||||
char *rep;
|
||||
int val;
|
||||
};
|
||||
|
||||
/* field names that we need to recognize */
|
||||
Keyword key[] = {
|
||||
{ "date", DATE },
|
||||
{ "resent-date", RESENT_DATE },
|
||||
{ "return_path", RETURN_PATH },
|
||||
{ "from", FROM },
|
||||
{ "sender", SENDER },
|
||||
{ "reply-to", REPLY_TO },
|
||||
{ "resent-from", RESENT_FROM },
|
||||
{ "resent-sender", RESENT_SENDER },
|
||||
{ "resent-reply-to", RESENT_REPLY_TO },
|
||||
{ "to", TO },
|
||||
{ "cc", CC },
|
||||
{ "bcc", BCC },
|
||||
{ "resent-to", RESENT_TO },
|
||||
{ "resent-cc", RESENT_CC },
|
||||
{ "resent-bcc", RESENT_BCC },
|
||||
{ "remote", REMOTE },
|
||||
{ "subject", SUBJECT },
|
||||
{ "precedence", PRECEDENCE },
|
||||
{ "mime-version", MIMEVERSION },
|
||||
{ "content-type", CONTENTTYPE },
|
||||
{ "message-id", MESSAGEID },
|
||||
{ "received", RECEIVED },
|
||||
{ "mailer", MAILER },
|
||||
{ "who-the-hell-cares", WORD }
|
||||
};
|
||||
|
||||
/*
|
||||
* Lexical analysis for an rfc822 header field. Continuation lines
|
||||
* are handled in yywhite() when skipping over white space.
|
||||
*
|
||||
*/
|
||||
int
|
||||
yylex(void)
|
||||
{
|
||||
String *t;
|
||||
int quoting;
|
||||
int escaping;
|
||||
char *start;
|
||||
Keyword *kp;
|
||||
int c, d;
|
||||
|
||||
/* print("lexing\n"); /**/
|
||||
if(yylp >= yyend)
|
||||
return 0;
|
||||
if(yydone)
|
||||
return 0;
|
||||
|
||||
quoting = escaping = 0;
|
||||
start = yylp;
|
||||
yylval = malloc(sizeof(Node));
|
||||
yylval->white = yylval->s = 0;
|
||||
yylval->next = 0;
|
||||
yylval->addr = 0;
|
||||
yylval->start = yylp;
|
||||
for(t = 0; yylp < yyend; yylp++){
|
||||
c = *yylp & 0xff;
|
||||
|
||||
/* dump nulls, they can't be in header */
|
||||
if(c == 0)
|
||||
continue;
|
||||
|
||||
if(escaping) {
|
||||
escaping = 0;
|
||||
} else if(quoting) {
|
||||
switch(c){
|
||||
case '\\':
|
||||
escaping = 1;
|
||||
break;
|
||||
case '\n':
|
||||
d = (*(yylp+1))&0xff;
|
||||
if(d != ' ' && d != '\t'){
|
||||
quoting = 0;
|
||||
yylp--;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case '"':
|
||||
quoting = 0;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch(c){
|
||||
case '\\':
|
||||
escaping = 1;
|
||||
break;
|
||||
case '(':
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\r':
|
||||
goto out;
|
||||
case '\n':
|
||||
if(yylp == start){
|
||||
yylp++;
|
||||
/* print("lex(c %c)\n", c); /**/
|
||||
yylval->end = yylp;
|
||||
return yylval->c = c;
|
||||
}
|
||||
goto out;
|
||||
case '@':
|
||||
case '>':
|
||||
case '<':
|
||||
case ':':
|
||||
case ',':
|
||||
case ';':
|
||||
if(yylp == start){
|
||||
yylp++;
|
||||
yylval->white = yywhite();
|
||||
/* print("lex(c %c)\n", c); /**/
|
||||
yylval->end = yylp;
|
||||
return yylval->c = c;
|
||||
}
|
||||
goto out;
|
||||
case '"':
|
||||
quoting = 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(t == 0)
|
||||
t = s_new();
|
||||
s_putc(t, c);
|
||||
}
|
||||
out:
|
||||
yylval->white = yywhite();
|
||||
if(t) {
|
||||
s_terminate(t);
|
||||
} else /* message begins with white-space! */
|
||||
return yylval->c = '\n';
|
||||
yylval->s = t;
|
||||
for(kp = key; kp->val != WORD; kp++)
|
||||
if(cistrcmp(s_to_c(t), kp->rep)==0)
|
||||
break;
|
||||
/* print("lex(%d) %s\n", kp->val-WORD, s_to_c(t)); /**/
|
||||
yylval->end = yylp;
|
||||
return yylval->c = kp->val;
|
||||
}
|
||||
|
||||
void
|
||||
yyerror(char *x)
|
||||
{
|
||||
USED(x);
|
||||
|
||||
/*fprint(2, "parse err: %s\n", x);/**/
|
||||
}
|
||||
|
||||
/*
|
||||
* parse white space and comments
|
||||
*/
|
||||
String *
|
||||
yywhite(void)
|
||||
{
|
||||
String *w;
|
||||
int clevel;
|
||||
int c;
|
||||
int escaping;
|
||||
|
||||
escaping = clevel = 0;
|
||||
for(w = 0; yylp < yyend; yylp++){
|
||||
c = *yylp & 0xff;
|
||||
|
||||
/* dump nulls, they can't be in header */
|
||||
if(c == 0)
|
||||
continue;
|
||||
|
||||
if(escaping){
|
||||
escaping = 0;
|
||||
} else if(clevel) {
|
||||
switch(c){
|
||||
case '\n':
|
||||
/*
|
||||
* look for multiline fields
|
||||
*/
|
||||
if(*(yylp+1)==' ' || *(yylp+1)=='\t')
|
||||
break;
|
||||
else
|
||||
goto out;
|
||||
case '\\':
|
||||
escaping = 1;
|
||||
break;
|
||||
case '(':
|
||||
clevel++;
|
||||
break;
|
||||
case ')':
|
||||
clevel--;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch(c){
|
||||
case '\\':
|
||||
escaping = 1;
|
||||
break;
|
||||
case '(':
|
||||
clevel++;
|
||||
break;
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\r':
|
||||
break;
|
||||
case '\n':
|
||||
/*
|
||||
* look for multiline fields
|
||||
*/
|
||||
if(*(yylp+1)==' ' || *(yylp+1)=='\t')
|
||||
break;
|
||||
else
|
||||
goto out;
|
||||
default:
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
if(w == 0)
|
||||
w = s_new();
|
||||
s_putc(w, c);
|
||||
}
|
||||
out:
|
||||
if(w)
|
||||
s_terminate(w);
|
||||
return w;
|
||||
}
|
||||
|
||||
/*
|
||||
* link two parsed entries together
|
||||
*/
|
||||
Node*
|
||||
link2(Node *p1, Node *p2)
|
||||
{
|
||||
Node *p;
|
||||
|
||||
for(p = p1; p->next; p = p->next)
|
||||
;
|
||||
p->next = p2;
|
||||
return p1;
|
||||
}
|
||||
|
||||
/*
|
||||
* link three parsed entries together
|
||||
*/
|
||||
Node*
|
||||
link3(Node *p1, Node *p2, Node *p3)
|
||||
{
|
||||
Node *p;
|
||||
|
||||
for(p = p2; p->next; p = p->next)
|
||||
;
|
||||
p->next = p3;
|
||||
|
||||
for(p = p1; p->next; p = p->next)
|
||||
;
|
||||
p->next = p2;
|
||||
|
||||
return p1;
|
||||
}
|
||||
|
||||
/*
|
||||
* make a:b, move all white space after both
|
||||
*/
|
||||
Node*
|
||||
colon(Node *p1, Node *p2)
|
||||
{
|
||||
if(p1->white){
|
||||
if(p2->white)
|
||||
s_append(p1->white, s_to_c(p2->white));
|
||||
} else {
|
||||
p1->white = p2->white;
|
||||
p2->white = 0;
|
||||
}
|
||||
|
||||
s_append(p1->s, ":");
|
||||
if(p2->s)
|
||||
s_append(p1->s, s_to_c(p2->s));
|
||||
|
||||
if(p1->end < p2->end)
|
||||
p1->end = p2->end;
|
||||
freenode(p2);
|
||||
return p1;
|
||||
}
|
||||
|
||||
/*
|
||||
* concatenate two fields, move all white space after both
|
||||
*/
|
||||
Node*
|
||||
concat(Node *p1, Node *p2)
|
||||
{
|
||||
char buf[2];
|
||||
|
||||
if(p1->white){
|
||||
if(p2->white)
|
||||
s_append(p1->white, s_to_c(p2->white));
|
||||
} else {
|
||||
p1->white = p2->white;
|
||||
p2->white = 0;
|
||||
}
|
||||
|
||||
if(p1->s == nil){
|
||||
buf[0] = p1->c;
|
||||
buf[1] = 0;
|
||||
p1->s = s_new();
|
||||
s_append(p1->s, buf);
|
||||
}
|
||||
|
||||
if(p2->s)
|
||||
s_append(p1->s, s_to_c(p2->s));
|
||||
else {
|
||||
buf[0] = p2->c;
|
||||
buf[1] = 0;
|
||||
s_append(p1->s, buf);
|
||||
}
|
||||
|
||||
if(p1->end < p2->end)
|
||||
p1->end = p2->end;
|
||||
freenode(p2);
|
||||
return p1;
|
||||
}
|
||||
|
||||
/*
|
||||
* look for disallowed chars in the field name
|
||||
*/
|
||||
int
|
||||
badfieldname(Node *p)
|
||||
{
|
||||
for(; p; p = p->next){
|
||||
/* field name can't contain white space */
|
||||
if(p->white && p->next)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* mark as an address
|
||||
*/
|
||||
Node *
|
||||
address(Node *p)
|
||||
{
|
||||
p->addr = 1;
|
||||
return p;
|
||||
}
|
||||
|
||||
/*
|
||||
* case independent string compare
|
||||
*/
|
||||
int
|
||||
cistrcmp(char *s1, char *s2)
|
||||
{
|
||||
int c1, c2;
|
||||
|
||||
for(; *s1; s1++, s2++){
|
||||
c1 = isupper(*s1) ? tolower(*s1) : *s1;
|
||||
c2 = isupper(*s2) ? tolower(*s2) : *s2;
|
||||
if (c1 != c2)
|
||||
return -1;
|
||||
}
|
||||
return *s2;
|
||||
}
|
||||
|
||||
/*
|
||||
* free a node
|
||||
*/
|
||||
void
|
||||
freenode(Node *p)
|
||||
{
|
||||
Node *tp;
|
||||
|
||||
while(p){
|
||||
tp = p->next;
|
||||
if(p->s)
|
||||
s_free(p->s);
|
||||
if(p->white)
|
||||
s_free(p->white);
|
||||
free(p);
|
||||
p = tp;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* an anonymous user
|
||||
*/
|
||||
Node*
|
||||
nobody(Node *p)
|
||||
{
|
||||
if(p->s)
|
||||
s_free(p->s);
|
||||
p->s = s_copy("pOsTmAsTeR");
|
||||
p->addr = 1;
|
||||
return p;
|
||||
}
|
||||
|
||||
/*
|
||||
* add anything that was dropped because of a parse error
|
||||
*/
|
||||
void
|
||||
missing(Node *p)
|
||||
{
|
||||
Node *np;
|
||||
char *start, *end;
|
||||
Field *f;
|
||||
String *s;
|
||||
|
||||
start = yybuffer;
|
||||
if(lastfield != nil){
|
||||
for(np = lastfield->node; np; np = np->next)
|
||||
start = np->end+1;
|
||||
}
|
||||
|
||||
end = p->start-1;
|
||||
|
||||
if(end <= start)
|
||||
return;
|
||||
|
||||
if(strncmp(start, "From ", 5) == 0)
|
||||
return;
|
||||
|
||||
np = malloc(sizeof(Node));
|
||||
np->start = start;
|
||||
np->end = end;
|
||||
np->white = nil;
|
||||
s = s_copy("BadHeader: ");
|
||||
np->s = s_nappend(s, start, end-start);
|
||||
np->next = nil;
|
||||
|
||||
f = malloc(sizeof(Field));
|
||||
f->next = 0;
|
||||
f->node = np;
|
||||
f->source = 0;
|
||||
if(firstfield)
|
||||
lastfield->next = f;
|
||||
else
|
||||
firstfield = f;
|
||||
lastfield = f;
|
||||
}
|
||||
|
||||
/*
|
||||
* create a new field
|
||||
*/
|
||||
void
|
||||
newfield(Node *p, int source)
|
||||
{
|
||||
Field *f;
|
||||
|
||||
missing(p);
|
||||
|
||||
f = malloc(sizeof(Field));
|
||||
f->next = 0;
|
||||
f->node = p;
|
||||
f->source = source;
|
||||
if(firstfield)
|
||||
lastfield->next = f;
|
||||
else
|
||||
firstfield = f;
|
||||
lastfield = f;
|
||||
endfield = startfield;
|
||||
startfield = yylp;
|
||||
}
|
||||
|
||||
/*
|
||||
* fee a list of fields
|
||||
*/
|
||||
void
|
||||
freefield(Field *f)
|
||||
{
|
||||
Field *tf;
|
||||
|
||||
while(f){
|
||||
tf = f->next;
|
||||
freenode(f->node);
|
||||
free(f);
|
||||
f = tf;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* add some white space to a node
|
||||
*/
|
||||
Node*
|
||||
whiten(Node *p)
|
||||
{
|
||||
Node *tp;
|
||||
|
||||
for(tp = p; tp->next; tp = tp->next)
|
||||
;
|
||||
if(tp->white == 0)
|
||||
tp->white = s_copy(" ");
|
||||
return p;
|
||||
}
|
||||
|
||||
void
|
||||
yycleanup(void)
|
||||
{
|
||||
Field *f, *fnext;
|
||||
Node *np, *next;
|
||||
|
||||
for(f = firstfield; f; f = fnext){
|
||||
for(np = f->node; np; np = next){
|
||||
if(np->s)
|
||||
s_free(np->s);
|
||||
if(np->white)
|
||||
s_free(np->white);
|
||||
next = np->next;
|
||||
free(np);
|
||||
}
|
||||
fnext = f->next;
|
||||
free(f);
|
||||
}
|
||||
firstfield = lastfield = 0;
|
||||
}
|
||||
58
src/cmd/upas/smtp/rmtdns.c
Normal file
58
src/cmd/upas/smtp/rmtdns.c
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
#include "common.h"
|
||||
#include <ndb.h>
|
||||
|
||||
int
|
||||
rmtdns(char *net, char *path)
|
||||
{
|
||||
|
||||
int fd, n, r;
|
||||
char *domain, *cp, buf[1024];
|
||||
|
||||
if(net == 0 || path == 0)
|
||||
return 0;
|
||||
|
||||
domain = strdup(path);
|
||||
cp = strchr(domain, '!');
|
||||
if(cp){
|
||||
*cp = 0;
|
||||
n = cp-domain;
|
||||
} else
|
||||
n = strlen(domain);
|
||||
|
||||
if(*domain == '[' && domain[n-1] == ']'){ /* accept [nnn.nnn.nnn.nnn] */
|
||||
domain[n-1] = 0;
|
||||
r = strcmp(ipattr(domain+1), "ip");
|
||||
domain[n-1] = ']';
|
||||
} else
|
||||
r = strcmp(ipattr(domain), "ip"); /* accept nnn.nnn.nnn.nnn */
|
||||
|
||||
if(r == 0){
|
||||
free(domain);
|
||||
return 0;
|
||||
}
|
||||
|
||||
snprint(buf, sizeof(buf), "%s/dns", net);
|
||||
|
||||
fd = open(buf, ORDWR); /* look up all others */
|
||||
if(fd < 0){ /* dns screw up - can't check */
|
||||
free(domain);
|
||||
return 0;
|
||||
}
|
||||
|
||||
n = snprint(buf, sizeof(buf), "%s all", domain);
|
||||
free(domain);
|
||||
seek(fd, 0, 0);
|
||||
n = write(fd, buf, n);
|
||||
close(fd);
|
||||
if(n < 0){
|
||||
rerrstr(buf, sizeof(buf));
|
||||
if (strcmp(buf, "dns: name does not exist") == 0)
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
void main(int, char *argv[]){ print("return = %d\n", rmtdns("/net.alt/tcp/109", argv[1]));}
|
||||
|
||||
*/
|
||||
1122
src/cmd/upas/smtp/smtp.c
Normal file
1122
src/cmd/upas/smtp/smtp.c
Normal file
File diff suppressed because it is too large
Load diff
61
src/cmd/upas/smtp/smtp.h
Normal file
61
src/cmd/upas/smtp/smtp.h
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
typedef struct Node Node;
|
||||
typedef struct Field Field;
|
||||
typedef Node *Nodeptr;
|
||||
#define YYSTYPE Nodeptr
|
||||
|
||||
struct Node {
|
||||
Node *next;
|
||||
int c; /* token type */
|
||||
char addr; /* true if this is an address */
|
||||
String *s; /* string representing token */
|
||||
String *white; /* white space following token */
|
||||
char *start; /* first byte for this token */
|
||||
char *end; /* next byte in input */
|
||||
};
|
||||
|
||||
struct Field {
|
||||
Field *next;
|
||||
Node *node;
|
||||
int source;
|
||||
};
|
||||
|
||||
typedef struct DS DS;
|
||||
struct DS {
|
||||
/* dist string */
|
||||
char buf[128];
|
||||
char expand[128];
|
||||
char *netdir;
|
||||
char *proto;
|
||||
char *host;
|
||||
char *service;
|
||||
};
|
||||
|
||||
extern Field *firstfield;
|
||||
extern Field *lastfield;
|
||||
extern Node *usender;
|
||||
extern Node *usys;
|
||||
extern Node *udate;
|
||||
extern int originator;
|
||||
extern int destination;
|
||||
extern int date;
|
||||
extern int messageid;
|
||||
|
||||
Node* anonymous(Node*);
|
||||
Node* address(Node*);
|
||||
int badfieldname(Node*);
|
||||
Node* bang(Node*, Node*);
|
||||
Node* colon(Node*, Node*);
|
||||
int cistrcmp(char*, char*);
|
||||
Node* link2(Node*, Node*);
|
||||
Node* link3(Node*, Node*, Node*);
|
||||
void freenode(Node*);
|
||||
void newfield(Node*, int);
|
||||
void freefield(Field*);
|
||||
void yyinit(char*, int);
|
||||
int yyparse(void);
|
||||
int yylex(void);
|
||||
String* yywhite(void);
|
||||
Node* whiten(Node*);
|
||||
void yycleanup(void);
|
||||
int mxdial(char*, char*, char*);
|
||||
void dial_string_parse(char*, DS*);
|
||||
1494
src/cmd/upas/smtp/smtpd.c
Normal file
1494
src/cmd/upas/smtp/smtpd.c
Normal file
File diff suppressed because it is too large
Load diff
68
src/cmd/upas/smtp/smtpd.h
Normal file
68
src/cmd/upas/smtp/smtpd.h
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
enum {
|
||||
ACCEPT = 0,
|
||||
REFUSED,
|
||||
DENIED,
|
||||
DIALUP,
|
||||
BLOCKED,
|
||||
DELAY,
|
||||
TRUSTED,
|
||||
NONE,
|
||||
|
||||
MAXREJECTS = 100,
|
||||
};
|
||||
|
||||
|
||||
typedef struct Link Link;
|
||||
typedef struct List List;
|
||||
|
||||
struct Link {
|
||||
Link *next;
|
||||
String *p;
|
||||
};
|
||||
|
||||
struct List {
|
||||
Link *first;
|
||||
Link *last;
|
||||
};
|
||||
|
||||
extern int fflag;
|
||||
extern int rflag;
|
||||
extern int sflag;
|
||||
|
||||
extern int debug;
|
||||
extern NetConnInfo *nci;
|
||||
extern char *dom;
|
||||
extern char* me;
|
||||
extern int trusted;
|
||||
extern List senders;
|
||||
extern List rcvers;
|
||||
|
||||
void addbadguy(char*);
|
||||
void auth(String *, String *);
|
||||
int blocked(String*);
|
||||
void data(void);
|
||||
char* dumpfile(char*);
|
||||
int forwarding(String*);
|
||||
void getconf(void);
|
||||
void hello(String*, int extended);
|
||||
void help(String *);
|
||||
int isbadguy(void);
|
||||
void listadd(List*, String*);
|
||||
void listfree(List*);
|
||||
int masquerade(String*, char*);
|
||||
void noop(void);
|
||||
int optoutofspamfilter(char*);
|
||||
void quit(void);
|
||||
void parseinit(void);
|
||||
void receiver(String*);
|
||||
int recipok(char*);
|
||||
int reply(char*, ...);
|
||||
void reset(void);
|
||||
int rmtdns(char*, char*);
|
||||
void sayhi(void);
|
||||
void sender(String*);
|
||||
void starttls(void);
|
||||
void turn(void);
|
||||
void verify(String*);
|
||||
void vfysenderhostok(void);
|
||||
int zzparse(void);
|
||||
317
src/cmd/upas/smtp/smtpd.y
Normal file
317
src/cmd/upas/smtp/smtpd.y
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
%{
|
||||
#include "common.h"
|
||||
#include <ctype.h>
|
||||
#include "smtpd.h"
|
||||
|
||||
#define YYSTYPE yystype
|
||||
typedef struct quux yystype;
|
||||
struct quux {
|
||||
String *s;
|
||||
int c;
|
||||
};
|
||||
Biobuf *yyfp;
|
||||
YYSTYPE *bang;
|
||||
extern Biobuf bin;
|
||||
extern int debug;
|
||||
|
||||
YYSTYPE cat(YYSTYPE*, YYSTYPE*, YYSTYPE*, YYSTYPE*, YYSTYPE*, YYSTYPE*, YYSTYPE*);
|
||||
int yyparse(void);
|
||||
int yylex(void);
|
||||
YYSTYPE anonymous(void);
|
||||
%}
|
||||
|
||||
%term SPACE
|
||||
%term CNTRL
|
||||
%term CRLF
|
||||
%start conversation
|
||||
%%
|
||||
|
||||
conversation : cmd
|
||||
| conversation cmd
|
||||
;
|
||||
cmd : error
|
||||
| 'h' 'e' 'l' 'o' spaces sdomain CRLF
|
||||
{ hello($6.s, 0); }
|
||||
| 'e' 'h' 'l' 'o' spaces sdomain CRLF
|
||||
{ hello($6.s, 1); }
|
||||
| 'm' 'a' 'i' 'l' spaces 'f' 'r' 'o' 'm' ':' spath CRLF
|
||||
{ sender($11.s); }
|
||||
| 'm' 'a' 'i' 'l' spaces 'f' 'r' 'o' 'm' ':' spath spaces 'a' 'u' 't' 'h' '=' sauth CRLF
|
||||
{ sender($11.s); }
|
||||
| 'r' 'c' 'p' 't' spaces 't' 'o' ':' spath CRLF
|
||||
{ receiver($9.s); }
|
||||
| 'd' 'a' 't' 'a' CRLF
|
||||
{ data(); }
|
||||
| 'r' 's' 'e' 't' CRLF
|
||||
{ reset(); }
|
||||
| 's' 'e' 'n' 'd' spaces 'f' 'r' 'o' 'm' ':' spath CRLF
|
||||
{ sender($11.s); }
|
||||
| 's' 'o' 'm' 'l' spaces 'f' 'r' 'o' 'm' ':' spath CRLF
|
||||
{ sender($11.s); }
|
||||
| 's' 'a' 'm' 'l' spaces 'f' 'r' 'o' 'm' ':' spath CRLF
|
||||
{ sender($11.s); }
|
||||
| 'v' 'r' 'f' 'y' spaces string CRLF
|
||||
{ verify($6.s); }
|
||||
| 'e' 'x' 'p' 'n' spaces string CRLF
|
||||
{ verify($6.s); }
|
||||
| 'h' 'e' 'l' 'p' CRLF
|
||||
{ help(0); }
|
||||
| 'h' 'e' 'l' 'p' spaces string CRLF
|
||||
{ help($6.s); }
|
||||
| 'n' 'o' 'o' 'p' CRLF
|
||||
{ noop(); }
|
||||
| 'q' 'u' 'i' 't' CRLF
|
||||
{ quit(); }
|
||||
| 't' 'u' 'r' 'n' CRLF
|
||||
{ turn(); }
|
||||
| 's' 't' 'a' 'r' 't' 't' 'l' 's' CRLF
|
||||
{ starttls(); }
|
||||
| 'a' 'u' 't' 'h' spaces name spaces string CRLF
|
||||
{ auth($6.s, $8.s); }
|
||||
| 'a' 'u' 't' 'h' spaces name CRLF
|
||||
{ auth($6.s, nil); }
|
||||
| CRLF
|
||||
{ reply("501 illegal command or bad syntax\r\n"); }
|
||||
;
|
||||
path : '<' '>' ={ $$ = anonymous(); }
|
||||
| '<' mailbox '>' ={ $$ = $2; }
|
||||
| '<' a_d_l ':' mailbox '>' ={ $$ = cat(&$2, bang, &$4, 0, 0 ,0, 0); }
|
||||
;
|
||||
spath : path ={ $$ = $1; }
|
||||
| spaces path ={ $$ = $2; }
|
||||
;
|
||||
auth : path ={ $$ = $1; }
|
||||
| mailbox ={ $$ = $1; }
|
||||
;
|
||||
sauth : auth ={ $$ = $1; }
|
||||
| spaces auth ={ $$ = $2; }
|
||||
;
|
||||
;
|
||||
a_d_l : at_domain ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
|
||||
| at_domain ',' a_d_l ={ $$ = cat(&$1, bang, &$3, 0, 0, 0, 0); }
|
||||
;
|
||||
at_domain : '@' domain ={ $$ = cat(&$2, 0, 0, 0, 0 ,0, 0); }
|
||||
;
|
||||
sdomain : domain ={ $$ = $1; }
|
||||
| domain spaces ={ $$ = $1; }
|
||||
;
|
||||
domain : element ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
|
||||
| element '.' ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
|
||||
| element '.' domain ={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); }
|
||||
;
|
||||
element : name ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
|
||||
| '#' number ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
|
||||
| '[' ']' ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
|
||||
| '[' dotnum ']' ={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); }
|
||||
;
|
||||
mailbox : local_part ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
|
||||
| local_part '@' domain ={ $$ = cat(&$3, bang, &$1, 0, 0 ,0, 0); }
|
||||
;
|
||||
local_part : dot_string ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
|
||||
| quoted_string ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
|
||||
;
|
||||
name : let_dig ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
|
||||
| let_dig ld_str ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
|
||||
| let_dig ldh_str ld_str ={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); }
|
||||
;
|
||||
ld_str : let_dig
|
||||
| let_dig ld_str ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
|
||||
;
|
||||
ldh_str : hunder
|
||||
| ld_str hunder ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
|
||||
| ldh_str ld_str hunder ={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); }
|
||||
;
|
||||
let_dig : a
|
||||
| d
|
||||
;
|
||||
dot_string : string ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
|
||||
| string '.' dot_string ={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); }
|
||||
;
|
||||
|
||||
string : char ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
|
||||
| string char ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
|
||||
;
|
||||
|
||||
quoted_string : '"' qtext '"' ={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); }
|
||||
;
|
||||
qtext : '\\' x ={ $$ = cat(&$2, 0, 0, 0, 0 ,0, 0); }
|
||||
| qtext '\\' x ={ $$ = cat(&$1, &$3, 0, 0, 0 ,0, 0); }
|
||||
| q
|
||||
| qtext q ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
|
||||
;
|
||||
char : c
|
||||
| '\\' x ={ $$ = $2; }
|
||||
;
|
||||
dotnum : snum '.' snum '.' snum '.' snum ={ $$ = cat(&$1, &$2, &$3, &$4, &$5, &$6, &$7); }
|
||||
;
|
||||
number : d ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
|
||||
| number d ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
|
||||
;
|
||||
snum : number ={ if(atoi(s_to_c($1.s)) > 255) print("bad snum\n"); }
|
||||
;
|
||||
spaces : SPACE ={ $$ = $1; }
|
||||
| SPACE spaces ={ $$ = $1; }
|
||||
;
|
||||
hunder : '-' | '_'
|
||||
;
|
||||
special1 : CNTRL
|
||||
| '(' | ')' | ',' | '.'
|
||||
| ':' | ';' | '<' | '>' | '@'
|
||||
;
|
||||
special : special1 | '\\' | '"'
|
||||
;
|
||||
notspecial : '!' | '#' | '$' | '%' | '&' | '\''
|
||||
| '*' | '+' | '-' | '/'
|
||||
| '=' | '?'
|
||||
| '[' | ']' | '^' | '_' | '`' | '{' | '|' | '}' | '~'
|
||||
;
|
||||
|
||||
a : 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i'
|
||||
| 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r'
|
||||
| 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z'
|
||||
;
|
||||
d : '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
|
||||
;
|
||||
c : a | d | notspecial
|
||||
;
|
||||
q : a | d | special1 | notspecial | SPACE
|
||||
;
|
||||
x : a | d | special | notspecial | SPACE
|
||||
;
|
||||
%%
|
||||
|
||||
void
|
||||
parseinit(void)
|
||||
{
|
||||
bang = (YYSTYPE*)malloc(sizeof(YYSTYPE));
|
||||
bang->c = '!';
|
||||
bang->s = 0;
|
||||
yyfp = &bin;
|
||||
}
|
||||
|
||||
yylex(void)
|
||||
{
|
||||
int c;
|
||||
|
||||
for(;;){
|
||||
c = Bgetc(yyfp);
|
||||
if(c == -1)
|
||||
return 0;
|
||||
if(debug)
|
||||
fprint(2, "%c", c);
|
||||
yylval.c = c = c & 0x7F;
|
||||
if(c == '\n'){
|
||||
return CRLF;
|
||||
}
|
||||
if(c == '\r'){
|
||||
c = Bgetc(yyfp);
|
||||
if(c != '\n'){
|
||||
Bungetc(yyfp);
|
||||
c = '\r';
|
||||
} else {
|
||||
if(debug)
|
||||
fprint(2, "%c", c);
|
||||
return CRLF;
|
||||
}
|
||||
}
|
||||
if(isalpha(c))
|
||||
return tolower(c);
|
||||
if(isspace(c))
|
||||
return SPACE;
|
||||
if(iscntrl(c))
|
||||
return CNTRL;
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
YYSTYPE
|
||||
cat(YYSTYPE *y1, YYSTYPE *y2, YYSTYPE *y3, YYSTYPE *y4, YYSTYPE *y5, YYSTYPE *y6, YYSTYPE *y7)
|
||||
{
|
||||
YYSTYPE rv;
|
||||
|
||||
if(y1->s)
|
||||
rv.s = y1->s;
|
||||
else {
|
||||
rv.s = s_new();
|
||||
s_putc(rv.s, y1->c);
|
||||
s_terminate(rv.s);
|
||||
}
|
||||
if(y2){
|
||||
if(y2->s){
|
||||
s_append(rv.s, s_to_c(y2->s));
|
||||
s_free(y2->s);
|
||||
} else {
|
||||
s_putc(rv.s, y2->c);
|
||||
s_terminate(rv.s);
|
||||
}
|
||||
} else
|
||||
return rv;
|
||||
if(y3){
|
||||
if(y3->s){
|
||||
s_append(rv.s, s_to_c(y3->s));
|
||||
s_free(y3->s);
|
||||
} else {
|
||||
s_putc(rv.s, y3->c);
|
||||
s_terminate(rv.s);
|
||||
}
|
||||
} else
|
||||
return rv;
|
||||
if(y4){
|
||||
if(y4->s){
|
||||
s_append(rv.s, s_to_c(y4->s));
|
||||
s_free(y4->s);
|
||||
} else {
|
||||
s_putc(rv.s, y4->c);
|
||||
s_terminate(rv.s);
|
||||
}
|
||||
} else
|
||||
return rv;
|
||||
if(y5){
|
||||
if(y5->s){
|
||||
s_append(rv.s, s_to_c(y5->s));
|
||||
s_free(y5->s);
|
||||
} else {
|
||||
s_putc(rv.s, y5->c);
|
||||
s_terminate(rv.s);
|
||||
}
|
||||
} else
|
||||
return rv;
|
||||
if(y6){
|
||||
if(y6->s){
|
||||
s_append(rv.s, s_to_c(y6->s));
|
||||
s_free(y6->s);
|
||||
} else {
|
||||
s_putc(rv.s, y6->c);
|
||||
s_terminate(rv.s);
|
||||
}
|
||||
} else
|
||||
return rv;
|
||||
if(y7){
|
||||
if(y7->s){
|
||||
s_append(rv.s, s_to_c(y7->s));
|
||||
s_free(y7->s);
|
||||
} else {
|
||||
s_putc(rv.s, y7->c);
|
||||
s_terminate(rv.s);
|
||||
}
|
||||
} else
|
||||
return rv;
|
||||
}
|
||||
|
||||
void
|
||||
yyerror(char *x)
|
||||
{
|
||||
USED(x);
|
||||
}
|
||||
|
||||
/*
|
||||
* an anonymous user
|
||||
*/
|
||||
YYSTYPE
|
||||
anonymous(void)
|
||||
{
|
||||
YYSTYPE rv;
|
||||
|
||||
rv.s = s_copy("/dev/null");
|
||||
return rv;
|
||||
}
|
||||
591
src/cmd/upas/smtp/spam.c
Normal file
591
src/cmd/upas/smtp/spam.c
Normal file
|
|
@ -0,0 +1,591 @@
|
|||
#include "common.h"
|
||||
#include "smtpd.h"
|
||||
#include <ip.h>
|
||||
|
||||
enum {
|
||||
NORELAY = 0,
|
||||
DNSVERIFY,
|
||||
SAVEBLOCK,
|
||||
DOMNAME,
|
||||
OURNETS,
|
||||
OURDOMS,
|
||||
|
||||
IP = 0,
|
||||
STRING,
|
||||
};
|
||||
|
||||
|
||||
typedef struct Keyword Keyword;
|
||||
|
||||
struct Keyword {
|
||||
char *name;
|
||||
int code;
|
||||
};
|
||||
|
||||
static Keyword options[] = {
|
||||
"norelay", NORELAY,
|
||||
"verifysenderdom", DNSVERIFY,
|
||||
"saveblockedmsg", SAVEBLOCK,
|
||||
"defaultdomain", DOMNAME,
|
||||
"ournets", OURNETS,
|
||||
"ourdomains", OURDOMS,
|
||||
0, NONE,
|
||||
};
|
||||
|
||||
static Keyword actions[] = {
|
||||
"allow", ACCEPT,
|
||||
"block", BLOCKED,
|
||||
"deny", DENIED,
|
||||
"dial", DIALUP,
|
||||
"delay", DELAY,
|
||||
0, NONE,
|
||||
};
|
||||
|
||||
static int hisaction;
|
||||
static List ourdoms;
|
||||
static List badguys;
|
||||
static ulong v4peerip;
|
||||
|
||||
static char* getline(Biobuf*);
|
||||
static int cidrcheck(char*);
|
||||
|
||||
static int
|
||||
findkey(char *val, Keyword *p)
|
||||
{
|
||||
|
||||
for(; p->name; p++)
|
||||
if(strcmp(val, p->name) == 0)
|
||||
break;
|
||||
return p->code;
|
||||
}
|
||||
|
||||
char*
|
||||
actstr(int a)
|
||||
{
|
||||
char buf[32];
|
||||
Keyword *p;
|
||||
|
||||
for(p=actions; p->name; p++)
|
||||
if(p->code == a)
|
||||
return p->name;
|
||||
if(a==NONE)
|
||||
return "none";
|
||||
sprint(buf, "%d", a);
|
||||
return buf;
|
||||
}
|
||||
|
||||
int
|
||||
getaction(char *s, char *type)
|
||||
{
|
||||
char buf[1024];
|
||||
Keyword *k;
|
||||
|
||||
if(s == nil || *s == 0)
|
||||
return ACCEPT;
|
||||
|
||||
for(k = actions; k->name != 0; k++){
|
||||
snprint(buf, sizeof buf, "/mail/ratify/%s/%s/%s", k->name, type, s);
|
||||
if(access(buf,0) >= 0)
|
||||
return k->code;
|
||||
}
|
||||
return ACCEPT;
|
||||
}
|
||||
|
||||
int
|
||||
istrusted(char *s)
|
||||
{
|
||||
char buf[1024];
|
||||
|
||||
if(s == nil || *s == 0)
|
||||
return 0;
|
||||
|
||||
snprint(buf, sizeof buf, "/mail/ratify/trusted/%s", s);
|
||||
return access(buf,0) >= 0;
|
||||
}
|
||||
|
||||
void
|
||||
getconf(void)
|
||||
{
|
||||
Biobuf *bp;
|
||||
char *cp, *p;
|
||||
String *s;
|
||||
char buf[512];
|
||||
uchar addr[4];
|
||||
|
||||
v4parseip(addr, nci->rsys);
|
||||
v4peerip = nhgetl(addr);
|
||||
|
||||
trusted = istrusted(nci->rsys);
|
||||
hisaction = getaction(nci->rsys, "ip");
|
||||
if(debug){
|
||||
fprint(2, "istrusted(%s)=%d\n", nci->rsys, trusted);
|
||||
fprint(2, "getaction(%s, ip)=%s\n", nci->rsys, actstr(hisaction));
|
||||
}
|
||||
snprint(buf, sizeof(buf), "%s/smtpd.conf", UPASLIB);
|
||||
bp = sysopen(buf, "r", 0);
|
||||
if(bp == 0)
|
||||
return;
|
||||
|
||||
for(;;){
|
||||
cp = getline(bp);
|
||||
if(cp == 0)
|
||||
break;
|
||||
p = cp+strlen(cp)+1;
|
||||
switch(findkey(cp, options)){
|
||||
case NORELAY:
|
||||
if(fflag == 0 && strcmp(p, "on") == 0)
|
||||
fflag++;
|
||||
break;
|
||||
case DNSVERIFY:
|
||||
if(rflag == 0 && strcmp(p, "on") == 0)
|
||||
rflag++;
|
||||
break;
|
||||
case SAVEBLOCK:
|
||||
if(sflag == 0 && strcmp(p, "on") == 0)
|
||||
sflag++;
|
||||
break;
|
||||
case DOMNAME:
|
||||
if(dom == 0)
|
||||
dom = strdup(p);
|
||||
break;
|
||||
case OURNETS:
|
||||
if (trusted == 0)
|
||||
trusted = cidrcheck(p);
|
||||
break;
|
||||
case OURDOMS:
|
||||
while(*p){
|
||||
s = s_new();
|
||||
s_append(s, p);
|
||||
listadd(&ourdoms, s);
|
||||
p += strlen(p)+1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
sysclose(bp);
|
||||
}
|
||||
|
||||
/*
|
||||
* match a user name. the only meta-char is '*' which matches all
|
||||
* characters. we only allow it as "*", which matches anything or
|
||||
* an * at the end of the name (e.g., "username*") which matches
|
||||
* trailing characters.
|
||||
*/
|
||||
static int
|
||||
usermatch(char *pathuser, char *specuser)
|
||||
{
|
||||
int n;
|
||||
|
||||
n = strlen(specuser)-1;
|
||||
if(specuser[n] == '*'){
|
||||
if(n == 0) /* match everything */
|
||||
return 0;
|
||||
return strncmp(pathuser, specuser, n);
|
||||
}
|
||||
return strcmp(pathuser, specuser);
|
||||
}
|
||||
|
||||
static int
|
||||
dommatch(char *pathdom, char *specdom)
|
||||
{
|
||||
int n;
|
||||
|
||||
if (*specdom == '*'){
|
||||
if (specdom[1] == '.' && specdom[2]){
|
||||
specdom += 2;
|
||||
n = strlen(pathdom)-strlen(specdom);
|
||||
if(n == 0 || (n > 0 && pathdom[n-1] == '.'))
|
||||
return strcmp(pathdom+n, specdom);
|
||||
return n;
|
||||
}
|
||||
}
|
||||
return strcmp(pathdom, specdom);
|
||||
}
|
||||
|
||||
/*
|
||||
* figure out action for this sender
|
||||
*/
|
||||
int
|
||||
blocked(String *path)
|
||||
{
|
||||
String *lpath;
|
||||
int action;
|
||||
|
||||
if(debug)
|
||||
fprint(2, "blocked(%s)\n", s_to_c(path));
|
||||
|
||||
/* if the sender's IP address is blessed, ignore sender email address */
|
||||
if(trusted){
|
||||
if(debug)
|
||||
fprint(2, "\ttrusted => trusted\n");
|
||||
return TRUSTED;
|
||||
}
|
||||
|
||||
/* if sender's IP address is blocked, ignore sender email address */
|
||||
if(hisaction != ACCEPT){
|
||||
if(debug)
|
||||
fprint(2, "\thisaction=%s => %s\n", actstr(hisaction), actstr(hisaction));
|
||||
return hisaction;
|
||||
}
|
||||
|
||||
/* convert to lower case */
|
||||
lpath = s_copy(s_to_c(path));
|
||||
s_tolower(lpath);
|
||||
|
||||
/* classify */
|
||||
action = getaction(s_to_c(lpath), "account");
|
||||
if(debug)
|
||||
fprint(2, "\tgetaction account %s => %s\n", s_to_c(lpath), actstr(action));
|
||||
s_free(lpath);
|
||||
return action;
|
||||
}
|
||||
|
||||
/*
|
||||
* get a canonicalized line: a string of null-terminated lower-case
|
||||
* tokens with a two null bytes at the end.
|
||||
*/
|
||||
static char*
|
||||
getline(Biobuf *bp)
|
||||
{
|
||||
char c, *cp, *p, *q;
|
||||
int n;
|
||||
|
||||
static char *buf;
|
||||
static int bufsize;
|
||||
|
||||
for(;;){
|
||||
cp = Brdline(bp, '\n');
|
||||
if(cp == 0)
|
||||
return 0;
|
||||
n = Blinelen(bp);
|
||||
cp[n-1] = 0;
|
||||
if(buf == 0 || bufsize < n+1){
|
||||
bufsize += 512;
|
||||
if(bufsize < n+1)
|
||||
bufsize = n+1;
|
||||
buf = realloc(buf, bufsize);
|
||||
if(buf == 0)
|
||||
break;
|
||||
}
|
||||
q = buf;
|
||||
for (p = cp; *p; p++){
|
||||
c = *p;
|
||||
if(c == '\\' && p[1]) /* we don't allow \<newline> */
|
||||
c = *++p;
|
||||
else
|
||||
if(c == '#')
|
||||
break;
|
||||
else
|
||||
if(c == ' ' || c == '\t' || c == ',')
|
||||
if(q == buf || q[-1] == 0)
|
||||
continue;
|
||||
else
|
||||
c = 0;
|
||||
*q++ = tolower(c);
|
||||
}
|
||||
if(q != buf){
|
||||
if(q[-1])
|
||||
*q++ = 0;
|
||||
*q = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
static int
|
||||
isourdom(char *s)
|
||||
{
|
||||
Link *l;
|
||||
|
||||
if(strchr(s, '.') == nil)
|
||||
return 1;
|
||||
|
||||
for(l = ourdoms.first; l; l = l->next){
|
||||
if(dommatch(s, s_to_c(l->p)) == 0)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
forwarding(String *path)
|
||||
{
|
||||
char *cp, *s;
|
||||
String *lpath;
|
||||
|
||||
if(debug)
|
||||
fprint(2, "forwarding(%s)\n", s_to_c(path));
|
||||
|
||||
/* first check if they want loopback */
|
||||
lpath = s_copy(s_to_c(s_restart(path)));
|
||||
if(nci->rsys && *nci->rsys){
|
||||
cp = s_to_c(lpath);
|
||||
if(strncmp(cp, "[]!", 3) == 0){
|
||||
found:
|
||||
s_append(path, "[");
|
||||
s_append(path, nci->rsys);
|
||||
s_append(path, "]!");
|
||||
s_append(path, cp+3);
|
||||
s_terminate(path);
|
||||
s_free(lpath);
|
||||
return 0;
|
||||
}
|
||||
cp = strchr(cp,'!'); /* skip our domain and check next */
|
||||
if(cp++ && strncmp(cp, "[]!", 3) == 0)
|
||||
goto found;
|
||||
}
|
||||
|
||||
/* if mail is from a trusted IP addr, allow it to forward */
|
||||
if(trusted) {
|
||||
s_free(lpath);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* sender is untrusted; ensure receiver is in one of our domains */
|
||||
for(cp = s_to_c(lpath); *cp; cp++) /* convert receiver lc */
|
||||
*cp = tolower(*cp);
|
||||
|
||||
for(s = s_to_c(lpath); cp = strchr(s, '!'); s = cp+1){
|
||||
*cp = 0;
|
||||
if(!isourdom(s)){
|
||||
s_free(lpath);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
s_free(lpath);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
masquerade(String *path, char *him)
|
||||
{
|
||||
char *cp, *s;
|
||||
String *lpath;
|
||||
int rv = 0;
|
||||
|
||||
if(debug)
|
||||
fprint(2, "masquerade(%s)\n", s_to_c(path));
|
||||
|
||||
if(trusted)
|
||||
return 0;
|
||||
if(path == nil)
|
||||
return 0;
|
||||
|
||||
lpath = s_copy(s_to_c(path));
|
||||
|
||||
/* sender is untrusted; ensure receiver is in one of our domains */
|
||||
for(cp = s_to_c(lpath); *cp; cp++) /* convert receiver lc */
|
||||
*cp = tolower(*cp);
|
||||
s = s_to_c(lpath);
|
||||
|
||||
/* scan first element of ! or last element of @ paths */
|
||||
if((cp = strchr(s, '!')) != nil){
|
||||
*cp = 0;
|
||||
if(isourdom(s))
|
||||
rv = 1;
|
||||
} else if((cp = strrchr(s, '@')) != nil){
|
||||
if(isourdom(cp+1))
|
||||
rv = 1;
|
||||
} else {
|
||||
if(isourdom(him))
|
||||
rv = 1;
|
||||
}
|
||||
|
||||
s_free(lpath);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/* this is a v4 only check */
|
||||
static int
|
||||
cidrcheck(char *cp)
|
||||
{
|
||||
char *p;
|
||||
ulong a, m;
|
||||
uchar addr[IPv4addrlen];
|
||||
uchar mask[IPv4addrlen];
|
||||
|
||||
if(v4peerip == 0)
|
||||
return 0;
|
||||
|
||||
/* parse a list of CIDR addresses comparing each to the peer IP addr */
|
||||
while(cp && *cp){
|
||||
v4parsecidr(addr, mask, cp);
|
||||
a = nhgetl(addr);
|
||||
m = nhgetl(mask);
|
||||
/*
|
||||
* if a mask isn't specified, we build a minimal mask
|
||||
* instead of using the default mask for that net. in this
|
||||
* case we never allow a class A mask (0xff000000).
|
||||
*/
|
||||
if(strchr(cp, '/') == 0){
|
||||
m = 0xff000000;
|
||||
p = cp;
|
||||
for(p = strchr(p, '.'); p && p[1]; p = strchr(p+1, '.'))
|
||||
m = (m>>8)|0xff000000;
|
||||
|
||||
/* force at least a class B */
|
||||
m |= 0xffff0000;
|
||||
}
|
||||
if((v4peerip&m) == a)
|
||||
return 1;
|
||||
cp += strlen(cp)+1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
isbadguy(void)
|
||||
{
|
||||
Link *l;
|
||||
|
||||
/* check if this IP address is banned */
|
||||
for(l = badguys.first; l; l = l->next)
|
||||
if(cidrcheck(s_to_c(l->p)))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
addbadguy(char *p)
|
||||
{
|
||||
listadd(&badguys, s_copy(p));
|
||||
};
|
||||
|
||||
char*
|
||||
dumpfile(char *sender)
|
||||
{
|
||||
int i, fd;
|
||||
ulong h;
|
||||
static char buf[512];
|
||||
char *cp;
|
||||
|
||||
if (sflag == 1){
|
||||
cp = ctime(time(0));
|
||||
cp[7] = 0;
|
||||
if(cp[8] == ' ')
|
||||
sprint(buf, "%s/queue.dump/%s%c", SPOOL, cp+4, cp[9]);
|
||||
else
|
||||
sprint(buf, "%s/queue.dump/%s%c%c", SPOOL, cp+4, cp[8], cp[9]);
|
||||
cp = buf+strlen(buf);
|
||||
if(access(buf, 0) < 0 && sysmkdir(buf, 0777) < 0)
|
||||
return "/dev/null";
|
||||
h = 0;
|
||||
while(*sender)
|
||||
h = h*257 + *sender++;
|
||||
for(i = 0; i < 50; i++){
|
||||
h += lrand();
|
||||
sprint(cp, "/%lud", h);
|
||||
if(access(buf, 0) >= 0)
|
||||
continue;
|
||||
fd = syscreate(buf, ORDWR, 0666);
|
||||
if(fd >= 0){
|
||||
if(debug)
|
||||
fprint(2, "saving in %s\n", buf);
|
||||
close(fd);
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "/dev/null";
|
||||
}
|
||||
|
||||
char *validator = "/mail/lib/validateaddress";
|
||||
|
||||
int
|
||||
recipok(char *user)
|
||||
{
|
||||
char *cp, *p, c;
|
||||
char buf[512];
|
||||
int n;
|
||||
Biobuf *bp;
|
||||
int pid;
|
||||
Waitmsg *w;
|
||||
|
||||
if(shellchars(user)){
|
||||
syslog(0, "smtpd", "shellchars in user name");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(access(validator, AEXEC) == 0)
|
||||
switch(pid = fork()) {
|
||||
case -1:
|
||||
break;
|
||||
case 0:
|
||||
execl(validator, "validateaddress", user, nil);
|
||||
exits(0);
|
||||
default:
|
||||
while(w = wait()) {
|
||||
if(w->pid != pid)
|
||||
continue;
|
||||
if(w->msg[0] != 0){
|
||||
/*
|
||||
syslog(0, "smtpd", "validateaddress %s: %s", user, w->msg);
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
snprint(buf, sizeof(buf), "%s/names.blocked", UPASLIB);
|
||||
bp = sysopen(buf, "r", 0);
|
||||
if(bp == 0)
|
||||
return 1;
|
||||
for(;;){
|
||||
cp = Brdline(bp, '\n');
|
||||
if(cp == 0)
|
||||
break;
|
||||
n = Blinelen(bp);
|
||||
cp[n-1] = 0;
|
||||
|
||||
while(*cp == ' ' || *cp == '\t')
|
||||
cp++;
|
||||
for(p = cp; c = *p; p++){
|
||||
if(c == '#')
|
||||
break;
|
||||
if(c == ' ' || c == '\t')
|
||||
break;
|
||||
}
|
||||
if(p > cp){
|
||||
*p = 0;
|
||||
if(cistrcmp(user, cp) == 0){
|
||||
syslog(0, "smtpd", "names.blocked blocks %s", user);
|
||||
Bterm(bp);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
Bterm(bp);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* a user can opt out of spam filtering by creating
|
||||
* a file in his mail directory named 'nospamfiltering'.
|
||||
*/
|
||||
int
|
||||
optoutofspamfilter(char *addr)
|
||||
{
|
||||
char *p, *f;
|
||||
int rv;
|
||||
|
||||
p = strchr(addr, '!');
|
||||
if(p)
|
||||
p++;
|
||||
else
|
||||
p = addr;
|
||||
|
||||
|
||||
rv = 0;
|
||||
f = smprint("/mail/box/%s/nospamfiltering", p);
|
||||
if(f != nil){
|
||||
rv = access(f, 0)==0;
|
||||
free(f);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
25
src/cmd/upas/smtp/y.tab.h
Normal file
25
src/cmd/upas/smtp/y.tab.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#define WORD 57346
|
||||
#define DATE 57347
|
||||
#define RESENT_DATE 57348
|
||||
#define RETURN_PATH 57349
|
||||
#define FROM 57350
|
||||
#define SENDER 57351
|
||||
#define REPLY_TO 57352
|
||||
#define RESENT_FROM 57353
|
||||
#define RESENT_SENDER 57354
|
||||
#define RESENT_REPLY_TO 57355
|
||||
#define SUBJECT 57356
|
||||
#define TO 57357
|
||||
#define CC 57358
|
||||
#define BCC 57359
|
||||
#define RESENT_TO 57360
|
||||
#define RESENT_CC 57361
|
||||
#define RESENT_BCC 57362
|
||||
#define REMOTE 57363
|
||||
#define PRECEDENCE 57364
|
||||
#define MIMEVERSION 57365
|
||||
#define CONTENTTYPE 57366
|
||||
#define MESSAGEID 57367
|
||||
#define RECEIVED 57368
|
||||
#define MAILER 57369
|
||||
#define BADTOKEN 57370
|
||||
Loading…
Add table
Add a link
Reference in a new issue