1504 lines
30 KiB
C
1504 lines
30 KiB
C
#include "common.h"
|
|
#include "smtpd.h"
|
|
#include "smtp.h"
|
|
#include <ctype.h>
|
|
#include <ip.h>
|
|
#include <ndb.h>
|
|
#include <mp.h>
|
|
#include <libsec.h>
|
|
#include <auth.h>
|
|
#include "../smtp/y.tab.h"
|
|
|
|
#define DBGMX 1
|
|
|
|
char *me;
|
|
char *him="";
|
|
char *dom;
|
|
process *pp;
|
|
String *mailer;
|
|
NetConnInfo *nci;
|
|
|
|
int filterstate = ACCEPT;
|
|
int trusted;
|
|
int logged;
|
|
int rejectcount;
|
|
int hardreject;
|
|
|
|
Biobuf bin;
|
|
|
|
int debug;
|
|
int Dflag;
|
|
int fflag;
|
|
int gflag;
|
|
int rflag;
|
|
int sflag;
|
|
int authenticate;
|
|
int authenticated;
|
|
int passwordinclear;
|
|
char *tlscert;
|
|
|
|
List senders;
|
|
List rcvers;
|
|
|
|
char pipbuf[ERRMAX];
|
|
char *piperror;
|
|
int pipemsg(int*);
|
|
String* startcmd(void);
|
|
int rejectcheck(void);
|
|
String* mailerpath(char*);
|
|
|
|
static int
|
|
catchalarm(void *a, char *msg)
|
|
{
|
|
int rv = 1;
|
|
|
|
USED(a);
|
|
|
|
/* log alarms but continue */
|
|
if(strstr(msg, "alarm")){
|
|
if(senders.first && rcvers.first)
|
|
syslog(0, "smtpd", "note: %s->%s: %s", s_to_c(senders.first->p),
|
|
s_to_c(rcvers.first->p), msg);
|
|
else
|
|
syslog(0, "smtpd", "note: %s", msg);
|
|
rv = 0;
|
|
}
|
|
|
|
/* kill the children if there are any */
|
|
if(pp)
|
|
syskillpg(pp->pid);
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* override string error functions to do something reasonable */
|
|
void
|
|
s_error(char *f, char *status)
|
|
{
|
|
char errbuf[Errlen];
|
|
|
|
errbuf[0] = 0;
|
|
rerrstr(errbuf, sizeof(errbuf));
|
|
if(f && *f)
|
|
reply("452 out of memory %s: %s\r\n", f, errbuf);
|
|
else
|
|
reply("452 out of memory %s\r\n", errbuf);
|
|
syslog(0, "smtpd", "++Malloc failure %s [%s]", him, nci->rsys);
|
|
exits(status);
|
|
}
|
|
|
|
void
|
|
main(int argc, char **argv)
|
|
{
|
|
char *p, buf[1024];
|
|
char *netdir;
|
|
|
|
netdir = nil;
|
|
quotefmtinstall();
|
|
ARGBEGIN{
|
|
case 'D':
|
|
Dflag++;
|
|
break;
|
|
case 'd':
|
|
debug++;
|
|
break;
|
|
case 'n': /* log peer ip address */
|
|
netdir = ARGF();
|
|
break;
|
|
case 'f': /* disallow relaying */
|
|
fflag = 1;
|
|
break;
|
|
case 'g':
|
|
gflag = 1;
|
|
break;
|
|
case 'h': /* default domain name */
|
|
dom = ARGF();
|
|
break;
|
|
case 'k': /* prohibited ip address */
|
|
p = ARGF();
|
|
if (p)
|
|
addbadguy(p);
|
|
break;
|
|
case 'm': /* set mail command */
|
|
p = ARGF();
|
|
if(p)
|
|
mailer = mailerpath(p);
|
|
break;
|
|
case 'r':
|
|
rflag = 1; /* verify sender's domain */
|
|
break;
|
|
case 's': /* save blocked messages */
|
|
sflag = 1;
|
|
break;
|
|
case 'a':
|
|
authenticate = 1;
|
|
break;
|
|
case 'p':
|
|
passwordinclear = 1;
|
|
break;
|
|
case 'c':
|
|
tlscert = ARGF();
|
|
break;
|
|
case 't':
|
|
fprint(2, "%s: the -t option is no longer supported, see -c\n", argv0);
|
|
tlscert = "/sys/lib/ssl/smtpd-cert.pem";
|
|
break;
|
|
default:
|
|
fprint(2, "usage: smtpd [-dfhrs] [-n net] [-c cert]\n");
|
|
exits("usage");
|
|
}ARGEND;
|
|
|
|
nci = getnetconninfo(netdir, 0);
|
|
if(nci == nil)
|
|
sysfatal("can't get remote system's address");
|
|
|
|
if(mailer == nil)
|
|
mailer = mailerpath("send");
|
|
|
|
if(debug){
|
|
close(2);
|
|
snprint(buf, sizeof(buf), "%s/smtpd.db", UPASLOG);
|
|
if (open(buf, OWRITE) >= 0) {
|
|
seek(2, 0, 2);
|
|
fprint(2, "%d smtpd %s\n", getpid(), thedate());
|
|
} else
|
|
debug = 0;
|
|
}
|
|
getconf();
|
|
Binit(&bin, 0, OREAD);
|
|
|
|
chdir(UPASLOG);
|
|
me = sysname_read();
|
|
if(dom == 0 || dom[0] == 0)
|
|
dom = domainname_read();
|
|
if(dom == 0 || dom[0] == 0)
|
|
dom = me;
|
|
sayhi();
|
|
parseinit();
|
|
/* allow 45 minutes to parse the header */
|
|
atnotify(catchalarm, 1);
|
|
alarm(45*60*1000);
|
|
zzparse();
|
|
exits(0);
|
|
}
|
|
|
|
void
|
|
listfree(List *l)
|
|
{
|
|
Link *lp;
|
|
Link *next;
|
|
|
|
for(lp = l->first; lp; lp = next){
|
|
next = lp->next;
|
|
s_free(lp->p);
|
|
free(lp);
|
|
}
|
|
l->first = l->last = 0;
|
|
}
|
|
|
|
void
|
|
listadd(List *l, String *path)
|
|
{
|
|
Link *lp;
|
|
|
|
lp = (Link *)malloc(sizeof(Link));
|
|
lp->p = path;
|
|
lp->next = 0;
|
|
|
|
if(l->last)
|
|
l->last->next = lp;
|
|
else
|
|
l->first = lp;
|
|
l->last = lp;
|
|
}
|
|
|
|
#define SIZE 4096
|
|
int
|
|
reply(char *fmt, ...)
|
|
{
|
|
char buf[SIZE], *out;
|
|
va_list arg;
|
|
int n;
|
|
|
|
va_start(arg, fmt);
|
|
out = vseprint(buf, buf+SIZE, fmt, arg);
|
|
va_end(arg);
|
|
n = (long)(out-buf);
|
|
if(debug) {
|
|
seek(2, 0, 2);
|
|
write(2, buf, n);
|
|
}
|
|
write(1, buf, n);
|
|
return n;
|
|
}
|
|
|
|
void
|
|
reset(void)
|
|
{
|
|
if(rejectcheck())
|
|
return;
|
|
listfree(&rcvers);
|
|
listfree(&senders);
|
|
if(filterstate != DIALUP){
|
|
logged = 0;
|
|
filterstate = ACCEPT;
|
|
}
|
|
reply("250 ok\r\n");
|
|
}
|
|
|
|
void
|
|
sayhi(void)
|
|
{
|
|
reply("220 %s SMTP\r\n", dom);
|
|
}
|
|
|
|
void
|
|
hello(String *himp, int extended)
|
|
{
|
|
char **mynames;
|
|
|
|
him = s_to_c(himp);
|
|
syslog(0, "smtpd", "%s from %s as %s", extended ? "ehlo" : "helo", nci->rsys, him);
|
|
if(rejectcheck())
|
|
return;
|
|
|
|
if(strchr(him, '.') && nci && !trusted && fflag && strcmp(nci->rsys, nci->lsys) != 0){
|
|
/*
|
|
* We don't care if he lies about who he is, but it is
|
|
* not okay to pretend to be us. Many viruses do this,
|
|
* just parroting back what we say in the greeting.
|
|
*/
|
|
if(strcmp(him, dom) == 0)
|
|
goto Liarliar;
|
|
for(mynames=sysnames_read(); mynames && *mynames; mynames++){
|
|
if(cistrcmp(*mynames, him) == 0){
|
|
Liarliar:
|
|
syslog(0, "smtpd", "Hung up on %s; claimed to be %s",
|
|
nci->rsys, him);
|
|
reply("554 Liar!\r\n");
|
|
exits("client pretended to be us");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
* it is never acceptable to claim to be "localhost",
|
|
* "localhost.localdomain" or "localhost.example.com"; only spammers
|
|
* do this. it should be unacceptable to claim any string that doesn't
|
|
* look like a domain name (e.g., has at least one dot in it), but
|
|
* Microsoft mail software gets this wrong.
|
|
*/
|
|
if (strcmp(him, "localhost") == 0 ||
|
|
strcmp(him, "localhost.localdomain") == 0 ||
|
|
strcmp(him, "localhost.example.com") == 0)
|
|
goto Liarliar;
|
|
if(strchr(him, '.') == 0 && nci != nil && strchr(nci->rsys, '.') != nil)
|
|
him = nci->rsys;
|
|
|
|
if(Dflag)
|
|
sleep(15*1000);
|
|
reply("250%c%s you are %s\r\n", extended ? '-' : ' ', dom, him);
|
|
if (extended) {
|
|
if(tlscert != nil)
|
|
reply("250-STARTTLS\r\n");
|
|
if (passwordinclear)
|
|
reply("250 AUTH CRAM-MD5 PLAIN LOGIN\r\n");
|
|
else
|
|
reply("250 AUTH CRAM-MD5\r\n");
|
|
}
|
|
}
|
|
|
|
void
|
|
sender(String *path)
|
|
{
|
|
String *s;
|
|
static char *lastsender;
|
|
|
|
if(rejectcheck())
|
|
return;
|
|
if (authenticate && !authenticated) {
|
|
rejectcount++;
|
|
reply("530 Authentication required\r\n");
|
|
return;
|
|
}
|
|
if(him == 0 || *him == 0){
|
|
rejectcount++;
|
|
reply("503 Start by saying HELO, please.\r\n", s_to_c(path));
|
|
return;
|
|
}
|
|
|
|
/* don't add the domain onto black holes or we will loop */
|
|
if(strchr(s_to_c(path), '!') == 0 && strcmp(s_to_c(path), "/dev/null") != 0){
|
|
s = s_new();
|
|
s_append(s, him);
|
|
s_append(s, "!");
|
|
s_append(s, s_to_c(path));
|
|
s_terminate(s);
|
|
s_free(path);
|
|
path = s;
|
|
}
|
|
if(shellchars(s_to_c(path))){
|
|
rejectcount++;
|
|
reply("503 Bad character in sender address %s.\r\n", s_to_c(path));
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* if the last sender address resulted in a rejection because the sending
|
|
* domain didn't exist and this sender has the same domain, reject immediately.
|
|
*/
|
|
if(lastsender){
|
|
if (strncmp(lastsender, s_to_c(path), strlen(lastsender)) == 0){
|
|
filterstate = REFUSED;
|
|
rejectcount++;
|
|
reply("554 Sender domain must exist: %s\r\n", s_to_c(path));
|
|
return;
|
|
}
|
|
free(lastsender); /* different sender domain */
|
|
lastsender = 0;
|
|
}
|
|
|
|
/*
|
|
* see if this ip address, domain name, user name or account is blocked
|
|
*/
|
|
filterstate = blocked(path);
|
|
|
|
logged = 0;
|
|
listadd(&senders, path);
|
|
reply("250 sender is %s\r\n", s_to_c(path));
|
|
}
|
|
|
|
enum { Rcpt, Domain, Ntoks };
|
|
|
|
typedef struct Sender Sender;
|
|
struct Sender {
|
|
Sender *next;
|
|
char *rcpt;
|
|
char *domain;
|
|
};
|
|
static Sender *sendlist, *sendlast;
|
|
static uchar rsysip[IPaddrlen];
|
|
|
|
static int
|
|
rdsenders(void)
|
|
{
|
|
int lnlen, nf, ok = 1;
|
|
char *line, *senderfile;
|
|
char *toks[Ntoks];
|
|
Biobuf *sf;
|
|
Sender *snd;
|
|
static int beenhere = 0;
|
|
|
|
if (beenhere)
|
|
return 1;
|
|
beenhere = 1;
|
|
|
|
fmtinstall('I', eipfmt);
|
|
parseip(rsysip, nci->rsys);
|
|
|
|
/*
|
|
* we're sticking with a system-wide sender list because
|
|
* per-user lists would require fully resolving recipient
|
|
* addresses to determine which users they correspond to
|
|
* (barring syntactic conventions).
|
|
*/
|
|
senderfile = smprint("%s/senders", UPASLIB);
|
|
sf = Bopen(senderfile, OREAD);
|
|
free(senderfile);
|
|
if (sf == nil)
|
|
return 1;
|
|
while ((line = Brdline(sf, '\n')) != nil) {
|
|
if (line[0] == '#' || line[0] == '\n')
|
|
continue;
|
|
lnlen = Blinelen(sf);
|
|
line[lnlen-1] = '\0'; /* clobber newline */
|
|
nf = tokenize(line, toks, nelem(toks));
|
|
if (nf != nelem(toks))
|
|
continue; /* malformed line */
|
|
|
|
snd = malloc(sizeof *snd);
|
|
if (snd == nil)
|
|
sysfatal("out of memory: %r");
|
|
memset(snd, 0, sizeof *snd);
|
|
snd->next = nil;
|
|
|
|
if (sendlast == nil)
|
|
sendlist = snd;
|
|
else
|
|
sendlast->next = snd;
|
|
sendlast = snd;
|
|
snd->rcpt = strdup(toks[Rcpt]);
|
|
snd->domain = strdup(toks[Domain]);
|
|
}
|
|
Bterm(sf);
|
|
return ok;
|
|
}
|
|
|
|
/*
|
|
* read (recipient, sender's DNS) pairs from /mail/lib/senders.
|
|
* Only allow mail to recipient from any of sender's IPs.
|
|
* A recipient not mentioned in the file is always permitted.
|
|
*/
|
|
static int
|
|
senderok(char *rcpt)
|
|
{
|
|
int mentioned = 0, matched = 0;
|
|
uchar dnsip[IPaddrlen];
|
|
Sender *snd;
|
|
Ndbtuple *nt, *next, *first;
|
|
|
|
rdsenders();
|
|
for (snd = sendlist; snd != nil; snd = snd->next) {
|
|
if (strcmp(rcpt, snd->rcpt) != 0)
|
|
continue;
|
|
/*
|
|
* see if this domain's ips match nci->rsys.
|
|
* if not, perhaps a later entry's domain will.
|
|
*/
|
|
mentioned = 1;
|
|
if (parseip(dnsip, snd->domain) != -1 &&
|
|
memcmp(rsysip, dnsip, IPaddrlen) == 0)
|
|
return 1;
|
|
/*
|
|
* NB: nt->line links form a circular list(!).
|
|
* we need to make one complete pass over it to free it all.
|
|
*/
|
|
first = nt = dnsquery(nci->root, snd->domain, "ip");
|
|
if (first == nil)
|
|
continue;
|
|
do {
|
|
if (strcmp(nt->attr, "ip") == 0 &&
|
|
parseip(dnsip, nt->val) != -1 &&
|
|
memcmp(rsysip, dnsip, IPaddrlen) == 0)
|
|
matched = 1;
|
|
next = nt->line;
|
|
free(nt);
|
|
nt = next;
|
|
} while (nt != first);
|
|
}
|
|
if (matched)
|
|
return 1;
|
|
else
|
|
return !mentioned;
|
|
}
|
|
|
|
void
|
|
receiver(String *path)
|
|
{
|
|
char *sender, *rcpt;
|
|
|
|
if(rejectcheck())
|
|
return;
|
|
if(him == 0 || *him == 0){
|
|
rejectcount++;
|
|
reply("503 Start by saying HELO, please\r\n");
|
|
return;
|
|
}
|
|
if(senders.last)
|
|
sender = s_to_c(senders.last->p);
|
|
else
|
|
sender = "<unknown>";
|
|
|
|
if(!recipok(s_to_c(path))){
|
|
rejectcount++;
|
|
syslog(0, "smtpd", "Disallowed %s (%s/%s) to blocked name %s",
|
|
sender, him, nci->rsys, s_to_c(path));
|
|
reply("550 %s ... user unknown\r\n", s_to_c(path));
|
|
return;
|
|
}
|
|
rcpt = s_to_c(path);
|
|
if (!senderok(rcpt)) {
|
|
rejectcount++;
|
|
syslog(0, "smtpd", "Disallowed sending IP of %s (%s/%s) to %s",
|
|
sender, him, nci->rsys, rcpt);
|
|
reply("550 %s ... sending system not allowed\r\n", rcpt);
|
|
return;
|
|
}
|
|
|
|
logged = 0;
|
|
/* forwarding() can modify 'path' on loopback request */
|
|
if(filterstate == ACCEPT && (fflag && !authenticated) && forwarding(path)) {
|
|
syslog(0, "smtpd", "Bad Forward %s (%s/%s) (%s)",
|
|
s_to_c(senders.last->p), him, nci->rsys, s_to_c(path));
|
|
rejectcount++;
|
|
reply("550 we don't relay. send to your-path@[] for loopback.\r\n");
|
|
return;
|
|
}
|
|
listadd(&rcvers, path);
|
|
reply("250 receiver is %s\r\n", s_to_c(path));
|
|
}
|
|
|
|
void
|
|
quit(void)
|
|
{
|
|
reply("221 Successful termination\r\n");
|
|
close(0);
|
|
exits(0);
|
|
}
|
|
|
|
void
|
|
turn(void)
|
|
{
|
|
if(rejectcheck())
|
|
return;
|
|
reply("502 TURN unimplemented\r\n");
|
|
}
|
|
|
|
void
|
|
noop(void)
|
|
{
|
|
if(rejectcheck())
|
|
return;
|
|
reply("250 Stop wasting my time!\r\n");
|
|
}
|
|
|
|
void
|
|
help(String *cmd)
|
|
{
|
|
if(rejectcheck())
|
|
return;
|
|
if(cmd)
|
|
s_free(cmd);
|
|
reply("250 Read rfc821 and stop wasting my time\r\n");
|
|
}
|
|
|
|
void
|
|
verify(String *path)
|
|
{
|
|
char *p, *q;
|
|
char *av[4];
|
|
|
|
if(rejectcheck())
|
|
return;
|
|
if(shellchars(s_to_c(path))){
|
|
reply("503 Bad character in address %s.\r\n", s_to_c(path));
|
|
return;
|
|
}
|
|
av[0] = s_to_c(mailer);
|
|
av[1] = "-x";
|
|
av[2] = s_to_c(path);
|
|
av[3] = 0;
|
|
|
|
pp = noshell_proc_start(av, (stream *)0, outstream(), (stream *)0, 1, 0);
|
|
if (pp == 0) {
|
|
reply("450 We're busy right now, try later\r\n");
|
|
return;
|
|
}
|
|
|
|
p = Brdline(pp->std[1]->fp, '\n');
|
|
if(p == 0){
|
|
reply("550 String does not match anything.\r\n");
|
|
} else {
|
|
p[Blinelen(pp->std[1]->fp)-1] = 0;
|
|
if(strchr(p, ':'))
|
|
reply("550 String does not match anything.\r\n");
|
|
else{
|
|
q = strrchr(p, '!');
|
|
if(q)
|
|
p = q+1;
|
|
reply("250 %s <%s@%s>\r\n", s_to_c(path), p, dom);
|
|
}
|
|
}
|
|
proc_wait(pp);
|
|
proc_free(pp);
|
|
pp = 0;
|
|
}
|
|
|
|
/*
|
|
* get a line that ends in crnl or cr, turn terminating crnl into a nl
|
|
*
|
|
* return 0 on EOF
|
|
*/
|
|
static int
|
|
getcrnl(String *s, Biobuf *fp)
|
|
{
|
|
int c;
|
|
|
|
for(;;){
|
|
c = Bgetc(fp);
|
|
if(debug) {
|
|
seek(2, 0, 2);
|
|
fprint(2, "%c", c);
|
|
}
|
|
switch(c){
|
|
case -1:
|
|
goto out;
|
|
case '\r':
|
|
c = Bgetc(fp);
|
|
if(c == '\n'){
|
|
if(debug) {
|
|
seek(2, 0, 2);
|
|
fprint(2, "%c", c);
|
|
}
|
|
s_putc(s, '\n');
|
|
goto out;
|
|
}
|
|
Bungetc(fp);
|
|
s_putc(s, '\r');
|
|
break;
|
|
case '\n':
|
|
s_putc(s, c);
|
|
goto out;
|
|
default:
|
|
s_putc(s, c);
|
|
break;
|
|
}
|
|
}
|
|
out:
|
|
s_terminate(s);
|
|
return s_len(s);
|
|
}
|
|
|
|
void
|
|
logcall(int nbytes)
|
|
{
|
|
Link *l;
|
|
String *to, *from;
|
|
|
|
to = s_new();
|
|
from = s_new();
|
|
for(l = senders.first; l; l = l->next){
|
|
if(l != senders.first)
|
|
s_append(from, ", ");
|
|
s_append(from, s_to_c(l->p));
|
|
}
|
|
for(l = rcvers.first; l; l = l->next){
|
|
if(l != rcvers.first)
|
|
s_append(to, ", ");
|
|
s_append(to, s_to_c(l->p));
|
|
}
|
|
syslog(0, "smtpd", "[%s/%s] %s sent %d bytes to %s", him, nci->rsys,
|
|
s_to_c(from), nbytes, s_to_c(to));
|
|
s_free(to);
|
|
s_free(from);
|
|
}
|
|
|
|
static void
|
|
logmsg(char *action)
|
|
{
|
|
Link *l;
|
|
|
|
if(logged)
|
|
return;
|
|
|
|
logged = 1;
|
|
for(l = rcvers.first; l; l = l->next)
|
|
syslog(0, "smtpd", "%s %s (%s/%s) (%s)", action,
|
|
s_to_c(senders.last->p), him, nci->rsys, s_to_c(l->p));
|
|
}
|
|
|
|
static int
|
|
optoutall(int filterstate)
|
|
{
|
|
Link *l;
|
|
|
|
switch(filterstate){
|
|
case ACCEPT:
|
|
case TRUSTED:
|
|
return filterstate;
|
|
}
|
|
|
|
for(l = rcvers.first; l; l = l->next)
|
|
if(!optoutofspamfilter(s_to_c(l->p)))
|
|
return filterstate;
|
|
|
|
return ACCEPT;
|
|
}
|
|
|
|
String*
|
|
startcmd(void)
|
|
{
|
|
int n;
|
|
Link *l;
|
|
char **av;
|
|
String *cmd;
|
|
char *filename;
|
|
|
|
/*
|
|
* ignore the filterstate if the all the receivers prefer it.
|
|
*/
|
|
filterstate = optoutall(filterstate);
|
|
|
|
switch (filterstate){
|
|
case BLOCKED:
|
|
case DELAY:
|
|
rejectcount++;
|
|
logmsg("Blocked");
|
|
filename = dumpfile(s_to_c(senders.last->p));
|
|
cmd = s_new();
|
|
s_append(cmd, "cat > ");
|
|
s_append(cmd, filename);
|
|
pp = proc_start(s_to_c(cmd), instream(), 0, outstream(), 0, 0);
|
|
break;
|
|
case DIALUP:
|
|
logmsg("Dialup");
|
|
rejectcount++;
|
|
reply("554 We don't accept mail from dial-up ports.\r\n");
|
|
/*
|
|
* we could exit here, because we're never going to accept mail from this
|
|
* ip address, but it's unclear that RFC821 allows that. Instead we set
|
|
* the hardreject flag and go stupid.
|
|
*/
|
|
hardreject = 1;
|
|
return 0;
|
|
case DENIED:
|
|
logmsg("Denied");
|
|
rejectcount++;
|
|
reply("554-We don't accept mail from %s.\r\n", s_to_c(senders.last->p));
|
|
reply("554 Contact postmaster@%s for more information.\r\n", dom);
|
|
return 0;
|
|
case REFUSED:
|
|
logmsg("Refused");
|
|
rejectcount++;
|
|
reply("554 Sender domain must exist: %s\r\n", s_to_c(senders.last->p));
|
|
return 0;
|
|
default:
|
|
case NONE:
|
|
logmsg("Confused");
|
|
rejectcount++;
|
|
reply("554-We have had an internal mailer error classifying your message.\r\n");
|
|
reply("554-Filterstate is %d\r\n", filterstate);
|
|
reply("554 Contact postmaster@%s for more information.\r\n", dom);
|
|
return 0;
|
|
case ACCEPT:
|
|
case TRUSTED:
|
|
/*
|
|
* now that all other filters have been passed,
|
|
* do grey-list processing.
|
|
*/
|
|
if(gflag)
|
|
vfysenderhostok();
|
|
|
|
/*
|
|
* set up mail command
|
|
*/
|
|
cmd = s_clone(mailer);
|
|
n = 3;
|
|
for(l = rcvers.first; l; l = l->next)
|
|
n++;
|
|
av = malloc(n*sizeof(char*));
|
|
if(av == nil){
|
|
reply("450 We're busy right now, try later\n");
|
|
s_free(cmd);
|
|
return 0;
|
|
}
|
|
|
|
n = 0;
|
|
av[n++] = s_to_c(cmd);
|
|
av[n++] = "-r";
|
|
for(l = rcvers.first; l; l = l->next)
|
|
av[n++] = s_to_c(l->p);
|
|
av[n] = 0;
|
|
/*
|
|
* start mail process
|
|
*/
|
|
pp = noshell_proc_start(av, instream(), outstream(), outstream(), 0, 0);
|
|
free(av);
|
|
break;
|
|
}
|
|
if(pp == 0) {
|
|
reply("450 We're busy right now, try later\n");
|
|
s_free(cmd);
|
|
return 0;
|
|
}
|
|
return cmd;
|
|
}
|
|
|
|
/*
|
|
* print out a header line, expanding any domainless addresses into
|
|
* address@him
|
|
*/
|
|
char*
|
|
bprintnode(Biobuf *b, Node *p)
|
|
{
|
|
if(p->s){
|
|
if(p->addr && strchr(s_to_c(p->s), '@') == nil){
|
|
if(Bprint(b, "%s@%s", s_to_c(p->s), him) < 0)
|
|
return nil;
|
|
} else {
|
|
if(Bwrite(b, s_to_c(p->s), s_len(p->s)) < 0)
|
|
return nil;
|
|
}
|
|
}else{
|
|
if(Bputc(b, p->c) < 0)
|
|
return nil;
|
|
}
|
|
if(p->white)
|
|
if(Bwrite(b, s_to_c(p->white), s_len(p->white)) < 0)
|
|
return nil;
|
|
return p->end+1;
|
|
}
|
|
|
|
static String*
|
|
getaddr(Node *p)
|
|
{
|
|
for(; p; p = p->next)
|
|
if(p->s && p->addr)
|
|
return p->s;
|
|
return nil;
|
|
}
|
|
|
|
/*
|
|
* add waring headers of the form
|
|
* X-warning: <reason>
|
|
* for any headers that looked like they might be forged.
|
|
*
|
|
* return byte count of new headers
|
|
*/
|
|
static int
|
|
forgedheaderwarnings(void)
|
|
{
|
|
int nbytes;
|
|
Field *f;
|
|
|
|
nbytes = 0;
|
|
|
|
/* warn about envelope sender */
|
|
if(strcmp(s_to_c(senders.last->p), "/dev/null") != 0 && masquerade(senders.last->p, nil))
|
|
nbytes += Bprint(pp->std[0]->fp, "X-warning: suspect envelope domain\n");
|
|
|
|
/*
|
|
* check Sender: field. If it's OK, ignore the others because this is an
|
|
* exploded mailing list.
|
|
*/
|
|
for(f = firstfield; f; f = f->next){
|
|
if(f->node->c == SENDER){
|
|
if(masquerade(getaddr(f->node), him))
|
|
nbytes += Bprint(pp->std[0]->fp, "X-warning: suspect Sender: domain\n");
|
|
else
|
|
return nbytes;
|
|
}
|
|
}
|
|
|
|
/* check From: */
|
|
for(f = firstfield; f; f = f->next){
|
|
if(f->node->c == FROM && masquerade(getaddr(f->node), him))
|
|
nbytes += Bprint(pp->std[0]->fp, "X-warning: suspect From: domain\n");
|
|
}
|
|
return nbytes;
|
|
}
|
|
|
|
/*
|
|
* pipe message to mailer with the following transformations:
|
|
* - change \r\n into \n.
|
|
* - add sender's domain to any addrs with no domain
|
|
* - add a From: if none of From:, Sender:, or Replyto: exists
|
|
* - add a Received: line
|
|
*/
|
|
int
|
|
pipemsg(int *byteswritten)
|
|
{
|
|
int status;
|
|
char *cp;
|
|
String *line;
|
|
String *hdr;
|
|
int n, nbytes;
|
|
int sawdot;
|
|
Field *f;
|
|
Node *p;
|
|
Link *l;
|
|
|
|
pipesig(&status); /* set status to 1 on write to closed pipe */
|
|
sawdot = 0;
|
|
status = 0;
|
|
|
|
/*
|
|
* add a 'From ' line as envelope
|
|
*/
|
|
nbytes = 0;
|
|
nbytes += Bprint(pp->std[0]->fp, "From %s %s remote from \n",
|
|
s_to_c(senders.first->p), thedate());
|
|
|
|
/*
|
|
* add our own Received: stamp
|
|
*/
|
|
nbytes += Bprint(pp->std[0]->fp, "Received: from %s ", him);
|
|
if(nci->rsys)
|
|
nbytes += Bprint(pp->std[0]->fp, "([%s]) ", nci->rsys);
|
|
nbytes += Bprint(pp->std[0]->fp, "by %s; %s\n", me, thedate());
|
|
|
|
/*
|
|
* read first 16k obeying '.' escape. we're assuming
|
|
* the header will all be there.
|
|
*/
|
|
line = s_new();
|
|
hdr = s_new();
|
|
while(sawdot == 0 && s_len(hdr) < 16*1024){
|
|
n = getcrnl(s_reset(line), &bin);
|
|
|
|
/* eof or error ends the message */
|
|
if(n <= 0)
|
|
break;
|
|
|
|
/* a line with only a '.' ends the message */
|
|
cp = s_to_c(line);
|
|
if(n == 2 && *cp == '.' && *(cp+1) == '\n'){
|
|
sawdot = 1;
|
|
break;
|
|
}
|
|
|
|
s_append(hdr, *cp == '.' ? cp+1 : cp);
|
|
}
|
|
|
|
/*
|
|
* parse header
|
|
*/
|
|
yyinit(s_to_c(hdr), s_len(hdr));
|
|
yyparse();
|
|
|
|
/*
|
|
* Look for masquerades. Let Sender: trump From: to allow mailing list
|
|
* forwarded messages.
|
|
*/
|
|
if(fflag)
|
|
nbytes += forgedheaderwarnings();
|
|
|
|
/*
|
|
* add an orginator and/or destination if either is missing
|
|
*/
|
|
if(originator == 0){
|
|
if(senders.last == nil)
|
|
Bprint(pp->std[0]->fp, "From: /dev/null@%s\n", him);
|
|
else
|
|
Bprint(pp->std[0]->fp, "From: %s\n", s_to_c(senders.last->p));
|
|
}
|
|
if(destination == 0){
|
|
Bprint(pp->std[0]->fp, "To: ");
|
|
for(l = rcvers.first; l; l = l->next){
|
|
if(l != rcvers.first)
|
|
Bprint(pp->std[0]->fp, ", ");
|
|
Bprint(pp->std[0]->fp, "%s", s_to_c(l->p));
|
|
}
|
|
Bprint(pp->std[0]->fp, "\n");
|
|
}
|
|
|
|
/*
|
|
* add sender's domain to any domainless addresses
|
|
* (to avoid forging local addresses)
|
|
*/
|
|
cp = s_to_c(hdr);
|
|
for(f = firstfield; cp != nil && f; f = f->next){
|
|
for(p = f->node; cp != 0 && p; p = p->next)
|
|
cp = bprintnode(pp->std[0]->fp, p);
|
|
if(status == 0 && Bprint(pp->std[0]->fp, "\n") < 0){
|
|
piperror = "write error";
|
|
status = 1;
|
|
}
|
|
}
|
|
if(cp == nil){
|
|
piperror = "sender domain";
|
|
status = 1;
|
|
}
|
|
|
|
/* write anything we read following the header */
|
|
if(status == 0 && Bwrite(pp->std[0]->fp, cp, s_to_c(hdr) + s_len(hdr) - cp) < 0){
|
|
piperror = "write error 2";
|
|
status = 1;
|
|
}
|
|
s_free(hdr);
|
|
|
|
/*
|
|
* pass rest of message to mailer. take care of '.'
|
|
* escapes.
|
|
*/
|
|
while(sawdot == 0){
|
|
n = getcrnl(s_reset(line), &bin);
|
|
|
|
/* eof or error ends the message */
|
|
if(n <= 0)
|
|
break;
|
|
|
|
/* a line with only a '.' ends the message */
|
|
cp = s_to_c(line);
|
|
if(n == 2 && *cp == '.' && *(cp+1) == '\n'){
|
|
sawdot = 1;
|
|
break;
|
|
}
|
|
nbytes += n;
|
|
if(status == 0 && Bwrite(pp->std[0]->fp, *cp == '.' ? cp+1 : cp, n) < 0){
|
|
piperror = "write error 3";
|
|
status = 1;
|
|
}
|
|
}
|
|
s_free(line);
|
|
if(sawdot == 0){
|
|
/* message did not terminate normally */
|
|
snprint(pipbuf, sizeof pipbuf, "network eof: %r");
|
|
piperror = pipbuf;
|
|
syskillpg(pp->pid);
|
|
status = 1;
|
|
}
|
|
|
|
if(status == 0 && Bflush(pp->std[0]->fp) < 0){
|
|
piperror = "write error 4";
|
|
status = 1;
|
|
}
|
|
stream_free(pp->std[0]);
|
|
pp->std[0] = 0;
|
|
*byteswritten = nbytes;
|
|
pipesigoff();
|
|
if(status && !piperror)
|
|
piperror = "write on closed pipe";
|
|
return status;
|
|
}
|
|
|
|
char*
|
|
firstline(char *x)
|
|
{
|
|
static char buf[128];
|
|
char *p;
|
|
|
|
strncpy(buf, x, sizeof(buf));
|
|
buf[sizeof(buf)-1] = 0;
|
|
p = strchr(buf, '\n');
|
|
if(p)
|
|
*p = 0;
|
|
return buf;
|
|
}
|
|
|
|
int
|
|
sendermxcheck(void)
|
|
{
|
|
char *cp, *senddom, *user;
|
|
char *who;
|
|
int pid;
|
|
Waitmsg *w;
|
|
|
|
who = s_to_c(senders.first->p);
|
|
if(strcmp(who, "/dev/null") == 0){
|
|
/* /dev/null can only send to one rcpt at a time */
|
|
if(rcvers.first != rcvers.last){
|
|
werrstr("rejected: /dev/null sending to multiple recipients");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if(access("/mail/lib/validatesender", AEXEC) < 0)
|
|
return 0;
|
|
|
|
senddom = strdup(who);
|
|
if((cp = strchr(senddom, '!')) == nil){
|
|
werrstr("rejected: domainless sender %s", who);
|
|
free(senddom);
|
|
return -1;
|
|
}
|
|
*cp++ = 0;
|
|
user = cp;
|
|
|
|
switch(pid = fork()){
|
|
case -1:
|
|
werrstr("deferred: fork: %r");
|
|
return -1;
|
|
case 0:
|
|
/*
|
|
* Could add an option with the remote IP address
|
|
* to allow validatesender to implement SPF eventually.
|
|
*/
|
|
execl("/mail/lib/validatesender", "validatesender",
|
|
"-n", nci->root, senddom, user, nil);
|
|
_exits("exec validatesender: %r");
|
|
default:
|
|
break;
|
|
}
|
|
|
|
free(senddom);
|
|
w = wait();
|
|
if(w == nil){
|
|
werrstr("deferred: wait failed: %r");
|
|
return -1;
|
|
}
|
|
if(w->pid != pid){
|
|
werrstr("deferred: wait returned wrong pid %d != %d", w->pid, pid);
|
|
free(w);
|
|
return -1;
|
|
}
|
|
if(w->msg[0] == 0){
|
|
free(w);
|
|
return 0;
|
|
}
|
|
/*
|
|
* skip over validatesender 143123132: prefix from rc.
|
|
*/
|
|
cp = strchr(w->msg, ':');
|
|
if(cp && *(cp+1) == ' ')
|
|
werrstr("%s", cp+2);
|
|
else
|
|
werrstr("%s", w->msg);
|
|
free(w);
|
|
return -1;
|
|
}
|
|
|
|
void
|
|
data(void)
|
|
{
|
|
String *cmd;
|
|
String *err;
|
|
int status, nbytes;
|
|
char *cp, *ep;
|
|
char errx[ERRMAX];
|
|
Link *l;
|
|
|
|
if(rejectcheck())
|
|
return;
|
|
if(senders.last == 0){
|
|
reply("503 Data without MAIL FROM:\r\n");
|
|
rejectcount++;
|
|
return;
|
|
}
|
|
if(rcvers.last == 0){
|
|
reply("503 Data without RCPT TO:\r\n");
|
|
rejectcount++;
|
|
return;
|
|
}
|
|
if(sendermxcheck()){
|
|
rerrstr(errx, sizeof errx);
|
|
if(strncmp(errx, "rejected:", 9) == 0)
|
|
reply("554 %s\r\n", errx);
|
|
else
|
|
reply("450 %s\r\n", errx);
|
|
for(l=rcvers.first; l; l=l->next)
|
|
syslog(0, "smtpd", "[%s/%s] %s -> %s sendercheck: %s",
|
|
him, nci->rsys, s_to_c(senders.first->p),
|
|
s_to_c(l->p), errx);
|
|
rejectcount++;
|
|
return;
|
|
}
|
|
|
|
cmd = startcmd();
|
|
if(cmd == 0)
|
|
return;
|
|
|
|
reply("354 Input message; end with <CRLF>.<CRLF>\r\n");
|
|
|
|
/*
|
|
* allow 145 more minutes to move the data
|
|
*/
|
|
alarm(145*60*1000);
|
|
|
|
status = pipemsg(&nbytes);
|
|
|
|
/*
|
|
* read any error messages
|
|
*/
|
|
err = s_new();
|
|
while(s_read_line(pp->std[2]->fp, err))
|
|
;
|
|
|
|
alarm(0);
|
|
atnotify(catchalarm, 0);
|
|
|
|
status |= proc_wait(pp);
|
|
if(debug){
|
|
seek(2, 0, 2);
|
|
fprint(2, "%d status %ux\n", getpid(), status);
|
|
if(*s_to_c(err))
|
|
fprint(2, "%d error %s\n", getpid(), s_to_c(err));
|
|
}
|
|
|
|
/*
|
|
* if process terminated abnormally, send back error message
|
|
*/
|
|
if(status){
|
|
int code;
|
|
|
|
if(strstr(s_to_c(err), "mail refused")){
|
|
syslog(0, "smtpd", "++[%s/%s] %s %s refused: %s", him, nci->rsys,
|
|
s_to_c(senders.first->p), s_to_c(cmd), firstline(s_to_c(err)));
|
|
code = 554;
|
|
} else {
|
|
syslog(0, "smtpd", "++[%s/%s] %s %s %s%s%sreturned %#q %s", him, nci->rsys,
|
|
s_to_c(senders.first->p), s_to_c(cmd),
|
|
piperror ? "error during pipemsg: " : "",
|
|
piperror ? piperror : "",
|
|
piperror ? "; " : "",
|
|
pp->waitmsg->msg, firstline(s_to_c(err)));
|
|
code = 450;
|
|
}
|
|
for(cp = s_to_c(err); ep = strchr(cp, '\n'); cp = ep){
|
|
*ep++ = 0;
|
|
reply("%d-%s\r\n", code, cp);
|
|
}
|
|
reply("%d mail process terminated abnormally\r\n", code);
|
|
} else {
|
|
/*
|
|
* if a message appeared on stderr, despite good status,
|
|
* log it. this can happen if rewrite.in contains a bad
|
|
* r.e., for example.
|
|
*/
|
|
if(*s_to_c(err))
|
|
syslog(0, "smtpd",
|
|
"%s returned good status, but said: %s",
|
|
s_to_c(mailer), s_to_c(err));
|
|
|
|
if(filterstate == BLOCKED)
|
|
reply("554 we believe this is spam. we don't accept it.\r\n");
|
|
else
|
|
if(filterstate == DELAY)
|
|
reply("554 There will be a delay in delivery of this message.\r\n");
|
|
else {
|
|
reply("250 sent\r\n");
|
|
logcall(nbytes);
|
|
}
|
|
}
|
|
proc_free(pp);
|
|
pp = 0;
|
|
s_free(cmd);
|
|
s_free(err);
|
|
|
|
listfree(&senders);
|
|
listfree(&rcvers);
|
|
}
|
|
|
|
/*
|
|
* when we have blocked a transaction based on IP address, there is nothing
|
|
* that the sender can do to convince us to take the message. after the
|
|
* first rejection, some spammers continually RSET and give a new MAIL FROM:
|
|
* filling our logs with rejections. rejectcheck() limits the retries and
|
|
* swiftly rejects all further commands after the first 500-series message
|
|
* is issued.
|
|
*/
|
|
int
|
|
rejectcheck(void)
|
|
{
|
|
|
|
if(rejectcount > MAXREJECTS){
|
|
syslog(0, "smtpd", "Rejected (%s/%s)", him, nci->rsys);
|
|
reply("554 too many errors. transaction failed.\r\n");
|
|
exits("errcount");
|
|
}
|
|
if(hardreject){
|
|
rejectcount++;
|
|
reply("554 We don't accept mail from dial-up ports.\r\n");
|
|
}
|
|
return hardreject;
|
|
}
|
|
|
|
/*
|
|
* create abs path of the mailer
|
|
*/
|
|
String*
|
|
mailerpath(char *p)
|
|
{
|
|
String *s;
|
|
|
|
if(p == nil)
|
|
return nil;
|
|
if(*p == '/')
|
|
return s_copy(p);
|
|
s = s_new();
|
|
s_append(s, UPASBIN);
|
|
s_append(s, "/");
|
|
s_append(s, p);
|
|
return s;
|
|
}
|
|
|
|
String *
|
|
s_dec64(String *sin)
|
|
{
|
|
String *sout;
|
|
int lin, lout;
|
|
lin = s_len(sin);
|
|
|
|
/*
|
|
* if the string is coming from smtpd.y, it will have no nl.
|
|
* if it is coming from getcrnl below, it will have an nl.
|
|
*/
|
|
if (*(s_to_c(sin)+lin-1) == '\n')
|
|
lin--;
|
|
sout = s_newalloc(lin+1);
|
|
lout = dec64((uchar *)s_to_c(sout), lin, s_to_c(sin), lin);
|
|
if (lout < 0) {
|
|
s_free(sout);
|
|
return nil;
|
|
}
|
|
sout->ptr = sout->base + lout;
|
|
s_terminate(sout);
|
|
return sout;
|
|
}
|
|
|
|
void
|
|
starttls(void)
|
|
{
|
|
uchar *cert;
|
|
int certlen, fd;
|
|
TLSconn *conn;
|
|
|
|
conn = mallocz(sizeof *conn, 1);
|
|
cert = readcert(tlscert, &certlen);
|
|
if (conn == nil || cert == nil) {
|
|
if (conn != nil)
|
|
free(conn);
|
|
reply("454 TLS not available\r\n");
|
|
return;
|
|
}
|
|
reply("220 Go ahead make my day\r\n");
|
|
conn->cert = cert;
|
|
conn->certlen = certlen;
|
|
fd = tlsServer(Bfildes(&bin), conn);
|
|
if (fd < 0) {
|
|
free(cert);
|
|
free(conn);
|
|
syslog(0, "smtpd", "TLS start-up failed with %s", him);
|
|
|
|
/* force the client to hang up */
|
|
close(Bfildes(&bin)); /* probably fd 0 */
|
|
close(1);
|
|
exits("tls failed");
|
|
}
|
|
Bterm(&bin);
|
|
Binit(&bin, fd, OREAD);
|
|
if (dup(fd, 1) < 0)
|
|
fprint(2, "dup of %d failed: %r\n", fd);
|
|
passwordinclear = 1;
|
|
syslog(0, "smtpd", "started TLS with %s", him);
|
|
}
|
|
|
|
void
|
|
auth(String *mech, String *resp)
|
|
{
|
|
Chalstate *chs = nil;
|
|
AuthInfo *ai = nil;
|
|
String *s_resp1_64 = nil;
|
|
String *s_resp2_64 = nil;
|
|
String *s_resp1 = nil;
|
|
String *s_resp2 = nil;
|
|
char *scratch = nil;
|
|
char *user, *pass;
|
|
|
|
if (rejectcheck())
|
|
goto bomb_out;
|
|
|
|
syslog(0, "smtpd", "auth(%s, %s) from %s", s_to_c(mech),
|
|
"(protected)", him);
|
|
|
|
if (authenticated) {
|
|
bad_sequence:
|
|
rejectcount++;
|
|
reply("503 Bad sequence of commands\r\n");
|
|
goto bomb_out;
|
|
}
|
|
if (cistrcmp(s_to_c(mech), "plain") == 0) {
|
|
|
|
if (!passwordinclear) {
|
|
rejectcount++;
|
|
reply("538 Encryption required for requested authentication mechanism\r\n");
|
|
goto bomb_out;
|
|
}
|
|
s_resp1_64 = resp;
|
|
if (s_resp1_64 == nil) {
|
|
reply("334 \r\n");
|
|
s_resp1_64 = s_new();
|
|
if (getcrnl(s_resp1_64, &bin) <= 0) {
|
|
goto bad_sequence;
|
|
}
|
|
}
|
|
s_resp1 = s_dec64(s_resp1_64);
|
|
if (s_resp1 == nil) {
|
|
rejectcount++;
|
|
reply("501 Cannot decode base64\r\n");
|
|
goto bomb_out;
|
|
}
|
|
memset(s_to_c(s_resp1_64), 'X', s_len(s_resp1_64));
|
|
user = (s_to_c(s_resp1) + strlen(s_to_c(s_resp1)) + 1);
|
|
pass = user + (strlen(user) + 1);
|
|
ai = auth_userpasswd(user, pass);
|
|
authenticated = ai != nil;
|
|
memset(pass, 'X', strlen(pass));
|
|
goto windup;
|
|
}
|
|
else if (cistrcmp(s_to_c(mech), "login") == 0) {
|
|
|
|
if (!passwordinclear) {
|
|
rejectcount++;
|
|
reply("538 Encryption required for requested authentication mechanism\r\n");
|
|
goto bomb_out;
|
|
}
|
|
if (resp == nil) {
|
|
reply("334 VXNlcm5hbWU6\r\n");
|
|
s_resp1_64 = s_new();
|
|
if (getcrnl(s_resp1_64, &bin) <= 0)
|
|
goto bad_sequence;
|
|
}
|
|
reply("334 UGFzc3dvcmQ6\r\n");
|
|
s_resp2_64 = s_new();
|
|
if (getcrnl(s_resp2_64, &bin) <= 0)
|
|
goto bad_sequence;
|
|
s_resp1 = s_dec64(s_resp1_64);
|
|
s_resp2 = s_dec64(s_resp2_64);
|
|
memset(s_to_c(s_resp2_64), 'X', s_len(s_resp2_64));
|
|
if (s_resp1 == nil || s_resp2 == nil) {
|
|
rejectcount++;
|
|
reply("501 Cannot decode base64\r\n");
|
|
goto bomb_out;
|
|
}
|
|
ai = auth_userpasswd(s_to_c(s_resp1), s_to_c(s_resp2));
|
|
authenticated = ai != nil;
|
|
memset(s_to_c(s_resp2), 'X', s_len(s_resp2));
|
|
windup:
|
|
if (authenticated)
|
|
reply("235 Authentication successful\r\n");
|
|
else {
|
|
rejectcount++;
|
|
reply("535 Authentication failed\r\n");
|
|
}
|
|
goto bomb_out;
|
|
}
|
|
else if (cistrcmp(s_to_c(mech), "cram-md5") == 0) {
|
|
char *resp;
|
|
int chal64n;
|
|
char *t;
|
|
|
|
chs = auth_challenge("proto=cram role=server");
|
|
if (chs == nil) {
|
|
rejectcount++;
|
|
reply("501 Couldn't get CRAM-MD5 challenge\r\n");
|
|
goto bomb_out;
|
|
}
|
|
scratch = malloc(chs->nchal * 2 + 1);
|
|
chal64n = enc64(scratch, chs->nchal * 2, (uchar *)chs->chal, chs->nchal);
|
|
scratch[chal64n] = 0;
|
|
reply("334 %s\r\n", scratch);
|
|
s_resp1_64 = s_new();
|
|
if (getcrnl(s_resp1_64, &bin) <= 0)
|
|
goto bad_sequence;
|
|
s_resp1 = s_dec64(s_resp1_64);
|
|
if (s_resp1 == nil) {
|
|
rejectcount++;
|
|
reply("501 Cannot decode base64\r\n");
|
|
goto bomb_out;
|
|
}
|
|
/* should be of form <user><space><response> */
|
|
resp = s_to_c(s_resp1);
|
|
t = strchr(resp, ' ');
|
|
if (t == nil) {
|
|
rejectcount++;
|
|
reply("501 Poorly formed CRAM-MD5 response\r\n");
|
|
goto bomb_out;
|
|
}
|
|
*t++ = 0;
|
|
chs->user = resp;
|
|
chs->resp = t;
|
|
chs->nresp = strlen(t);
|
|
ai = auth_response(chs);
|
|
authenticated = ai != nil;
|
|
goto windup;
|
|
}
|
|
rejectcount++;
|
|
reply("501 Unrecognised authentication type %s\r\n", s_to_c(mech));
|
|
bomb_out:
|
|
if (ai)
|
|
auth_freeAI(ai);
|
|
if (chs)
|
|
auth_freechal(chs);
|
|
if (scratch)
|
|
free(scratch);
|
|
if (s_resp1)
|
|
s_free(s_resp1);
|
|
if (s_resp2)
|
|
s_free(s_resp2);
|
|
if (s_resp1_64)
|
|
s_free(s_resp1_64);
|
|
if (s_resp2_64)
|
|
s_free(s_resp2_64);
|
|
}
|