Thanks to John Cummings.

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

View file

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

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

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

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

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

File diff suppressed because it is too large Load diff

View file

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

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

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

View file

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

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

File diff suppressed because it is too large Load diff

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

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

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

File diff suppressed because it is too large Load diff

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

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

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

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

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

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

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

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