#!/usr/bin/perl # # $Id: access_nntp,v 1.7 1994/08/03 07:17:26 jeff Exp jeff $ # # written by Jeff Gilbreth, June 20 1994. # # socket code taken from `urlsnarf', written by Allen Condit. # # # This PERL filter accepts specialized HTTP requests from Chimera # on stdin and converts them into NNTP requests to the local server. # The program then formats the results into an HTTP response sent to # stdout, and exits. # # # # # # Copyright 1994 by Jeff Gilbreth. # # Permission to use, copy, modify, and distribute this software and its # documentation for any purpose and without fee is hereby granted, # provided that the above copyright notice appear in all copies and that # both that copyright notice and this permission notice appear in # supporting documentation, and that the name of Jeff Gilbreth not be # used in advertising or publicity pertaining to distribution of the # software without specific, written prior permission. Jeff Gilbreth # makes no representations about the suitability of this software for # any purpose. It is provided "as is" without express or implied # warranty. # # JEFF GILBRETH DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO # EVENT SHALL JEFF GILBRETH BE LIABLE FOR ANY SPECIAL, INDIRECT OR # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF # USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR # OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. # # # # $Log: access_nntp,v $ # Revision 1.7 1994/08/03 07:17:26 jeff # added the log # # $Id: news.pl 1.0 1995/01/03 19:05:15 michael Exp michael $ # # Rewritten in Parts by Michael Kellen, February 1995 # # MICHAEL KELLEN DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO # EVENT SHALL MICHAEL KELLEN BE LIABLE FOR ANY SPECIAL, INDIRECT OR # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF # USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR # OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. # # Major OverHaul1995/01/03 michael kellen # # Fancy HTML Formatting Added # Message ID access recognized # Built-in Post/Reply Forms Added (stubs for postto.pl and mailto.pl) # Overviews Files Used Instead of Headers for Group List # make a call to the option handler... # require 'getopts.pl'; &Getopts ('hsrn:'); $prog = `basename $0`; chop $prog; &Usage() if ($#ARGV != -1); &Usage() if $opt_h; # # some defaults # $USER = "$ENV{'USER'}"; # user name $NAME = "$ENV{'NAME'}"; # Full Name $HOME = "$ENV{'HOME'}"; # Home directory ($HOSTNAME) = gethostbyname("$ENV{'HOSTNAME'}"); $quotechar = "$ENV{'QUOTECHAR'}"; $quotechar = ">" if (!$quotechar); $servername ="$ENV{'NNTPSERVER'}"; $servername = "usenet.coe.montana.edu" if (!$servername); $orgname = "$ENV{'NEWSORG'}"; $orgname = "$ENV{'ORGANIZATION'}" if (!$orgname); $orgname = "MSU Physics Department" if (!$orgname); $history_file = "$HOME/.access_nntp_rc"; $newsrc = "$HOME/.newsrc"; # No Need to Edit Below Here # ($name, $aliases, $port, $proto) = getservbyname("nntp", "tcp"); $version = "1.0"; $filename = "/"; $protocol = "nntp"; $return_code = 200; $return_string = "OK"; $content_type = "text/html"; if (defined $opt_n) { $servername = $opt_n; } &ProcessHTTPRequest; &GetHistory if ($opt_r); &MakeNNTPRequest; &FormatData; &ReturnHTTPData; if ($opt_r && defined %history) { dbmclose (%history); } exit 0; # # INPUT routine # sub ProcessHTTPRequest { # format of input - headers may be in any order, or not present or blank # # BLAH URI HTTP/1.0 # User-Agent: chimera/1.64 # Accept: *.* # URI: nntp://host/newsgroup/article# # X-protocol: nntp # X-hostname: host # X-port: port # X-filename: /newsgroup/article[?action] # message [optional] # [blank line] chop ($http = ); ($http_cmd, $http_uri, $http_id) = split (/\s+/, $http, 3); ($http_name, $http_version) = split (/\//, $http_id); if ($http_name ne "HTTP") { &HTTPError ("protocol \"$http_name\" not accepted\n"); } if ($http_version != $version) { &HTTPError ("invalid HTTP protocol version: $http_version\n"); } while () { chop ($type = $_); # check for the blank line that terminates input # if ($type eq "") { last; } # check for non-field lines # if ($type !~ /^[\w-]+\s*:/) { $MESSAGE .= $_; next; } # must be a field line. parse name and value # ($type_name, $type_value) = split (/\s*:\s*/, $type, 2); if ($type_name eq "X-hostname") { next if (!$type_value); $servername = $type_value; } elsif ($type_name eq "URI") { ($uri_proto, $uri_path) = split (/\s*:\s*/, $type_value); if ($uri_proto ne "news") { &HTTPError ("protocol \"$uri_proto\" not accepted for URIs\n"); } } elsif ($type_name eq "X-port") { next if ($type_value eq "0"); $port = $type_value; } elsif ($type_name eq "X-filename") { next if ($type_value eq "" || $type_value eq "/"); $filename = $type_value; } elsif ($type_name eq "X-protocol") { $protocol = $type_value; } else { push (@unknown_fields, $type_name); } } ($filename,$action) = split( '\?', $filename, 2); if ($filename =~ /@/ ) { $temp = $filename; while ($temp =~ s/\/*([^\/]+)// ) { push (@newspath, $1); } $articleID = shift(@newspath) if (@newspath >= 1); } else { $temp = $filename; while ($temp =~ s/\/*([^\/]+)// ) { push (@newspath, $1); } $group = shift(@newspath) if (@newspath >= 1); $article = shift(@newspath) if (@newspath >= 1); } $post = "Y" if ($action =~ /post/); $mail = "Y" if ($action =~ /mail/); $include = "Y" if ($action =~ /include/); $new = "Y" if ($action =~ /new/); 0; } # # LOOKUP routine # sub MakeNNTPRequest { $server = $servername; # connect to the server # local($sockaddr,$here,$there,$response,$tries) = ("Snc4x8"); $here = pack($sockaddr, 2, 0, &getaddress("localhost")); $there = pack($sockaddr, 2, $port, &getaddress($server)); &HTTPError ("socket: $!") if (!socket(N,2,1,6)); &HTTPError ("connect($servername:$port): $!") if (!connect(N,$there)); select(N); $| = 1; # make unbuffered select(STDOUT); # check the response to the initial connection # $accept = ; ($accept_code, $accept_string) = split (/\s+/, $accept, 2); if ($accept_code >=300) { &HTTPError ("initial connect problem: $accept_code $accept_string"); } if (defined $articleID) { # article requested by MessageID # if ($articleID =~ /^\//) { $articleID =~ s|^/||; } $response_string = &SubRequest ("STAT <$articleID>"); ($response_code, $response_string) = &CondSubRequest ("LAST"); if ($response_code < 300) { ($prev_article) = split (' ', $response_string); } $response_string = &SubRequest ("STAT <$articleID>"); ($response_code, $response_string) = &CondSubRequest ("NEXT"); if ($response_code < 300) { ($next_article) = split (' ', $response_string); } $response_string = &SubRequest ("ARTICLE <$articleID>"); } elsif (defined $group) { # request info on the desired group # $response_string = &SubRequest ("GROUP $group"); # process the group info # ($grp_amount, $grp_start, $grp_end) = split (' ', $response_string); if (defined $article) { # first, we make the desired article the # current one. then we get the numbers # of the previous and next articles. # # after all that, we then get the desired article # $response_string = &SubRequest ("STAT $article"); ($response_code, $response_string) = &CondSubRequest ("LAST"); if ($response_code < 300) { ($prev_article) = split (' ', $response_string); } $response_string = &SubRequest ("STAT $article"); ($response_code, $response_string) = &CondSubRequest ("NEXT"); if ($response_code < 300) { ($next_article) = split (' ', $response_string); } $request = "ARTICLE $article"; } else { # get the list of articles # $request = "XOVER ${grp_start}-${grp_end}"; } # make the article request # $response_string = &SubRequest ("$request"); } else { # no group specified, get the WHOLE list # $response_string = &SubRequest ("LIST"); } # grab the data provided. look for the end-marker # line with a single period # $line = ; while ($line !~ /^\./) { $DATA .= $line; $line = ; } # close the nntp connection # print N "QUIT\n"; 0; } # this little routine sends requests to the nntp server # and checks the response code for errors. if it is # successful, we only need the $response_string # sub SubRequest { local ($request) = @_; local ($response, $response_code, $response_string); print N "$request\n"; chop ($response = ); ($response_code, $response_string) = split (' ', $response, 2); if ($response_code >= 300) { &HTTPError ("bad request: \"$request\": $response_code ", "$response_string"); } $response_string; } # this little routine sends requests to the nntp server # and checks the response code for errors. if it is # successful, we should get the $response_code, for # further checking of codes, and the $response_string. # sub CondSubRequest { local ($request) = @_; local ($response, $response_code, $response_string); print N "$request\n"; chop ($response = ); ($response_code, $response_string) = split (' ', $response, 2); if ($response_code >= 300 && $response_code < 400) { &HTTPError ("bad request: \"$request\": $response_code ", "$response_string"); } ($response_code, $response_string); } sub FillHeader { $sigfile = "$ENV{'SIGNATURE'}"; $sigfile = "$HOME/.signature" if (!$sigfile); if ( -r $sigfile ) { open (SIG, $sigfile); while() { $signature .= $_; } close(SIG); } $REPLYTO = "$ENV{'REPLYTO'}"; # Any valid Reply-To value if ( $post eq "Y" ) { $head = "$ENV{'NEWSHEADER'}"; if (!$head) { $head = "Newsgroups: %n\n"; $head .= "Subject: %s\n"; $head .= "From: %N <%L@%H>\n"; $head .= "Organization: %o\n" if ($orgname); $head .= "Followups-To: $followups\n" if ($followups); $head .= "Keywords: \n"; $head .= "References: %R\n"; $head .= "Reply-To: $REPLYTO\n" if ($REPLYTO); $head .= "Cc: \n\n"; } $attrib = "$ENV{'ATTRIB'}"; if (!$attrib) { $attrib = "In %i, %f said:\n"; } } elsif ( $mail eq "Y" ) { $head = "$ENV{'MAILHEADER'}"; if (!$head) { $head .= "To: %f\n"; $head .= "Subject: %s\n"; $head .= "From: %N <%L@%H>\n"; $head .= "Organization: %o\n" if ($orgname); $head .= "Keywords: \n"; $head .= "References: %R\n"; $head .= "Reply-To: $REPLYTO\n" if ($REPLYTO); $head .= "Cc: \n\n"; } $attrib = "$ENV{'YOUSAID'}"; if (!$yousaid) { $attrib = "In %i, you said:\n"; } } ($messageID) = $HEAD =~ /\nMessage-ID: <([^>]*)>/; ($refs) = $HEAD =~ /\nReferences: ([^\n]*)/; $refs =~ s/ //; $refs .= " <$messageID>"; $head =~ s/%C/$group/ if ( $head =~ /%C/); $head =~ s/%n/$groups/ if ( $head =~ /%n/); $head =~ s/%s/$subject/ if ( $head =~ /%s/); $head =~ s/%R/$refs/ if ( $head =~ /%R/); $head =~ s/%o/$orgname/ if ( $head =~ /%o/); $head =~ s/%N/$NAME/ if ( $head =~ /%N/); $head =~ s/%L/$USER/ if ( $head =~ /%L/); $head =~ s/%H/$HOSTNAME/ if ( $head =~ /%H/); $head =~ s/%f/$author/ if ( $head =~ /%f/); $head =~ s/%i/<$messageID>/ if ( $head =~ /%i/); $head =~ s//>/g; $attrib =~ s/%C/$group/ if ( $attrib =~ /%C/); $attrib =~ s/%n/$groups/ if ( $attrib =~ /%n/); $attrib =~ s/%s/$subject/ if ( $attrib =~ /%s/); $attrib =~ s/%R/$refs/ if ( $attrib =~ /%R/); $attrib =~ s/%o/$orgname/ if ( $attrib =~ /%o/); $attrib =~ s/%N/$NAME/ if ( $attrib =~ /%N/); $attrib =~ s/%L/$USER/ if ( $attrib =~ /%L/); $attrib =~ s/%H/$HOSTNAME/ if ( $attrib =~ /%H/); $attrib =~ s/%f/$author/ if ( $attrib =~ /%f/); $attrib =~ s/%i/<$messageID>/ if ( $attrib =~ /%i/); $BODY = "$attrib\n$BODY" if ( $include eq "Y" ); } sub FancyFormatArticle { @ArtLines = split( /\n/, $DATA); $HEAD = ""; $BODY = ""; while (@ArtLines > 0) { $thisline = shift(@ArtLines) . "\n"; if ( $thisline !~ /^[A-Z+]/) { last; } $HEAD .= $thisline ; } while (@ArtLines > 0) { $thisline = shift(@ArtLines); if ( $include eq "Y" ) { chop $thisline; $BODY .= "$quotechar$thisline\n"; } elsif ( !$action ) { $thisline .= "\n"; $thisline =~ s|^([>:\|]+)([^\n]*)|$1$2|; $thisline =~ s|<|<|g; $thisline =~ s|>|>|g; $thisline =~ s|<(/*I)>|<$1>|g; $BODY .= $thisline ; } } ($subject) = $HEAD =~ /Subject: ([^\n]*)/; $subject =~ s/ //; ($messageID) = $HEAD =~ /\nMessage-ID: ([^\n]*)/; $messageID =~ s/ //; $messageID =~ s/>//; $messageID =~ s/]*)>/) { $from = "$2"; } elsif ($author =~ /([^(]*)\(([^)]*)\)/ ) { $from = "$1"; } elsif ($author =~ /([^@]*@.*)/ ) { $from = "$1"; } ($group) = $HEAD =~ /Newsgroups: ([^,\n]*)/; $group =~ s/ //; ($groups) = $HEAD =~ /Newsgroups: ([^\n]*)/; $groups =~ s/ //; ($refs) = $HEAD =~ /References: ([^\n]*)/; if ( $action ) { $subject =~ s/(Re: )//g; $subject = "Re: $subject"; # Personal bias ... crossposts set to followup to ONE GROUP # ONLY. Can be edited, but requires intentional crossposting. $followup = $group if ($group ne $groups); &FillHeader; $NEWDATA = ""; $NEWDATA .= "Posting to $group" if ( $post eq "Y" ); $NEWDATA .= "Mailing to $author" if ( $mail eq "Y" ); $NEWDATA .= "\n"; $NEWDATA .= "
\n" if ( $post eq "Y" ); $NEWDATA .= "mailto:$from>\n" if ( $mail eq "Y" ); $NEWDATA .= "\n"; $NEWDATA .= "\n"; $NEWDATA .= "
\n"; $NEWDATA .= "\n"; $NEWDATA .= ""; $NEWDATA .= " Append Signature "; $NEWDATA .= "" if ( $post eq "Y" ); $NEWDATA .= "Mail>" if ( $mail eq "Y" ); $NEWDATA .= "
"; } else { $subject =~ s/>/>/; $subject =~ s/Date: $1
\n"; $date =~ s/ //; } if ($HEAD =~ /Organi[sz]ation: ([^\n]*)/) { $org = "Organization: $1
\n"; $org =~ s/ //; } if ($author =~ /([^<]*)<([^>]*)>/) { $author = "$1 <$2>"; } elsif ($author =~ /([^(]*)\(([^)]*)\)/ ) { $author = "$2 <$1>"; } elsif ($author =~ /([^@]*@.*)/ ) { $author = "<$1>"; } $author = "Author: $author
\n"; @GroupList = split ( /,|\n/ , $groups ); $groups = ""; while (@GroupList > 0) { $thisline = shift(@GroupList); $groups .= "[$thisline]"; } if ($HEAD =~ /References: ([^\n]*)/) { $refs = $1 ; $refs =~ s/<|>| //g; @GroupList = split ( /,|\s|\n/ , $refs ); $refs = "References: "; while (@GroupList > 0) { $thisline = shift(@GroupList); $refs .= "" . "<$thisline>"; } $refs .= "
\n"; } $HEAD =~ s//>/g; $BODY =~ s|<URL:([a-z][^&]*)>|<URL:$1>|g; $BODY =~ s|<([^@\s]*@[^&\s]*)>|<$1>|g; $BODY =~ s|[^=:](http:[^\s\n]*)|<$1>|g; $BODY =~ s|[^=:](gopher:[^\s\n]*)|<$1>|g; $BODY =~ s|[^=:](ftp:[^\s\n]*)|<$1>|g; $BODY =~ s|\n--[\s-]*\n|\n
\n|g; $BODY =~ s|article <([^\s&]*)>|<$1>|g; $NEWDATA .= "$subject\n" ; $NEWDATA .= "

$groups

\n$date"; $NEWDATA .= "

$subject

\n" ; $NEWDATA .= "$author$org$refs"; $NEWDATA .= "" ; $NEWDATA .= "
\n$BODY\n
\n" ; $NEWDATA .= " [ PREV ] " if (defined $prev_article); $NEWDATA .= " [ NEXT ] " if (defined $next_article); $NEWDATA .= "
\n"; $NEWDATA .= " [" . " POST+INCLUDE ] "; $NEWDATA .= " [" . " POST ] "; $NEWDATA .= " [" . " NEW THREAD ] "; $NEWDATA .= " [" . " MAIL ] "; $NEWDATA .= " [" . " MAIL+INCLUDE ] "; $NEWDATA .= "\n\n"; } } sub FormatData { if ( defined $articleID) { $curr_url = "$protocol:$articleID"; &FancyFormatArticle; } elsif (defined $group) { if (defined $article) { $curr_url = "$protocol:/$group"; &FancyFormatArticle; } else { $curr_url = "$protocol:$filename"; &FilterArticles() if ($opt_r); &SortBySubject() if ($opt_s); $NEWDATA .= "NEWSGROUP $group"; $NEWDATA .= " [" . " ALL GROUPS ]\n"; $NEWDATA .= "

$group

\n"; $NEWDATA .= "sorted by subject\n" if ($opt_s); $NEWDATA .= "
[" . " START NEW THREAD ]
\n"; $NEWDATA .= "
    \n"; @data_array = split (/\n/, $DATA); foreach $l (@data_array) { # ($article_id, $article_subj) = # split (' ', $l, 2); # uncomment if using Overviews instead ($article_id, $article_subj, $author, $info) = split ('\t', $l, 4); if ( $author =~ /\S+@\S+\s+\(([^)]+)/ ) { $author = $1; } elsif ( $author =~ /([^<]+)/>/g ; $NEWDATA .= # uncomment the next line and comment out the line following # if you want the article subjects to be the links # # "
  • $article_id ". "
  • ". "". # uncomment the next line and comment out the line following # if you want the article subjects to be the links # "$article_subj"; # "$article_id $article_subj"; # uncomment if using overviews to display author # $NEWDATA .= " [$author] "; $NEWDATA .= "\n"; } $NEWDATA .= "
\n"; $NEWDATA .= "
[" . " START NEW THREAD ]
\n"; } } else { $curr_url = "$protocol:"; # give the page a heading # $NEWDATA .= "NEWS from $servername\n"; $NEWDATA .= "

NEWS from $servername

\n"; # start the listing of groups # $NEWDATA .= "
    \n"; @data_array = split (/\n/, $DATA); foreach $l (sort @data_array) { ($group_name, $group_n1, $group_n2, $group_flag) = split (' ', $l, 4); $NEWDATA .= "
  • ". "". "$group_name\n"; } $NEWDATA .= "
\n"; } $DATA = $NEWDATA if (defined $NEWDATA); } # # an article filter # sub FilterArticles { local (@list, %article_array, $l, $id, $range, $low, $high, $entry, $FILTER_DATA); @list = split (',', $history{$group}); foreach $l (split (/\n/, $DATA)) { ($id, $entry) = split (' ', $l, 2); $article_array{$id} = $entry; } while ($range = shift (@list)) { ($low, $high) = split ('-', $range); $high = $low if ($high eq ""); for ($id = $low; $id <= $high; $id++) { if ($article_array{$id} ne "") { $article_array{$id} = ""; } } } foreach $id (sort keys (%article_array)) { $FILTER_DATA .= "$id $article_array{$id}\n" if ($article_array{$id} ne ""); } $DATA = $FILTER_DATA; 0; } # # a simple article sorter # sub SortBySubject { local(@skeys, @data); @data = split(/\n/, $DATA); foreach $l (@data) { chop ($l); ($id, $subject) = split(' ', $l, 2); $RE = ""; if ($subject =~ s/^(Re: )//) { $RE = $1; } push(@skeys, $subject); } sub byskeys { if($skeys[$a] eq $skeys[$b]) { $a <=> $b; } else { $skeys[$a] cmp $skeys[$b]; } } @sortdata = @data[sort byskeys $[..$#data]; $DATA = join("\n", @sortdata); } # # OUTPUT routine # sub ReturnHTTPData { # format of output: # # HTTP/1.0 200 OK # Content-type: text/html # Content-length: 12345 # # DATA # $content_length = length ($DATA); print STDOUT "HTTP/$version $return_code $return_string\n"; print STDOUT "Content-type: $content_type\n"; print STDOUT "Content-length: $content_length\n"; print STDOUT "\n"; print STDOUT "$DATA"; print STDOUT "\n"; 0; } sub Usage { print STDERR "Usage:\t$prog [-n ]\n\n"; print STDERR "\tThis PERL filter accepts specialized\n"; print STDERR "\tHTTP requests from Chimera on stdin\n"; print STDERR "\tand converts them into NNTP requests\n"; print STDERR "\tto the local server. The program then\n"; print STDERR "\tformats the results into an HTTP response\n"; print STDERR "\tsent to stdout, and exits.\n"; exit 1; } # a little routine that translates a # machine name into its address # sub getaddress { local($host) = @_; local(@ary); @ary = gethostbyname($host); return(unpack("C4",$ary[4])); } sub HTTPError { local(@string) = @_; local($message); $message .= "\n" . "

ERROR

\n" . "news: " . join ('', @string) . "\n\n"; $content_length = length($message); print STDOUT "HTTP/$version $return_code $return_string\n"; print STDOUT "Content-type: text/html\n"; print STDOUT "Content-length: $content_length\n"; print STDOUT "$message"; exit 1; } #--------------------------------------------------------------------------- # # #--------------------------------------------------------------------------- # # a .newsrc clone # sub GetHistory { local($key, $entry); if (! dbmopen (%history, $history_file, undef)) { dbmopen (%history, $history_file, 0600); if (open (RC, "<$newsrc")) { print STDERR "creating history file from '$newsrc'.\n"; while () { chop; ($key, $entry) = split(/[!:]\s*/, $_, 2); print STDERR "[$key] -> {$entry}\n"; $history{$key} = $entry; } } else { warn "unreadable '$newsrc': $!. starting from scratch...\n"; } } }