/* kbs - Simple, easily fooled, POP3 spam filter Copyright (C) 2003 Ian Cowburn (ianc@noddybox.demon.co.uk) 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 of the License, 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 ------------------------------------------------------------------------- Config */ static const char id[]="$Id$"; #include #include #include #include #include #include "global.h" #include "config.h" #include "dstring.h" #include "dbase.h" #include "rexp.h" #include "util.h" /* ---------------------------------------- TYPES */ typedef enum { TOK_Expression, TOK_Set, TOK_OpenBracket, TOK_CloseBracket, TOK_Domain, TOK_TrustedUsers, TOK_TrustedDomains, TOK_Default, TOK_BlockUser, TOK_AllowSubject, TOK_BlockSubject, TOK_SubjectMacro, TOK_Blacklist, TOK_VarHostname, TOK_VarPort, TOK_VarUsername, TOK_VarPassword, TOK_VarLog, TOK_VarTimeout, TOK_VarCasesense, TOK_VarDejunk, TOK_VarTestmode, TOK_VarBlockHTML, TOK_VarVerbose, TOK_ConstOn, TOK_ConstOff, TOK_ConstBlock, TOK_ConstAllow, TOK_EOF } Token; typedef struct Command { const char *cmd; Token token; } Command; typedef struct { char *macro; char *expansion; } Macro; /* ---------------------------------------- GLOBALS */ static DString error; static char *hostname=NULL; static char *username=NULL; static char *password=NULL; static char *log=NULL; static int port=110; static int timeout=60; static int casesense=FALSE; static int dejunk=FALSE; static int testmode=FALSE; static int blockhtml=FALSE; static int verbose=TRUE; static int no_macro=0; static Macro *macro=NULL; /* ---------------------------------------- COMMAND TABLE */ static const Command cmd_table[]= { /* Commands and main tokens */ {"set", TOK_Set}, {"trusted_users", TOK_TrustedUsers}, {"trusted_domains", TOK_TrustedDomains}, {"{", TOK_OpenBracket}, {"}", TOK_CloseBracket}, {"domain", TOK_Domain}, {"default", TOK_Default}, {"block_user", TOK_BlockUser}, {"allow_subject", TOK_AllowSubject}, {"block_subject", TOK_BlockSubject}, {"subject_macro", TOK_SubjectMacro}, {"blacklist", TOK_Blacklist}, /* Variables */ {"hostname", TOK_VarHostname}, {"port", TOK_VarPort}, {"username", TOK_VarUsername}, {"password", TOK_VarPassword}, {"log", TOK_VarLog}, {"timeout", TOK_VarTimeout}, {"casesense", TOK_VarCasesense}, {"dejunk", TOK_VarDejunk}, {"testmode", TOK_VarTestmode}, {"blockhtml", TOK_VarBlockHTML}, {"verbose", TOK_VarVerbose}, /* Constants */ {"on", TOK_ConstOn}, {"off", TOK_ConstOff}, {"block", TOK_ConstBlock}, {"allow", TOK_ConstAllow}, /* End of table */ {NULL, TOK_EOF} }; /* ---------------------------------------- REQUIRED PROTOS */ static Token GetToken(FILE *fp, DString *ret); /* ---------------------------------------- COMMAND HANDLER UTILS */ static DString ExpandRE(DString re) { DString new; Macro *m; char *p; int f; new=DSInit(); p=re->text; while(*p) { m=NULL; for(f=0;fexpansion); p+=strlen(m->macro); } } DSFree(re); return new; } /* ---------------------------------------- COMMAND HANDLERS */ static int DoSet(FILE *fp) { # define TYPE_STR 0 # define TYPE_INT 1 # define TYPE_ONOFF 2 static const struct VarTable { Token token; int type; void *ptr; } var_table[]= { {TOK_VarHostname, TYPE_STR, (void *)&hostname}, {TOK_VarPort, TYPE_INT, (void *)&port}, {TOK_VarUsername, TYPE_STR, (void *)&username}, {TOK_VarPassword, TYPE_STR, (void *)&password}, {TOK_VarLog, TYPE_STR, (void *)&log}, {TOK_VarTimeout, TYPE_INT, (void *)&timeout}, {TOK_VarCasesense, TYPE_ONOFF, (void *)&casesense}, {TOK_VarDejunk, TYPE_ONOFF, (void *)&dejunk}, {TOK_VarTestmode, TYPE_ONOFF, (void *)&testmode}, {TOK_VarBlockHTML, TYPE_ONOFF, (void *)&blockhtml}, {TOK_VarVerbose, TYPE_ONOFF, (void *)&verbose}, {TOK_EOF,0,NULL} }; const struct VarTable *vt=NULL; DString ds; Token tok; int status=TRUE; int f; ds=DSInit(); tok=GetToken(fp,&ds); for(f=0;var_table[f].token!=TOK_EOF && !vt;f++) if (var_table[f].token==tok) vt=var_table+f; if (vt) { char **cp; int *ip; switch(vt->type) { case TYPE_STR: cp=vt->ptr; tok=GetToken(fp,&ds); if (*cp) free(*cp); *cp=CopyStr(ds->text); break; case TYPE_INT: ip=vt->ptr; tok=GetToken(fp,&ds); *ip=atoi(ds->text); break; case TYPE_ONOFF: ip=vt->ptr; tok=GetToken(fp,&ds); switch(tok) { case TOK_ConstOn: *ip=TRUE; break; case TOK_ConstOff: *ip=FALSE; break; default: status=FALSE; DSAddCP(error,"Expected on/off. Got: "); DSAddDS(error,ds); } break; } } else { status=FALSE; DSAddCP(error,"Unrecognised variable: "); DSAddDS(error,ds); } DSFree(ds); # undef TYPE_STR # undef TYPE_INT # undef TYPE_ONOFF return status; } static int DoDomain(FILE *fp) { DString ds; Token tok; Domain *domain; RE_Expression re; ds=DSInit(); tok=GetToken(fp,&ds); if (tok!=TOK_Expression) { DSAddCP(error,"Domain must be followed by expression. Got: "); DSAddDS(error,ds); DSFree(ds); return FALSE; } if (!(re=RECompile(ds->text))) { DSAddCP(error,"Bad regular expression: "); DSAddDS(error,ds); DSFree(ds); return FALSE; } domain=DBNewDomain(re); tok=GetToken(fp,&ds); if (tok!=TOK_OpenBracket) { DSAddCP(error,"Missing opening bracket in domain"); DSFree(ds); return FALSE; } while((tok=GetToken(fp,&ds))!=TOK_CloseBracket) { if (tok==TOK_EOF) { DSAddCP(error,"Missing close bracket in domain"); DSFree(ds); return FALSE; } switch(tok) { case TOK_Default: switch(GetToken(fp,&ds)) { case TOK_ConstAllow: DBDefault(domain,FALSE); break; case TOK_ConstBlock: DBDefault(domain,TRUE); break; default: DSAddCP(error,"Unexpected value in default command: "); DSAddDS(error,ds); DSFree(ds); return FALSE; break; } break; case TOK_BlockUser: GetToken(fp,&ds); DBBlockUser(domain,ds->text); break; case TOK_AllowSubject: GetToken(fp,&ds); ds=ExpandRE(ds); if (!(re=RECompile(ds->text))) { DSAddCP(error,"Bad regular expression: "); DSAddDS(error,ds); DSFree(ds); return FALSE; } DBAllowSubject(domain,re); break; case TOK_BlockSubject: GetToken(fp,&ds); ds=ExpandRE(ds); if (!(re=RECompile(ds->text))) { DSAddCP(error,"Bad regular expression: "); DSAddDS(error,ds); DSFree(ds); return FALSE; } DBBlockSubject(domain,re); break; default: DSAddCP(error,"Unexpected string in domain command: "); DSAddDS(error,ds); DSFree(ds); return FALSE; break; } } DSFree(ds); return TRUE; } static int DoTrustedUsers(FILE *fp) { DString ds; Token tok; ds=DSInit(); tok=GetToken(fp,&ds); if (tok!=TOK_OpenBracket) { DSAddCP(error,"Missing opening bracket in trusted_users"); DSFree(ds); return FALSE; } while((tok=GetToken(fp,&ds))!=TOK_CloseBracket) { if (tok==TOK_EOF) { DSAddCP(error,"Missing close bracket in trusted_users"); DSFree(ds); return FALSE; } DBTrustedUser(ds->text); } DSFree(ds); return TRUE; } static int DoTrustedDomains(FILE *fp) { DString ds; Token tok; ds=DSInit(); tok=GetToken(fp,&ds); if (tok!=TOK_OpenBracket) { DSAddCP(error,"Missing opening bracket in trusted_domains"); return FALSE; } while((tok=GetToken(fp,&ds))!=TOK_CloseBracket) { if (tok==TOK_EOF) { DSAddCP(error,"Missing close bracket in trusted_domains"); return FALSE; } DBTrustedDomain(ds->text); } return TRUE; } static int DoSubjectMacro(FILE *fp) { DString arg[2]; int f; for(f=0;f<2;f++) { arg[f]=DSInit(); if (GetToken(fp,&arg[f])==TOK_EOF) { DSAddCP(error,"Unexpected EOF in subject_macro"); return FALSE; } } if (arg[0]->len==0 || arg[1]->len==0) { DSAddCP(error,"Can't use zero length strings in subject_macro"); return FALSE; } no_macro++; macro=Realloc(macro,sizeof *macro * no_macro); macro[no_macro-1].macro=CopyStr(arg[0]->text); macro[no_macro-1].expansion=CopyStr(arg[1]->text); for(f=0;f<2;f++) DSFree(arg[f]); return TRUE; } static int DoBlacklist(FILE *fp) { DString ds; Token tok; ds=DSInit(); tok=GetToken(fp,&ds); if (tok!=TOK_OpenBracket) { DSAddCP(error,"Missing opening bracket in blacklist"); DSFree(ds); return FALSE; } while((tok=GetToken(fp,&ds))!=TOK_CloseBracket) { RE_Expression re; if (tok==TOK_EOF) { DSAddCP(error,"Missing close bracket in blacklist"); DSFree(ds); return FALSE; } if (!(re=RECompile(ds->text))) { DSAddCP(error,"Bad regular expression: "); DSAddDS(error,ds); DSFree(ds); return FALSE; } DBBlacklist(re); } DSFree(ds); return TRUE; } /* ---------------------------------------- PRVIVATE FUNCTIONS */ static int Getc(FILE *fp) { int ch; ch=fgetc(fp); if (ch=='#') { ch=fgetc(fp); while(ch!=EOF && ch!='\n') ch=fgetc(fp); } return ch; } static int SkipWS(FILE *fp) { int ch; ch=Getc(fp); while(isspace(ch)) ch=Getc(fp); if (ch==EOF) return FALSE; else { ungetc(ch,fp); return TRUE; } } static Token FindToken(const char *p) { int f; for(f=0;cmd_table[f].cmd;f++) if (strcmp(cmd_table[f].cmd,p)==0) return cmd_table[f].token; return TOK_Expression; } static int IsTerm(int ch) { return isspace(ch) || ch=='#'; } static Token GetToken(FILE *fp, DString *ret) { int ch; int done=FALSE; int quote=0; Token tok; *ret=DSReset(*ret); if (feof(fp)) { DSAddCP(*ret,"EOF"); return TOK_EOF; } if (!SkipWS(fp)) { DSAddCP(*ret,"EOF"); return TOK_EOF; } while(!done) { ch=fgetc(fp); if (quote) { if (ch==EOF) { done=TRUE; DSAddCP(error,"Unexpected EOF in string"); tok=TOK_EOF; } else if (ch==quote) { done=TRUE; tok=FindToken((*ret)->text); } else { DSAddChar(*ret,ch); } } else { if (ch==EOF) { done=TRUE; if ((*ret)->len) tok=FindToken((*ret)->text); else tok=TOK_EOF; } else if (IsTerm(ch)) { ungetc(ch,fp); done=TRUE; tok=FindToken((*ret)->text); } else if (ch=='\'' || ch=='"') { quote=ch; } else { DSAddChar(*ret,ch); } } } return tok; } static int Parse(FILE *fp) { DString txt; Token tok; txt=DSInit(); while((tok=GetToken(fp,&txt))!=TOK_EOF) { int ok=TRUE; switch(tok) { case TOK_Set: if (!DoSet(fp)) ok=FALSE; break; case TOK_Domain: if (!DoDomain(fp)) ok=FALSE; break; case TOK_TrustedUsers: if (!DoTrustedUsers(fp)) ok=FALSE; break; case TOK_TrustedDomains: if (!DoTrustedDomains(fp)) ok=FALSE; break; case TOK_SubjectMacro: if (!DoSubjectMacro(fp)) ok=FALSE; break; case TOK_Blacklist: if (!DoBlacklist(fp)) ok=FALSE; break; case TOK_Expression: DSAddCP(error,"Unknown command in config file: "); DSAddDS(error,txt); ok=FALSE; break; default: DSAddCP(error,"Unexpected token in config file: "); DSAddDS(error,txt); ok=FALSE; break; } if (!ok) break; txt=DSReset(txt); } DSFree(txt); return error->len==0; } /* ---------------------------------------- INTERFACES */ int ConfigLoad(void) { DString ds; FILE *fp; int ret; error=DSInit(); hostname=CopyStr("localhost"); username=CopyStr("nobody"); password=CopyStr(""); log=CopyStr(""); if (!getenv("HOME")) return FALSE; ds=DSInit(); DSAddCP(ds,getenv("HOME")); DSAddChar(ds,'/'); DSAddCP(ds,".kbsrc"); if ((fp=fopen(ds->text,"r"))) ret=Parse(fp); else ret=FALSE; DSFree(ds); return ret; } const char *ConfigError(void) { if (strlen(error->text)) return error->text; else return strerror(errno); } const char *ConfigString(ConfigStringVar var) { switch(var) { case CONFIG_HOSTNAME: return hostname; case CONFIG_USERNAME: return username; case CONFIG_PASSWORD: return password; case CONFIG_LOG: return log; default: return ""; } } int ConfigInt(ConfigIntVar var) { switch(var) { case CONFIG_PORT: return port; case CONFIG_TIMEOUT: return timeout; case CONFIG_CASESENSE: return casesense; case CONFIG_DEJUNK: return dejunk; case CONFIG_TESTMODE: return testmode; case CONFIG_BLOCKHTML: return blockhtml; case CONFIG_VERBOSE: return verbose; default: return 0; } } void ConfigClose(void) { int f; for(f=0;f