#include #include #include #include #include //check host error codes enum { None, Pass, Neutral, Fail, SoftFail, TempError, PermError }; char *statwords[]={ [None] "None", [Pass] "Pass", [Neutral] "Neutral", [Fail] "Fail", [SoftFail] "SoftFail", [TempError] "TempError", [PermError] "PermError" }; // lex status enum { TERM, MORE, DONE, ERROR }; enum { NONE, PLUS, MINUS, QMARK, TILDE, DDOT, UNKNOWN, VERSION, SID, REDIRECT, EXP, ALL, INCLUDE, A, MX, PTR, IP4, IP6, EXISTS, EXPL, END, SPF, TXT }; char *Keywords[]={ [NONE] " ", [PLUS] "+", [MINUS] "-", [QMARK] "?", [TILDE] "~", [DDOT] ":", [UNKNOWN] "unknown", [VERSION] "v=", [SID] "spf2.0/pra", [REDIRECT] "redirect", [EXP] "exp", [ALL] "all", [INCLUDE] "include", [A] "a", [MX] "mx", [PTR] "ptr", [IP4] "ip4", [IP6] "ip6", [EXISTS] "exists", [EXPL] "exp", [END] "end", [SPF] "spf", [TXT] "txt", }; #define MAXDIGITS 4 #define MAXTOKEN 128 #define MAXTERM 1000 #define MAXVAL 100 #define MAXDEEP 10 #define MAXQUERY 50 typedef struct Symbol Symbol; typedef struct Val Val; struct Val { uchar ip[16]; uchar mask[16]; char s[MAXVAL]; char n[MAXVAL]; }; struct Symbol { int pos; // position int len; int ismacro; int isdomainspec; int op; // mechanism int q; // qualifier int m; // modifier int err; // error message in val int n; // number of val char t[MAXVAL]; Val *v; }; Symbol Z={0,0,0,0,0,0,0,0,0,nil}; char *macrovars[11]; int DEBUG=0; int WHITELIST=0; int chkdeep; int check_host(uchar *ip, char *domain, int deep); /* utils */ void* emalloc(ulong sz) { void *v; if((v=malloc(sz)) == nil) { fprint(2, "out of memory allocating %lud\n", sz); exits("mem"); } memset(v, 0, sz); setmalloctag(v, getcallerpc(&sz)); return v; } void* erealloc(void *v, ulong sz) { void *nv; if((nv=realloc(v, sz)) == nil) { fprint(2, "out of memory allocating %lud\n", sz); exits("mem"); } if(v == nil) setmalloctag(nv, getcallerpc(&v)); setrealloctag(nv, getcallerpc(&v)); return nv; } void debug(char *fmt, ... ) { va_list arg; char buf[20000]; if ( ! DEBUG ) return; va_start(arg, fmt); vseprint(buf, buf+20000, fmt, arg); va_end(arg); fprint(2,"%s\n",buf); } /* give to parser the next element thanks quintile :) */ int lex(char *src, char **dstp, int *pos, int *len) { char *s, *d; s = src + *pos; while(isspace(*s)) s++; if(*s =='\0' || *s == '\n') return DONE; d = *dstp; while(isprint(*s) && !isspace(*s)){ if((d - *dstp) >= MAXTERM-1) sysfatal("lex: corrupt SPF record - term too long\n"); *d = (isupper(*s))? tolower(*s): *s; s++; d++; } *d = 0; *len = d - *dstp; *pos = s - src; return MORE; } int match(char *s) { for(int i=0;in = %d",S->n); for(int i=0;in;i++) { memset(mask,0xff,16); if ( strlen(S->v[i].s) == 0 ) { debug("expandip(): zero length value found: %s",S->v[i].s); return PermError; } hascidr=strchr(S->v[i].s,'/'); v4=parseip(ip,S->v[i].s); if(hascidr != nil) parseipmask(mask,hascidr); if (v4 != 6) v4tov6(S->v[i].mask,mask); else memmove(S->v[i].mask,mask,16); memmove(S->v[i].ip,ip,16); } return None; } int isanip(char *name) { uchar isip[16]; int len; if ( name == nil ) return 0; len=strlen(name); if ( parseip(isip,name)!=0 && isdigit(name[0]) && isdigit(name[len-1]) ) return 1; return 0; } char* getaddress(char *str) { char *p; int len; if ( str == nil ) return nil; len=strlen(str); if ( len < 3) return nil; p=str+len-2; // skipo \0 and \n from str while ( p != str && !isspace(*p)) p--; if ( isspace(*p) ) p++; return strdup(p); } /* it will return up to nl lines */ int ress(char *q, char **lines, int nl) { int fd; int i,n; char buf[1024]; if ( (fd=open("/net/dns", ORDWR)) < 0 ) return -1; seek(fd, 0, 0); if(write(fd, q, strlen(q)) < 0) { snprint(buf,1024,"%r"); close(fd); if ( strstr(buf,"dns: name does not exist") != 0 ) return None; else return -1; } seek(fd, 0, 0); i=0; while((n=read(fd, buf,sizeof(buf))) > 0 && i= line){ len++; p--; if(*p == '.'){ memmove(np, p+1, len); np += len; len = 0; } } memmove(np, p+1, len); np += len; strcpy(np, "in-addr.arpa"); strcpy(line, buf); return strdup(line); } int addip(Symbol *S, char *addr, char *cidr, char *n) { S->n++; debug("addip() S->n == %d",S->n); if ( S->n == 1 ) S->v = emalloc(sizeof(Val)); else S->v = erealloc(S->v,sizeof(Val)*S->n); if (cidr) snprint(S->v[S->n-1].s,MAXVAL,"%s/%s",addr,cidr); else snprint(S->v[S->n-1].s,MAXVAL,"%s",addr); if (n) snprint(S->v[S->n-1].n,MAXVAL,"%s",n); return None; } int symbres(Symbol *S, char *str, int deep) { int j,i; char *query, *hascidr=nil; char *names[MAXQUERY]; char *addr; debug("symbress(): deep = %d",deep); if ( deep > MAXDEEP) return None; if ( deep > 0 ) query=smprint("%s ip\n",str); else { if ( hascidr=strchr(str,'/') ) { *hascidr=0; hascidr++; } switch(S->m) { case A: query=smprint("%s ip\n",str); break; case MX: query=smprint("%s mx\n",str); break; case PTR: query=smprint("%s ptr\n",ptrq(str)); break; case EXISTS: case NONE: query=smprint("%s ip\n",str); break; case SPF: query=smprint("%s spf\n",str); break; case TXT: query=smprint("%s txt\n",str); break; default: debug("symbres(): unknown operation %d\n",S->m); return PermError; } } i=ress(query,names,MAXQUERY); for(j=0;j 0) { if ((*p >= '0' && *p <= '9') && numdigits < (MAXDIGITS-1)) { digits[numdigits]=*p; numdigits++; } if (*p == 'r') isreverse=1; } if ((p-start) > (numdigits+isreverse)) { switch(*p){ case '.': case '-': case '+': case ',': case '/': case '_': case '=': delim=smprint("%c",*p); break; } } p++; } /* macro end */ digits[numdigits]='\0'; ntok=atoi(digits); p++; // skip the closing curly if (var == -1) { return nil; } else { if (delim == nil) delim=smprint("."); aux=smprint("%s",macrovars[var]); n=getfields(aux, tok, MAXTOKEN, 1, delim); if (ntok > n || ntok == 0) ntok=n; if (ntok == 0) fmtprint(&fmt,"%s",macrovars[var]); else { /* If transformers or delimiters are provided, the replacement value for a macro letter is split into parts. After performing any reversal operation and/or removal of left-hand parts, the parts are rejoined using "." and not the original splitting characters. */ if (isreverse) { for(j=(n-1); j>=(n-ntok); j--) fmtprint(&fmt,"%s%s",tok[j], (j > (n-ntok)) ? ".": ""); } else { for(j=0; jq=match(ptr); if (S->q <= UNKNOWN) { ptr++; S->m=match(ptr); } else { /* + is the implicit qualifier */ S->m=S->q; S->q=PLUS; } len = strlen(Keywords[(*pts)->m]); ptr+=len; if ( (ptr-term) < 0 ) sysfatal("parse(): error parsing!!!"); aux=nil; /* if command has : it is specifiying a domain-spec that should be expanded as a macro */ if ( *ptr == ':' || *ptr == '=' ) { S->isdomainspec=1; ptr++; aux=smprint("%s",ptr); macro=expandmacro(aux); if (macro == nil ) return PermError; else len=strlen(macro); } else { macro=smprint("%s",ptr); len=strlen(macro); } free(aux); stat=None; /* mechanism parsing */ debug("parse(): S->m =%s\n",Keywords[S->m]); switch(S->m){ case VERSION: case SID: case ALL: S->v = emalloc(sizeof(Val)); snprint(S->v[0].s,MAXVAL,"%s",macro); break; case IP4: case IP6: S->n=1; S->v = emalloc(sizeof(Val)); snprint(S->v[0].s,MAXVAL,"%s",macro); stat=expandip(S); break; case REDIRECT: case EXPL: case EXISTS: case INCLUDE: S->v = emalloc(sizeof(Val)); snprint(S->v[0].s,MAXVAL,"%s",macro); break; case A: case MX: /* minimun len is 3 becouse mx/24 is a valid construct */ if (len<4) { aux=smprint("%s%s",macrovars[2],macro); /* sender domain */ free(macro); macro=aux; } debug("parse(): mx: aux = %s",macro); stat=symbres(S,macro,0); if ( stat != None ) { debug("parse(): a/mx error received from symbres: %s",macro); break; } stat=expandip(S); break; case PTR: if ( len > 1 ) strncpy(S->t,macro,MAXVAL); else strncpy(S->t,macrovars[2],MAXVAL); stat=symbres(S,macrovars[4],0); if ( stat != None ) { debug("parse(): ptr error received from symbres"); break; } stat=expandip(S); break; default: /* user defined macros */ stat=symbres(S,macro,0); break; } free(macro); return stat; } int testq(int q) { int val; switch(q) { case PLUS: val=Pass; break; case MINUS: val=Fail; break; case QMARK: val=Neutral; break; case TILDE: val=SoftFail; break; default: val=Pass; break; }; return val; } int eval(Symbol *pts, uchar *ip) { uchar mip1[16]; uchar mip2[16]; int q,i, answer; Symbol *S; S = pts; answer=Fail; q=testq(S->q); memset(mip1,0,16); memset(mip2,0,16); debug("eval(): S->m =%s",Keywords[S->m]); switch(S->m){ case INCLUDE: chkdeep++; answer=check_host(ip,S->v[0].s, chkdeep); break; case REDIRECT: chkdeep++; answer= check_host(ip,S->v[0].s,chkdeep); break; case VERSION: if (strncmp(S->v[0].s,"pf1",3) != 0 ) answer=PermError; else return None; break; case SID: if ( strncmp(S->v[0].s,"spf2.0/pra",10) !=0) answer=PermError; else return None; break; case ALL: return q; break; case IP4: case IP6: for(int i=0;in;i++) { if (WHITELIST) fprint(2,"%I %M\n",S->v[i].ip,S->v[i].mask); maskip(S->v[i].ip, S->v[i].mask, mip1); maskip(ip, S->v[i].mask, mip2); debug("eval(): mip1 %I mip2 %I",mip1,mip2); if (equivip6(mip1, mip2)) { answer=Pass; break; } else answer=Fail; } break; case EXPL: case EXISTS: case A: case MX: /* all of this mechanism matches if is one of the 's IP addresses. */ for(i=0;in;i++) { if (WHITELIST) fprint(2,"%I %M\n",S->v[i].ip,S->v[i].mask); debug("eval(): %I == %I",S->v[i].ip, ip); if (equivip6(S->v[i].ip, ip)) { answer=Pass; break; } else answer=Fail; } break; case PTR: for(i=0;in;i++) { if (WHITELIST) fprint(2,"%I %M\n",S->v[i].ip,S->v[i].mask); debug("eval(): %I == %I",S->v[i].ip, ip); if (equivip6(S->v[i].ip, ip)) { debug("eval(): %s == %s",S->v[i].n, S->t); if ( strstr(S->v[i].n,S->t) != 0 ) { answer=Pass; break; } } else answer=Fail; } break; default: answer=PermError; debug("eval(): no valid symbol found S->m %d",S->m); break; } if (answer == q ) return Pass; return answer; } int check_host(uchar *ip, char *domain, int deep) { Symbol *symbtab; Symbol *ps; int nsymb; int tabsize; int stat, pos, len,lstat; char *rules; char *term; if (deep == MAXDEEP ) return PermError; debug("check_host(): deep == %d",deep); rules=getrules(domain,TXT); if (rules == nil ) { rules=getrules(domain,SPF); if (rules == nil ) { debug("check_host(): unable to find rules"); /* invalid, malformed, or non-existent domains cause SPF checks to return "None" because no SPF record can be found */ return None; } } debug("check_host(): rules %s",rules); /* create the symbol table and parse all elements */ tabsize=10; symbtab=emalloc(sizeof(Symbol)*tabsize); nsymb=stat=pos=len=0; while (1) { if ( nsymb >= tabsize-1) { tabsize+=5; symbtab=erealloc(symbtab,sizeof(Symbol)*tabsize); debug("check_host(): realloc done"); } symbtab[nsymb]=Z; ps=&symbtab[nsymb]; term=emalloc(MAXTERM); lstat=lex(rules, &term,&pos, &len); if (lstat == DONE) break; else if (lstat == MORE) { ps->pos=pos; ps->len=len; } else return PermError; stat=parse(&ps,term); free(term); if ( stat != None) { debug("check_host(): parse error received: %d",stat); return stat; } nsymb++; } /* evaluate the elements and return the result */ for(int i=0;im == ALL) { return stat; } if (ps->m == REDIRECT) return stat; } return stat; } void usage(void) { print("spf [-w] [-D]-u -d -a -l \n"); exits("usage()"); } void main(int argc, char *argv[]) { int stat; char *suser, *sdom, *sip, *ldom, *vipdom=nil; uchar clientip[16]; fmtinstall('I',eipfmt); fmtinstall('V',eipfmt); fmtinstall('M',eipfmt); suser=sdom=sip=ldom=nil; if (argc < 9) usage(); DEBUG=0; ARGBEGIN{ case 'u': suser=ARGF(); /* local-part of sender */ break; case 'd': sdom=ARGF(); /* domain of sender */ break; case 'a': sip=ARGF(); /* client ip */ break; case 'l': ldom=ARGF(); /* local domain */ break; case 'D': DEBUG=1; break; case 'w': WHITELIST=1; break; default: print(" badflag('%c')\n", ARGC()); usage(); } ARGEND; if (argc != 0|| suser==nil || sdom==nil || ldom==nil || sip==nil ) usage(); //vipdom=getrules(sip,PTR); if ( vipdom == nil || *vipdom == '!') { free(vipdom); vipdom=smprint("unknown"); } macrovars[0]=smprint("%s@%s",suser,sdom); /* sender string */ macrovars[1]=strdup(suser); /* local-part of sender */ macrovars[2]=strdup(sdom); /* domain of sender */ macrovars[3]=strdup(sdom); /* domain */ macrovars[4]=strdup(sip); /* ip */ macrovars[5]=strdup(vipdom); /* ip validated domain name */ macrovars[6]=strdup(sdom); /* HELO/EHLO domain */ macrovars[7]=strdup(sip); /* SMTP client IP */ macrovars[8]=strdup(ldom); /* checker domain name */ macrovars[9]=smprint("%ld",time(0)); /* current timestamp */ macrovars[10]=smprint("in-addr"); /* in-addr or ip6 string */ parseip(clientip,sip); chkdeep=0; stat=check_host(clientip,sdom,chkdeep); print("%s\n",statwords[stat]); exits(statwords[stat]); }