NNRP BOFH Perl filter patch

This is version 2 of the Perl filter enhancement patch.

This patch is against a stock INN 1.7 nnrpd.  It applies cleanly to
1.7+insync and should apply to 1.5.1 as well.  Apply it in the nnrpd
directory of the source tree.  Affected files are perl.c and post.c.
You need only recompile nnrpd (do a 'make' in the nnrp directory) and
copy the new executable to /usr/news/bin or wherever is appropriate for
your server.

This patch does several things:

1. Gives the Perl filter (filter_nnrpd.pl) access to message bodies,
in the $body variable.

2. Gives the Perl filter access to the poster's authinfo username,
in the $user variable.  Obviously this has no effect unless you use
authinfo.

3. Adds a returncode DROP to the Perl interface.  If the string returned
by the Perl subroutine begins with DROP, the post will be discarded
and success returned to the client.

4. Adds a returncode SPOOL to the Perl interface.  If the string
returned by the Perl subroutine begins with SPOOL, success will be
returned to the client and the post will be saved to in.coming/spam
for manual inspection.

Thus, doing something like this:

return "DROP spam" if ($body =~ /http:..dirty\.spammer\.com/);

will drop any post containing that URL while returning success to the
posting client, making the spammer think he is being successful and
keeping him from changing ISPs and starting over somewhere else.

return "SPOOL possible MMF" if ($hdr{"Subject"} =~ /Make Money Fast/);

will spool "suspect" posts in in.coming/spam so you can look at them
and decide whether they should go out.  They can be injected manually
with rnews.  Spooled posts to moderated groups will be placed in
in.coming/spam/mod; you *cannot* feed these to rnews, they must be
mailed to the moderation address.  This is somewhat inconvenient.

return "posting access denied" if ($user eq "badguy");

will return failure for anything posted by "badguy", removing his
Usenet posting ability while still allowing him to read.  (This example
requires authinfo).

return "SPOOL suspect post from $user" if ($user = "maybebadguy");

will spool posts from "maybebadguy" for manual inspection, while
returning success to the posting client.  They can be injected manually
with rnews after inspection (unless they are destined for moderated
newsgroups).  (This example requires authinfo.)


This code was written by Andrew Gierth <andrew@erlenstar.demon.co.uk>
and can be distributed freely, with attribution.

Changes since first version: posts to moderated groups no longer bypass
the Perl filter checks.

---8<---------------------------------------------------------
*** post.c.orig	Fri Oct 10 11:27:23 1997
--- post.c	Wed Nov 19 02:43:07 1997
***************
*** 609,617 ****
  **  moderated, etc.
  */
  STATIC STRING
! ValidNewsgroups(hdr, article)
      char		*hdr;
!     char		*article;
  {
      static char		distbuff[SMBUF];
      register char	*groups;
--- 611,619 ----
  **  moderated, etc.
  */
  STATIC STRING
! ValidNewsgroups(hdr, modgroup)
      char		*hdr;
!     char		**modgroup;
  {
      static char		distbuff[SMBUF];
      register char	*groups;
***************
*** 630,636 ****
  	return "Can't parse newsgroups line";
  
      /* Don't mail article if just checking Followup-To line. */
!     approved = HDR(_approved) != NULL || article == NULL;
  
      Error[0] = '\0';
      FoundOne = FALSE;
--- 632,638 ----
  	return "Can't parse newsgroups line";
  
      /* Don't mail article if just checking Followup-To line. */
!     approved = HDR(_approved) != NULL || modgroup == NULL;
  
      Error[0] = '\0';
      FoundOne = FALSE;
***************
*** 659,669 ****
  	case NF_FLAG_OK:
  	    break;
  	case NF_FLAG_MODERATED:
! 	    if (!approved) {
! 		DISPOSE(groups);
!                 tmpPtr = DDend(h);
! 		DISPOSE(tmpPtr);
! 		return MailArticle(gp->Name, article);
  	    }
  	    break;
  	case NF_FLAG_IGNORE:
--- 661,668 ----
  	case NF_FLAG_OK:
  	    break;
  	case NF_FLAG_MODERATED:
! 	    if (!approved && !*modgroup) {
! 		*modgroup = gp->Name;
  	    }
  	    break;
  	case NF_FLAG_IGNORE:
***************
*** 745,753 ****
  **  Spool article to temp file.
  */
  STATIC STRING
! Spoolit(article, Error)
      char 		*article;
      char		*Error;
  {
      static char		CANTSPOOL[NNTP_STRLEN+2];
      register HEADER	*hp;
--- 744,753 ----
  **  Spool article to temp file.
  */
  STATIC STRING
! SpoolitTo(article, Error, SpoolDir)
      char 		*article;
      char		*Error;
+     char                *SpoolDir;
  {
      static char		CANTSPOOL[NNTP_STRLEN+2];
      register HEADER	*hp;
***************
*** 761,767 ****
      sprintf(CANTSPOOL, "%s and can't write text to local spool file", Error);
  
      /* Try to write it to the spool dir. */
!     TempName(_PATH_SPOOLNEWS, temp);
      /* rnews -U ignores files starting with . */
      strrchr(temp, '/')[1] = '.';
      if ((F = fopen(temp, "w")) == NULL) {
--- 761,767 ----
      sprintf(CANTSPOOL, "%s and can't write text to local spool file", Error);
  
      /* Try to write it to the spool dir. */
!     TempName(SpoolDir, temp);
      /* rnews -U ignores files starting with . */
      strrchr(temp, '/')[1] = '.';
      if ((F = fopen(temp, "w")) == NULL) {
***************
*** 799,805 ****
      if (fclose(F))
  	return CANTSPOOL;
  
!     TempName(_PATH_SPOOLNEWS, path);
      if (rename(temp, path) < 0) {
          syslog(L_FATAL, "cant rename %s %s %m", temp, path);
  	return CANTSPOOL;
--- 799,805 ----
      if (fclose(F))
  	return CANTSPOOL;
  
!     TempName(SpoolDir, path);
      if (rename(temp, path) < 0) {
          syslog(L_FATAL, "cant rename %s %s %m", temp, path);
  	return CANTSPOOL;
***************
*** 809,814 ****
--- 809,826 ----
      return NULL;
  }
  
+ /*
+ **  Spool article to temp file.
+ */
+ STATIC STRING
+ Spoolit(article, Error)
+     char 		*article;
+     char		*Error;
+ {
+     return SpoolitTo(article, Error, _PATH_SPOOLNEWS);
+ }
+  
+ 
  STRING
  ARTpost(article, idbuff)
      char		*article;
***************
*** 823,828 ****
--- 835,841 ----
      FILE		*FromServer;
      char		buff[NNTP_STRLEN + 2], frombuf[SMBUF];
      STRING		error;
+     char                *modgroup = NULL;
  
      /* Set up the other headers list. */
      if (OtherHeaders == NULL) {
***************
*** 851,863 ****
      if (i == 0 && HDR(_control) == NULL)
  	return "Article is empty";
  
!     if (idbuff != NULL)
!       strcpy (idbuff,"(mailed to moderator)") ;
!     if ((error = ValidNewsgroups(HDR(_newsgroups), article)) != NULL
!      || WasMailed)
  	return error;
-     if (idbuff != NULL)
-       idbuff [0] = '\0' ;
      
      strncpy(frombuf, HDR(_from), sizeof(frombuf) - 1);
      frombuf[sizeof(frombuf) - 1] = '\0';
--- 864,871 ----
      if (i == 0 && HDR(_control) == NULL)
  	return "Article is empty";
  
!     if ((error = ValidNewsgroups(HDR(_newsgroups), &modgroup)) != NULL)
  	return error;
      
      strncpy(frombuf, HDR(_from), sizeof(frombuf) - 1);
      frombuf[sizeof(frombuf) - 1] = '\0';
***************
*** 873,879 ****
  	return "From: address not in Internet syntax";
      if ((p = HDR(_followupto)) != NULL
       && !EQ(p, "poster")
!      && (error = ValidNewsgroups(p, (char *)NULL)) != NULL)
  	return error;
  #if	LOCAL_MAX_ARTSIZE > 0
      if (strlen(article) > LOCAL_MAX_ARTSIZE) {
--- 881,887 ----
  	return "From: address not in Internet syntax";
      if ((p = HDR(_followupto)) != NULL
       && !EQ(p, "poster")
!      && (error = ValidNewsgroups(p, (char **)NULL)) != NULL)
  	return error;
  #if	LOCAL_MAX_ARTSIZE > 0
      if (strlen(article) > LOCAL_MAX_ARTSIZE) {
***************
*** 886,894 ****
  
  #if defined(DO_PERL)
      /* Calls the Perl subroutine for headers management */
!     if ((p = (char *)HandleHeaders()) != NULL)
!         return p;
  #endif /* defined(DO_PERL) */
  
      /* Open a local connection to the server. */
      if (RemoteMaster)
--- 894,922 ----
  
  #if defined(DO_PERL)
      /* Calls the Perl subroutine for headers management */
!     if ((p = (char *)HandleHeaders(article)) != NULL) {
! 	if (strncmp(p, "DROP", 4) == 0) {
! 	    syslog(L_NOTICE, "%s post %s", ClientHost, p);
! 	    return NULL;
! 	}
! 	else if (strncmp(p, "SPOOL", 5) == 0) {
! 	    syslog(L_NOTICE, "%s post %s", ClientHost, p);
! 	    return SpoolitTo(article, p, modgroup ? _PATH_SPOOLNEWS "/spam/mod"
! 			                          : _PATH_SPOOLNEWS "/spam");
! 	}
! 	else
! 	    return p;
!     }
  #endif /* defined(DO_PERL) */
+ 
+     /* handle mailing to moderated groups */
+ 
+     if (modgroup)
+     {
+ 	if (idbuff != NULL)
+ 	    strcpy (idbuff,"(mailed to moderator)") ;
+ 	return MailArticle(modgroup, article);
+     }
  
      /* Open a local connection to the server. */
      if (RemoteMaster)
*** perl.c.orig	Tue Oct  7 16:18:14 1997
--- perl.c	Mon Nov 24 10:43:58 1997
***************
*** 32,44 ****
  extern BOOL PerlFilterActive;
  extern HEADER	Table[], *EndOfTable;
  extern char LogName[];
  
  char *
! HandleHeaders()
  {
     dSP;
     HEADER	*hp;
     HV		*hdr;
     int		rc;
     char		*p;
     static char	buf[256];
--- 32,46 ----
  extern BOOL PerlFilterActive;
  extern HEADER	Table[], *EndOfTable;
  extern char LogName[];
+ extern char PERMuser[];
  
  char *
! HandleHeaders(char *article)
  {
     dSP;
     HEADER	*hp;
     HV		*hdr;
+    SV           *body;
     int		rc;
     char		*p;
     static char	buf[256];
***************
*** 56,66 ****
--- 58,74 ----
           hv_store(hdr, (char *) hp->Name, strlen(hp->Name), newSVpv(hp->Value, 0), 0);
     }
  
+    sv_setpv(perl_get_sv("user",TRUE), PERMuser);
+ 
+    body = perl_get_sv("body", TRUE);
+    sv_setpv(body, article);
+ 
     rc = perl_call_argv("filter_post", G_EVAL|G_SCALAR, NULL);
  
     SPAGAIN;
  
     hv_undef (hdr);
+    sv_setsv (body, &sv_undef);
  
     buf [0] = '\0' ;
     
