From c378e8f900d85d59a8a616bf0b8b14e426d898e1 Mon Sep 17 00:00:00 2001 From: Ian C Date: Mon, 26 Jan 2004 02:01:49 +0000 Subject: Added allow_to in domain; Added include; Update docs; Fixed folding header lines --- doc/INSTRUCTION | 76 ++++++++++++++++++++++++++++++++++++---------- src/Makefile | 51 ++++++++++++++++++++++++++++++- src/config.c | 81 ++++++++++++++++++++++++++++++++++++++++++++----- src/config.h | 5 +-- src/dbase.c | 43 +++++++++++++++++++++++++- src/dbase.h | 6 ++++ src/kbs.c | 2 +- src/kbsrc | 9 +++++- src/pop3.c | 94 +++++++++++++++++++++++++++++++++++++++++++++++---------- 9 files changed, 322 insertions(+), 45 deletions(-) diff --git a/doc/INSTRUCTION b/doc/INSTRUCTION index 40d91b1..1ac9e8e 100644 --- a/doc/INSTRUCTION +++ b/doc/INSTRUCTION @@ -2,7 +2,9 @@ Usage ===== - kbs + kbs [config-file] + +If config-file is not provided, a default of $HOME/.kbsrc is used. Description @@ -16,11 +18,13 @@ a special keyword in a subject header to be used. Config file format ================== -The kbs config file is read from $HOME/.kbsrc and has sections in the suggested -order: +The kbs config file is read from $HOME/.kbsrc (or the parameter supplied) and +has sections in the suggested order: [config settings] +[include] + [trusted settings] [blacklist settings] @@ -29,7 +33,7 @@ order: [domain settings] -It is *highly* recommended to set .kbsrc to be only readable by the user - this +It is *stringly* suggested to set .kbsrc to be only readable by the user - this is as passwords are stored in there. Blank lines and text proceeded with a hash (#) are ignored. @@ -39,7 +43,7 @@ includes spaces, simple quote them (with either single or double quotes). Escapes aren't used (to make regular expression writing easier), so if you want to include a quote in a string, simply use the other sort of quote. If you -want both, sorry, you're stuck! +want both, sorry! To see an example kbsrc file, see kbsrc in the src directory. @@ -98,7 +102,10 @@ And understand the following variables: blockhtml Whether messages that are pure HTML (content part just reported as "text/html" are blocked. Also blocks - messages with an empty, or missing, content type. + messages with an empty, or missing, content type + WARNING: this will delete mail sent with old command + line mailers as they don't set a content-type - however, + plenty of spam has no valid content-type either. Defaults to off. testmode Whether things will be really deleted, or just @@ -109,6 +116,33 @@ And understand the following variables: (or too easy for that matter). +Include files +============= + +Include files are useful if you want to scan multiple hosts buy using mutiple +config files, eg. + +In file 1: + +set host host1 +set username user1 +set password pass1 + +include "/etc/kbsrc" + + +In file 2: + +set host host2 +set username user2 +set password pass2 + +include "/etc/kbsrc" + + +Then put all your rules as needed in /etc/kbsrc. + + Trusted settings ================ @@ -191,6 +225,7 @@ will be used. { [default block|allow] [block_user ] + [allow_to ] [allow_subject ] [block_subject ] } @@ -202,6 +237,13 @@ The block user allows a specific username to be blocked. For instance, I've noticed that spammers have a great love of emailing from your username at a different domain. +The allow_to sets a regular expression that the 'To: ' line in the header +data must match. This can be handy as for some reason, even though spam may +be directed to your inbox, the 'To:' line will actually read a nonsense name +for your host. Note that it's not recommended to anchor the regular expression +as if the mail has been sent to multiple recipients it is not guaranteed +where your name will appear in the list of users. + The allow_subject means that subjects that match that regular expression are always let through. @@ -213,17 +255,19 @@ Multiple allow and block commands can be in one domain. The commands inside a domain can appear in any order, but the checks are always done in this order: -1. If a trusted domain, allow message. -2. If a trusted user, allow message. -3. If an HTML message and these are blocked, delete message. -4. If the domain is blacklisted, delete message. -5. If the domain is not matched in a domain command, allow message. -6. If the subject is allowed for the domain, allow message. -7. If the username is blocked for the domain, delete message. -8. If the subject is disallowed for the domain, delete message. -9. Delete the message if the default is to block, otherwise allow. + 1. If a trusted domain, allow message. + 2. If a trusted user, allow message. + 3. If an HTML message and these are blocked, delete message. + 4. If the domain is blacklisted, delete message. + 5. If the domain is not matched in a domain command, allow message. + 6. If the subject is allowed for the domain, allow message. + 7. If an allow_to has been set for the domain, and it doesn't match, + delete message. + 8. If the username is blocked for the domain, delete message. + 9. If the subject is disallowed for the domain, delete message. +10. Delete the message if the default is to block, otherwise allow. ------------------------------------------------------------------------------- -$Id: INSTRUCTION,v 1.6 2004-01-01 01:25:04 ianc Exp $ +$Id: INSTRUCTION,v 1.7 2004-01-26 02:01:49 ianc Exp $ diff --git a/src/Makefile b/src/Makefile index e1ef0ce..15b4775 100644 --- a/src/Makefile +++ b/src/Makefile @@ -18,7 +18,7 @@ # # ------------------------------------------------------------------------- # -# $Id: Makefile,v 1.6 2003-12-12 01:15:38 ianc Exp $ +# $Id: Makefile,v 1.7 2004-01-26 02:01:49 ianc Exp $ # # CFLAGS assumes that gcc is being used - simply comment out if required @@ -54,3 +54,52 @@ depend: if test -e Makefile ; then rm -f Makefile.bak ; fi # DO NOT DELETE THIS LINE -- make depend depends on it + +kbs.o: /usr/include/stdlib.h /usr/include/sys/cdefs.h +kbs.o: /usr/include/sys/_types.h /usr/include/machine/_types.h +kbs.o: /usr/include/limits.h /usr/include/sys/limits.h +kbs.o: /usr/include/machine/_limits.h /usr/include/sys/syslimits.h +kbs.o: /usr/include/stdio.h /usr/include/string.h /usr/include/strings.h +kbs.o: /usr/include/stdarg.h /usr/include/time.h /usr/include/sys/timespec.h +kbs.o: /usr/include/errno.h global.h config.h pop3.h msg.h dbase.h rexp.h +kbs.o: dstring.h util.h +pop3.o: /usr/include/stdlib.h /usr/include/sys/cdefs.h +pop3.o: /usr/include/sys/_types.h /usr/include/machine/_types.h +pop3.o: /usr/include/string.h /usr/include/strings.h /usr/include/stdio.h +pop3.o: /usr/include/stdarg.h /usr/include/ctype.h /usr/include/runetype.h +pop3.o: /usr/include/unistd.h /usr/include/sys/types.h +pop3.o: /usr/include/machine/endian.h /usr/include/sys/select.h +pop3.o: /usr/include/sys/_sigset.h /usr/include/sys/_timeval.h +pop3.o: /usr/include/sys/timespec.h /usr/include/sys/unistd.h +pop3.o: /usr/include/sys/socket.h /usr/include/sys/_iovec.h +pop3.o: /usr/include/machine/param.h /usr/include/netinet/in.h +pop3.o: /usr/include/netinet6/in6.h /usr/include/netinet/tcp.h +pop3.o: /usr/include/netdb.h global.h pop3.h msg.h dstring.h util.h +config.o: /usr/include/stdlib.h /usr/include/sys/cdefs.h +config.o: /usr/include/sys/_types.h /usr/include/machine/_types.h +config.o: /usr/include/string.h /usr/include/strings.h /usr/include/stdio.h +config.o: /usr/include/errno.h /usr/include/ctype.h /usr/include/runetype.h +config.o: global.h config.h dstring.h dbase.h msg.h rexp.h util.h +rexp.o: /usr/include/stdlib.h /usr/include/sys/cdefs.h +rexp.o: /usr/include/sys/_types.h /usr/include/machine/_types.h +rexp.o: /usr/include/regex.h global.h rexp.h config.h util.h +dbase.o: /usr/include/stdlib.h /usr/include/sys/cdefs.h +dbase.o: /usr/include/sys/_types.h /usr/include/machine/_types.h +dbase.o: /usr/include/string.h /usr/include/strings.h /usr/include/stdio.h +dbase.o: /usr/include/ctype.h /usr/include/runetype.h global.h dbase.h msg.h +dbase.o: rexp.h dstring.h config.h util.h +dstring.o: /usr/include/stdlib.h /usr/include/sys/cdefs.h +dstring.o: /usr/include/sys/_types.h /usr/include/machine/_types.h +dstring.o: /usr/include/string.h /usr/include/strings.h /usr/include/stdio.h +dstring.o: /usr/include/stdarg.h /usr/include/ctype.h /usr/include/runetype.h +dstring.o: /usr/include/sys/types.h /usr/include/machine/endian.h +dstring.o: /usr/include/sys/select.h /usr/include/sys/_sigset.h +dstring.o: /usr/include/sys/_timeval.h /usr/include/sys/timespec.h +dstring.o: /usr/include/sys/socket.h /usr/include/sys/_iovec.h +dstring.o: /usr/include/machine/param.h /usr/include/netinet/in.h +dstring.o: /usr/include/netinet6/in6.h /usr/include/netinet/tcp.h +dstring.o: /usr/include/netdb.h global.h dstring.h util.h +util.o: /usr/include/stdlib.h /usr/include/sys/cdefs.h +util.o: /usr/include/sys/_types.h /usr/include/machine/_types.h +util.o: /usr/include/string.h /usr/include/strings.h /usr/include/stdio.h +util.o: global.h util.h diff --git a/src/config.c b/src/config.c index 1c10591..66ea944 100644 --- a/src/config.c +++ b/src/config.c @@ -57,6 +57,8 @@ typedef enum { TOK_BlockSubject, TOK_SubjectMacro, TOK_Blacklist, + TOK_Include, + TOK_AllowTo, TOK_VarHostname, TOK_VarPort, TOK_VarUsername, @@ -128,6 +130,8 @@ static const Command cmd_table[]= {"block_subject", TOK_BlockSubject}, {"subject_macro", TOK_SubjectMacro}, {"blacklist", TOK_Blacklist}, + {"include", TOK_Include}, + {"allow_to", TOK_AllowTo}, /* Variables */ @@ -160,6 +164,7 @@ static const Command cmd_table[]= /* ---------------------------------------- REQUIRED PROTOS */ static Token GetToken(FILE *fp, DString *ret); +static int Parse(FILE *fp); /* ---------------------------------------- COMMAND HANDLER UTILS @@ -417,6 +422,22 @@ static int DoDomain(FILE *fp) DBBlockSubject(domain,re); break; + case TOK_AllowTo: + GetToken(fp,&ds); + + ds=ExpandRE(ds); + + if (!(re=RECompile(ds->text))) + { + DSAddCP(error,"Bad regular expression: "); + DSAddDS(error,ds); + DSFree(ds); + return FALSE; + } + + DBAllowTo(domain,re); + break; + default: DSAddCP(error,"Unexpected string in domain command: "); DSAddDS(error,ds); @@ -576,6 +597,42 @@ static int DoBlacklist(FILE *fp) } +static int DoInclude(FILE *fp) +{ + FILE *inc; + DString ds; + Token tok; + int ret; + + ds=DSInit(); + + tok=GetToken(fp,&ds); + + if (tok==TOK_EOF) + { + DSAddCP(error,"Unexpected EOF in include command"); + DSFree(ds); + return FALSE; + } + + if (!(inc=fopen(ds->text,"r"))) + { + DSAddCP(error,"Couldn't open include file "); + DSAddDS(error,ds); + DSFree(ds); + return FALSE; + } + + DSFree(ds); + + ret=Parse(inc); + + fclose(inc); + + return ret; +} + + /* ---------------------------------------- PRVIVATE FUNCTIONS */ static int Getc(FILE *fp) @@ -752,6 +809,11 @@ static int Parse(FILE *fp) ok=FALSE; break; + case TOK_Include: + if (!DoInclude(fp)) + ok=FALSE; + break; + case TOK_Expression: DSAddCP(error,"Unknown command in config file: "); DSAddDS(error,txt); @@ -780,7 +842,7 @@ static int Parse(FILE *fp) /* ---------------------------------------- INTERFACES */ -int ConfigLoad(void) +int ConfigLoad(const char *path) { DString ds; FILE *fp; @@ -793,14 +855,19 @@ int ConfigLoad(void) password=CopyStr(""); log=CopyStr(""); - if (!getenv("HOME")) - return FALSE; - ds=DSInit(); - DSAddCP(ds,getenv("HOME")); - DSAddChar(ds,'/'); - DSAddCP(ds,".kbsrc"); + if (path) + DSAddCP(ds,path); + else + { + if (!getenv("HOME")) + return FALSE; + + DSAddCP(ds,getenv("HOME")); + DSAddChar(ds,'/'); + DSAddCP(ds,".kbsrc"); + } if ((fp=fopen(ds->text,"r"))) ret=Parse(fp); diff --git a/src/config.h b/src/config.h index 7f48a70..d545531 100644 --- a/src/config.h +++ b/src/config.h @@ -61,9 +61,10 @@ typedef enum /* ---------------------------------------- INTERFACES */ -/* Loads in config file. Returns FALSE for problems. +/* Loads in config file (the default name if path is NULL). Returns FALSE + for problems. */ -int ConfigLoad(void); +int ConfigLoad(const char *path); /* Returns a reason for config load failure diff --git a/src/dbase.c b/src/dbase.c index c3e83b7..e1caa04 100644 --- a/src/dbase.c +++ b/src/dbase.c @@ -49,9 +49,11 @@ struct Domain int no_user; int no_block; int no_allow; + int no_allow_to; char **user; RE_Expression *block; RE_Expression *allow; + RE_Expression *allow_to; Domain *next; Domain *prev; }; @@ -165,6 +167,7 @@ Domain *DBNewDomain(RE_Expression name) dom->user=NULL; dom->block=NULL; dom->allow=NULL; + dom->allow_to=NULL; if (tail) tail->next=dom; @@ -208,6 +211,15 @@ void DBBlockSubject(Domain *domain, RE_Expression re) } +void DBAllowTo(Domain *domain, RE_Expression re) +{ + domain->no_allow_to++; + domain->allow_to=Realloc(domain->allow_to, + sizeof(RE_Expression)*domain->no_allow_to); + domain->allow_to[domain->no_allow_to-1]=re; +} + + void DBTrustedUser(const char *username) { no_trusted_users++; @@ -254,7 +266,7 @@ int DBBlockMessage(const POP3Message *msg) (strncmp(msg->content_type,html,strlen(html))==0 || strcmp(msg->content_type,"UNKNOWN")==0)) { - DSAddCP(reason,"HTML message"); + DSAddCP(reason,"HTML message or unknown content type"); return TRUE; } @@ -293,6 +305,30 @@ int DBBlockMessage(const POP3Message *msg) } } + if (dom->no_allow_to) + { + int found=FALSE; + int f; + + for(f=0;fno_allow_to && !found;f++) + if (RESearch(dom->allow_to[f],msg->to)) + found=TRUE; + + if (!found) + { + if (show) + { + DSAddCP(reason,"disallowed to address - "); + DSAddCP(reason,msg->to); + } + else + DSAddCP(reason,"disallowed to address"); + + DSFree(ds); + return TRUE; + } + } + for(f=0;fno_user;f++) { int res; @@ -392,6 +428,11 @@ void DBClose(void) free(t->allow); + for(f=0;fno_allow;f++) + REFree(t->allow_to[f]); + + free(t->allow_to); + free(t); } diff --git a/src/dbase.h b/src/dbase.h index 3c771af..1b3dfa6 100644 --- a/src/dbase.h +++ b/src/dbase.h @@ -70,6 +70,12 @@ void DBAllowSubject(Domain *domain, RE_Expression re); void DBBlockSubject(Domain *domain, RE_Expression re); +/* Sets the allowed 'To:' name for a domain. + re will be freed by the database when closed. +*/ +void DBAllowTo(Domain *domain, RE_Expression re); + + /* Adds a trusted username */ void DBTrustedUser(const char *username); diff --git a/src/kbs.c b/src/kbs.c index c951a63..8cdf8be 100644 --- a/src/kbs.c +++ b/src/kbs.c @@ -60,7 +60,7 @@ int main(int argc, char *argv[]) else name=argv[0]; - if (!ConfigLoad()) + if (!ConfigLoad(argv[1])) { fprintf(stderr,"%s: %s\n",name,ConfigError()); exit(EXIT_FAILURE); diff --git a/src/kbsrc b/src/kbsrc index 5361dd3..27031df 100644 --- a/src/kbsrc +++ b/src/kbsrc @@ -69,6 +69,12 @@ domain "." # block_user emailname + + # Only allow emails legitimately directed to us: + # + allow_to emailname@myisp.co.uk + + # Obviously, things like "credit card" will not be blocked from the bank # as that's in the trusted_domains. # @@ -94,9 +100,10 @@ domain "." block_subject ".* ?naked ?.*" block_subject ".* ?nigeria ?.*" + # Why would anyone sane want to use my username in a subject? # I know who I am! Note it's capitilised so nothing bizarre gets # done by the subject macros. # - block_subject "EMAILNAME@MY-ISP.CO.UK" + block_subject "EMAILNAME@MYISP.CO.UK" } diff --git a/src/pop3.c b/src/pop3.c index c6b6fe1..bfd9e6c 100644 --- a/src/pop3.c +++ b/src/pop3.c @@ -57,6 +57,7 @@ static const char message_id[]=KBS_MSG_H; #define HDR_FROM 1 #define HDR_TO 2 #define HDR_CONTENT_TYPE 3 +#define HDR_DEBUG 4 /* ---------------------------------------- TYPES */ @@ -71,6 +72,7 @@ static int connected=FALSE; /* ---------------------------------------- PRIVATE FUNCTIONS */ +/* static void Chomp(char *p) { size_t l; @@ -87,6 +89,7 @@ static void Chomp(char *p) p[--l]=0; } } +*/ static char *ChompQuotesWS(char *p) @@ -182,13 +185,14 @@ static int Send(const char *cmd, ...) } +static char gc_buff[BLOCKSIZE]; +static ssize_t gc_len=0; +static int gc_ptr=0; + + static int GetChar(void) { - static char buff[BLOCKSIZE]; - static ssize_t len=0; - static int ptr=0; - - if (len==0 || ptr==len) + if (gc_len==0 || gc_ptr==gc_len) { struct timeval tv; fd_set set; @@ -205,13 +209,20 @@ static int GetChar(void) if (!FD_ISSET(sock,&set)) return -1; - if ((len=read(sock,buff,sizeof buff))<1) + if ((gc_len=read(sock,gc_buff,sizeof gc_buff))<1) return -1; - ptr=0; + gc_ptr=0; } - return buff[ptr++]; + return gc_buff[gc_ptr++]; +} + + +static void UnGetChar(int c) +{ + if (gc_ptr) + gc_buff[--gc_ptr]=c; } @@ -225,7 +236,8 @@ static int GetLine(DString t) while(!done && (ch=GetChar())!=-1) { - DSAddChar(t,ch); + if (ch!='\015' && ch!='\012') + DSAddChar(t,ch); if (last=='\015' && ch=='\012') done=TRUE; @@ -236,8 +248,6 @@ static int GetLine(DString t) if (ch==-1) return FALSE; - Chomp(t->text); - return TRUE; } @@ -263,14 +273,59 @@ static int GetResponse(DString ret) } +static int IsHeader(const DString line) +{ + const char *p; + + p=line->text; + + while(*p && !isspace(*p)) + { + if (*p==':') + return TRUE; + + p++; + } + + return FALSE; +} + + static int GetMultiLine(DString ret) { - int flag; + int done=FALSE; + int flag=TRUE; - if (GetLine(ret)) - flag=TRUE; - else - flag=FALSE; + while (!done) + { + if (GetLine(ret)) + { + if (IsHeader(ret)) + { + int ch; + + if ((ch=GetChar())!=-1) + { + if (ch!=' ' && ch!='\t') + done=TRUE; + + UnGetChar(ch); + } + else + { + flag=FALSE; + done=TRUE; + } + } + else + done=TRUE; + } + else + { + flag=FALSE; + done=TRUE; + } + } return flag; } @@ -289,15 +344,18 @@ static void ParseHeader(POP3Message *msg, char *line) {"To: ", HDR_TO}, {"Content-type: ", HDR_CONTENT_TYPE}, {"Content-Type: ", HDR_CONTENT_TYPE}, + /* {"Received: ", HDR_DEBUG}, */ {NULL, HDR_UNKNOWN} }; + const char *name; int f; int type=HDR_UNKNOWN; for(f=0;type==HDR_UNKNOWN && field[f].text;f++) if (strncmp(field[f].text,line,strlen(field[f].text))==0) { + name=field[f].text; type=field[f].type; line+=strlen(field[f].text); } @@ -341,6 +399,10 @@ static void ParseHeader(POP3Message *msg, char *line) msg->content_type=CopyStr(line); break; + case HDR_DEBUG: + printf("%s = '%s'\n",name,line); + break; + default: break; } -- cgit v1.2.3