Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

#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)
{
        static 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) ", s_to_c(path));

        if(trusted || path == nil) {
                if(debug)
                        fprint(2, "0\n");
                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);
        if (debug)
                fprint(2, "%d\n", rv);
        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;
}