/* * nntp.c Copyright 1999 Christopher M Sedore. All Rights Reserved. * Please see the "COPYING" file for license details. * * This file contains functions which implement NNTP. The implementation * is a simple one, using and array of protocol commands, requires * permissions, and a callback function. * * These routines are driven via async io through upcalls to NntpMain(). */ #include "main.h" int ArtRead(struct context *cc); void NntpIhave(struct context *cc); void NntpQuit(struct context *cc); void NntpCheck(struct context *cc); void NntpTakethis(struct context *cc); void NntpModestream(struct context *cc); void NntpMaint(struct context *cc); extern void NntpFeedIn(struct context *cc); void NntpFeedOut(struct context *cc); void NntpUnknown(struct context *cc); #define NUM_NNTP_COMMANDS 8 struct _nntpcmds { char cmd[20]; int cmdlen; int perm; void (*cmdfunc)(struct context *cc); } nntpCommands[NUM_NNTP_COMMANDS+1]= {"check",5,PERM_FEED,NntpCheck, "ihave",5,PERM_FEED,NntpIhave, "takethis",8,PERM_FEED,NntpTakethis, "mode stream",11,PERM_FEED,NntpModestream, "quit",4,-1,NntpQuit, "maint",5,PERM_MAINT,NntpMaint, "statsin",7,PERM_MAINT,NntpFeedIn, "statsout",8,PERM_MAINT,NntpFeedOut, "unknown",7,-1,NntpUnknown}; TAILQ_HEAD(consumerlist,artconsumer) consumerhead; extern int externalFilterActive; u_quad_t HashArticleID(char *mid,int length) { char *a; MD5_CTX ctx; u_quad_t md5buf[2]; if (length==0) { a=mid; while ((a-mid<255) && (*a!='>')) a++; if (a-mid==255) return 0; a++; length=(a-mid)-1; } MD5Init(&ctx); MD5Update(&ctx,mid,length); MD5Final((char *)&md5buf,&ctx); return md5buf[0]; } /* * GetArticleID() * * Try to find a message-id searching forward through cc->bp for * "<" some chars ">". We're not very picky, but modificaitons * to this function could make us so. */ int GetArticleID(struct context *cc) { char *a,*b; a=cc->bp; while ((*a!='<') && (*a) ) {a++;} if (!*a) { return -1; } b=a; while ((*b!='>') && (*b) && (b-amid[b-a]=*b; b++; } if ((b-a<3) || (b-a==MAX_ARTICLEID) || (!*b)) return -1; cc->mid[b-a]=*b; b++; cc->mid[b-a]=0; cc->mid64=HashArticleID(cc->mid,(b-a)-1); return 0; } /* * RegisterArticleConsumer() is used to hook into the article arrival * process. When an article is received, it is passed through * the consumers and they can specify whether they have "consumed" * the article, that we shoud reject it, or that they don't care * about it (though a consumer who ignores may modify the article * in place). * * Setting pri to 0 indicates that you want to be inserted at the * head of the consumer chain, 1 indicates putting you on the end. * Order matters, since once an article is consumed, no other * consumers will see it. */ void RegisterArticleConsumer(int (*callback)(struct article *art),int pri) { struct artconsumer *ac; assert(callback!=NULL); ac=malloc(sizeof(struct artconsumer)); bzero(ac,sizeof(struct artconsumer)); ac->callback=callback; if (!pri) { TAILQ_INSERT_HEAD(&consumerhead,ac,next); } else { TAILQ_INSERT_TAIL(&consumerhead,ac,next); } } /* * PrepareToReadArticle() is called to prepare to read an article on * the specified connection. This function allocates memory, sets * flags, and prepares the article structure. */ void PrepareToReadArticle(struct context *cc) { char *s=NULL; cc->art=malloc(sizeof(struct article)); bzero(cc->art,sizeof(struct article)); cc->art->len=0; cc->art->bufsz=BASE_ARTSIZE; cc->art->bufp=malloc(BASE_ARTSIZE); cc->art->buf=cc->art->bufp+255; cc->flags|=NNTP_READ_ARTICLE; strcpy(cc->art->mid,cc->mid); cc->callback=ArtRead; cc->state="readart"; cc->flags&=~NNTP_BAD_ARTICLE; assert(*cc->art->mid=='<'); } /* * CleanupAfterReadArticle() is called to free memory allocated * by PrepareToReadArticle() */ void CleanupAfterReadArticle(struct article *art) { FreeMatchData(art); free(art->bufp); free(art); } /* * FindConsumer() passes the article to consumers until it a) is consumed * b) is rejected, or c) runs out of consumers. * * FindConsumer() is also responsible for making sure we haven't * already received the article, and that if there is an incoming * distribution, that the article would pass through it. */ void FindConsumer(struct context *cc) { struct artconsumer *ac; int df,res,none=1; if (!AddSeen(cc->mid64,0)) { // printf("got already\n"); cc->flags|=NNTP_BAD_ARTICLE; CleanupAfterReadArticle(cc->art); return; } if (cc->incoming->dist) { if (!CheckMatch(cc->art,cc->incoming->dist)) { printf("dist mismatch\n"); cc->flags|=NNTP_BAD_ARTICLE; CleanupAfterReadArticle(cc->art); return; } } for (ac=TAILQ_FIRST(&consumerhead);ac;ac=TAILQ_NEXT(ac,next)) { df=1; if (ac->dist) { df=CheckMatch(cc->art,ac->dist); } if (df) { assert(ac->callback!=NULL); res=ac->callback(cc->art); if (res==CONSUMER_OK) { none=0; break; } if (res==CONSUMER_REJECT) { cc->flags|=NNTP_BAD_ARTICLE; break; } if (res==CONSUMER_IGNORE) continue; } } if (none) CleanupAfterReadArticle(cc->art); } /* * ArtRead() is the callback function setup when we transition to reading * an article on an incoming connection. ArtRead() will check to make * sure the article read was not flagged bad, and then call * FindConsumer(). It also generates the NNTP response. */ int ArtRead(struct context *cc) { cc->flags&=~NNTP_READ_ARTICLE; cc->state="artproc"; if (!(cc->flags & NNTP_BAD_ARTICLE)){ if (PrepareForMatch(cc->art)) { cc->state="procart"; if (externalFilterActive) { ArtExternalFilter(cc); return -1; } else FindConsumer(cc); } else { cc->flags|=NNTP_BAD_ARTICLE; // printf("prepare failed\n"); CleanupAfterReadArticle(cc->art); cc->art=NULL; } } else { cc->flags|=NNTP_BAD_ARTICLE; printf("bad art\n"); CleanupAfterReadArticle(cc->art); cc->art=NULL; } return ArtReadRespond(cc); } int ArtReadRespond(struct context *cc) { if (cc->flags & NNTP_BAD_ARTICLE) { cc->incoming->reject++; if (cc->flags & NNTP_STREAMING) { sprintf(&cc->obuf[cc->obuflen], "439 %s\r\n",cc->mid); cc->obuflen+=strlen(&cc->obuf[cc->obuflen]); } else { memcpy(&cc->obuf[cc->obuflen],"437\r\n",5); cc->obuflen+=5; } } else { cc->incoming->accept++; if (cc->flags & NNTP_STREAMING) { sprintf(&cc->obuf[cc->obuflen], "239 %s\r\n",cc->mid); cc->obuflen+=strlen(&cc->obuf[cc->obuflen]); } else { memcpy(&cc->obuf[cc->obuflen],"235\r\n",5); cc->obuflen+=5; } } cc->state="nextcmd"; cc->callback=NntpMain; return 0; } /* * NntpUnknown() is the "default" command function--a fallthrough * that generates an error. */ void NntpUnknown(struct context *cc) { memcpy(&cc->obuf[cc->obuflen],"500 what?\r\n",11); cc->obuflen+=11; printf("unk: [%s] %u\n",cc->bp,cc->buflen); } /* * Nntp*() functions that implement NNTP commands. */ void NntpQuit(struct context *cc) { memcpy(&cc->obuf[cc->obuflen],"205 Bye\r\n",9); cc->obuflen+=9; cc->flags|=NNTP_CLOSE_CONNECTION_AFTER_OP; } void NntpIhave(struct context *cc) { if (GetArticleID(cc)) { memcpy(&cc->obuf[cc->obuflen],"437 bad msg id\r\n",16); cc->obuflen+=16; return; } else { if (!CheckSeen(cc->mid64)) { if (AddPrecommit(cc->mid64)) { memcpy(&cc->obuf[cc->obuflen],"335\r\n",5); cc->obuflen+=5; cc->flags&=~NNTP_STREAMING; PrepareToReadArticle(cc); return; } } memcpy(&cc->obuf[cc->obuflen],"435\r\n",5); cc->incoming->refuse++; cc->obuflen+=5; } } void NntpCheck(struct context *cc) { if (GetArticleID(cc)) { memcpy(&cc->obuf[cc->obuflen],"439 bad msg id\r\n",16); cc->obuflen+=16; cc->incoming->reject++; return; } else { if (!CheckSeen(cc->mid64)) { if (AddPrecommit(cc->mid64)) { sprintf(&cc->obuf[cc->obuflen],"238 %s\r\n",cc->mid); cc->obuflen+=strlen(&cc->obuf[cc->obuflen]); return; } } cc->incoming->refuse++; sprintf(&cc->obuf[cc->obuflen],"438 %s\r\n",cc->mid); cc->obuflen+=strlen(&cc->obuf[cc->obuflen]); } } void NntpTakethis(struct context *cc) { if (GetArticleID(cc)) { sprintf(&cc->obuf[cc->obuflen],"439 %s\r\n",cc->mid); cc->obuflen+=strlen(&cc->obuf[cc->obuflen]); return; } else { AddPrecommit(cc->mid64); cc->flags|=NNTP_STREAMING; PrepareToReadArticle(cc); } } void NntpModestream(struct context *cc) { memcpy(&cc->obuf[cc->obuflen],"203 StreamOK\r\n",14); cc->obuflen+=14; } /* * NntpMaint() is a half-baked, half-completed online config method. * It should be yanked. */ void NntpMaint(struct context *cc) { char *u,*p; NntpUnknown(cc); return; u=strtok(cc->bp,"\t "); p=strtok(NULL,"\t "); cc->flags|=NNTP_MAINTENANCE; sprintf(&cc->obuf[cc->obuflen],"200 maint mode enabled\r\n"); cc->obuflen+=strlen(&cc->obuf[cc->obuflen]); } /* * NntpMain() is the main upcall routine for incoming data from the * connection. It searches the array of NNTP commands and executes * a callback to the appropriate function. */ int NntpMain(struct context *cc) { char *s; int x,lr; cc->state="nntp"; x=0; while (xbp,nntpCommands[x].cmd, nntpCommands[x].cmdlen)) && (cc->flags & nntpCommands[x].perm)) { nntpCommands[x].cmdfunc(cc); break; } x++; } if (x==NUM_NNTP_COMMANDS) { nntpCommands[x].cmdfunc(cc); } return 0; } int NntpInit() { TAILQ_INIT(&consumerhead); }