#!/usr/local/bin/perl # readmsg-m (configured to read from U. Mannheim news server) # readmsg # Read-only Usenet message reader. # Reads a Usenet message specified by number, via nntp. # Works with the accompanying readnews script, which retrieves # article headers. # # Use: # Message Title # v1.45 - February 6, 2001 -rsc # - now uses Socket module for better socket compatibility # v1.44 - July 25, 1998 -rsc # - Works with already HTMLified addresses in message text. # - HTMLify routine recognizes more special characters in e-mail addresses. # v1.4 - July 23, 1998 -rsc # - Avoid HTMLifying article numbers that look like e-mail addresses. # - Entify < > and " in header lines and message bodies. # - Send an overt quit command to news server when finished. # v1.3 - July 14, 1998 -rsc # - HTMLify e-mail addresses and URLs in text portion of messages, # in addition to those in message headers # Other minor modifications from Bob K.'s and G. Heil's code for # Seattle Community Network. # ($these variables are unchanged, $TheseVariables are my changes) # # Rod Clark #---------------------------------------------------------------- # configuration #---------------------------------------------------------------- $newshost = "news.uni-mannheim.de"; # The following prompt appears on error message screens (except # "connection refused" errors) but not on articles. $EmailPrompt = "Questions about using this newsreader: "; $EmailAddress = "webmaster\@scn.org"; $port = 119; $AF_INET = 2; $sockaddr = 'S n a4 x8'; use Socket; $MaxArticleLines = 1000; @ArticleIDWords = ('article', 'message', 'in', 'message-id:', 'references:'); #---------------------------------------------------------------- # end of configuration #---------------------------------------------------------------- ($newsgroup, $artnum) = split( /\,/, $ENV{'QUERY_STRING'}, 2); # $debug = 1; # $newsgroup = "seattle.general"; # $artnum = 191; &PrintContentTypeHeader; &PrintPageTop; &ConnectToNewsServer; &SpecifyGroup; &RetrieveArticle; &QuitNewsServer; &PrintPageFooter; exit 0; #---------------------------------------------------------------- sub ConnectToNewsServer { # connect to NNTP server # translate protocol name to associated number ($name, $aliases, $proto) = getprotobyname ('tcp'); # translate service (port) name to corresponding number ($name, $aliases, $port) = getservbyname ($port, 'tcp') unless $port =~ /^\d+$/; # translate network hostname to corresponding number ($name, $aliases, $type, $len, $thisaddr) = gethostbyname ($hostname); if ($newshost =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/) { $thataddr = pack ('C4', $1, $2, $3, $4); } elsif ($newshost =~ /(\w+)(\.\w+)*/) { ($name, $aliases, $type, $len, $thataddr) = gethostbyname ($newshost); } else { $ErrorMessage = "Error: NNTP host not specified in proper format"; &PrintErrorFooter; exit 1; } if ($debug) { print "DEBUG: IP = $thataddress\n"; } $this = pack ($sockaddr, $AF_INET, 0, $thisaddr); $that = pack ($sockaddr, $AF_INET, $port, $thataddr); # Make the socket a filehandle if (socket (S, $AF_INET, SOCK_STREAM, $proto)) { if ($debug) { print "DEBUG: socket ok\n"; } } else { $ErrorMessage = "Error: socket failed"; &PrintErrorFooter; exit 1; } # give the socket an address if (bind (S, $this)) { if ($debug) { print "DEBUG: bind ok\n"; } } else { $ErrorMessage = "Error: bind to $this failed. $1"; &PrintErrorFooter; exit 1; } # Call up the server if (connect (S, $that)) { if ($debug) { print "DEBUG: Connect to $newshost ok\n"; } } else { $ErrorMessage = "Can't connect to $newshost TCP/IP port $port. Error: $1"; &PrintErrorFooter; exit 1; } # buffer the socket select (S); $| = 1; select (STDOUT); # read news server status $_ = ; ($stat, $rest) = split(/ /, $_, 2); if ($stat == 200 || $stat == 201) { # on initial connection, NNTP server will return # 200 server ready - posting allowed # 201 server ready - no posting allowed # otherwise there has been a problem if ($debug) { if ($stat == 200) { print "DEBUG: status=$stat (posting allowed)\n"; } elsif ($stat == 201) { print "DEBUG: status=$stat (no posting)\n"; } } } else { $ErrorMessage = "$newshost refused connection with message: $stat - $rest"; &PrintErrorFooterPlain; exit 0; } } sub SpecifyGroup { print S "GROUP $newsgroup\n"; $_ = ; if ($debug) { print "DEBUG: server group line: $_\n"; } ($status, $gmany, $gfirst, $glast, $gname) = split (/ /, $_, 5); if ($status == 211) # status is OK { if ($gmany == 0) { $ErrorMessage = "$newsgroup is empty"; &PrintErrorFooter; exit 0; } } elsif ($status == 411) { $ErrorMessage = "$newsgroup does not exist on server"; &PrintErrorFooter; exit 0; } else { $ErrorMessage = "can't access newsgroup - please try again later"; &PrintErrorFooter; exit 1; } } sub RetrieveArticle { # request status of article print S "STAT $artnum\n"; # read status response $_ = ; ($status, $article, $id, $rest) = split (/ /, $_, 4); if ($status != 223) # article not retrieved { print "
\n"; print "

\n"; print "article could not be retrieved\n"; print "

\n"; &PrintErrorFooter; exit 0; } else { # send request for article print S "article\n"; # read status response $_ = ; # determine article status from response ($status, $article, $id, $rest) = split (/ /, $_, 4); if ($status != 220) # article status not OK { print "
\n"; print "

\n"; if ($status == 400) { print "can't connect to news server - please try again later\n"; } elsif ($status == 412) { print "no newsgroup selected\n"; } elsif ($status == 423) { print "article does not exist on server\n"; } elsif ($status == 430) { print "article not found\n"; } else { print "article status not valid\n"; } print "

\n"; &PrintErrorFooter; exit 0; } else { $in_header = 1; # read first line of header $_ = ; # loop until encounter lone period at start of line # (.^J ends text in NNTP) $LineCount = 1; while (($_ !~ /^\.[^\.].*$/) && ($LineCount < $MaxArticleLines)) { # remove trailing CR LF chop; chop; if ($in_header) { # blank line marks end of header if ($_ =~ /^\s*$/) { $in_header = 0 ; print "

\n

\n";
          }
          elsif ($_ =~ /^From: (.*)/)
          {
            $from = $1;
            if ($from =~ /([\w\-\!\+\#\$\!]+\.*[\w\-\!\+\#\$\.]+\@[\w\-\!\+\$\#\.]+\.[A-Za-z]+)/)
            {
              $email = $1;
            }
            $from = &EntifyVariable ($from);
            if ($email)
            {
              print "From: $from
\n"; } else { print "From: $from
\n"; } } elsif ($_ =~ /^Subject: (.*)/) { $subject = $1; $subject = &EntifyVariable ($subject); print "Subject: $subject
\n"; } elsif ($_ =~ /^Date: (.*)/) { $date = $1; $date = &EntifyVariable ($date); print "Date: $date
\n"; } } else { &HTMLifyAddressesInTextLine; print "$_\n"; } # read next line of article $_ = ; $LineCount++; } print "
\n"; } } } sub QuitNewsServer { print S "QUIT\n"; $_ = ; if ($debug) { print "DEBUG: response to quit: $_\n"; } ($status, $StatusMessage) = split (/ /, $_, 2); if ($status != 205) { $ErrorMessage = "news server status=$status: $StatusMessage\n"; &PrintErrorFooter; exit 1; } } sub PrintContentTypeHeader { print "Content-type: text/html\n\n"; } sub PrintPageTop { print < $newsgroup

$newsgroup


ENDPRINT } sub PrintErrorFooter { print <$ErrorMessage
$EmailPrompt$EmailAddress ENDPRINT &PrintPageFooter; } sub PrintErrorFooterPlain { print <$ErrorMessage
ENDPRINT } sub PrintPageFooter { print < ENDPRINT } sub HTMLifyAddressesInTextLine { $OrigLine = &EntifyVariable ($_); &HTMLifyEmailAddressesInLine; &HTMLifyURLsInLine; $_ = $OrigLine; } sub HTMLifyEmailAddressesInLine { $TextLine = $OrigLine; $AllAddressesFound = 0; while (!$AllAddressesFound) { # skip strings already HTMLified on same line foreach $FoundEmailAddress (@FoundEmailAddresses) { if ($TextLine =~ /$FoundEmailAddress/) { $TextLine =~ s/$FoundEmailAddress//g; } } # HTMLify e-mail addresses if ($TextLine =~ /([\w\-\+\!\#\$]+\.*[\w\-\+\!\#\$\.]+\@[\w\-\+\!\$\#\.]+\.[A-Za-z]+)/) { # HTMLify e-mail addresses $FoundString = $1; $FoundEmailString = &EscapeCharsInString ($FoundString); $EscFoundString = &DoubleEscapeCharsInString ($FoundString); $SkipArticle = 0; foreach $ArticleIDWord (@ArticleIDWord) { if ($TextLine =~ /\b$ArticleWord\s+<$FoundEmailString>/i) { $SkipArticle = 1; } } if (!$SkipArticle) { push (@FoundEmailAddresses, $FoundEmailString); } $TextLine =~ s/$EscFoundString//; } else { $AllAddressesFound = 1; } } foreach $FoundEmailAddress (@FoundEmailAddresses) { $OrigLine =~ s/$FoundEmailAddress/$FoundEmailAddress<\/a>/; } } sub HTMLifyURLsInLine { $TextLine = $OrigLine; $AllAddressesFound = 0; while (!$AllAddressesFound) { # skip strings already HTMLified on same line foreach $FoundWebAddress (@FoundWebAddresses) { if ($TextLine =~ /$FoundWebAddress/) { $TextLine =~ s/$FoundWebAddress//g; } } if (($TextLine =~ /(http:\/\/[\w-]+\.[\w-\.]*[A-Za-z]+[\S]*)/i) || ($TextLine =~ /(https:\/\/[\w-]+\.[\w-\.]*[A-Za-z]+[\S]*)/i) || ($TextLine =~ /(ftp:\/\/[\w-]+\.[\w-\.]*[A-Za-z]+[\S]*)/i) || ($TextLine =~ /(telnet:\/\/[\w-]+\.[\w-\.]*[A-Za-z]+[\S]*)/i) || ($TextLine =~ /(gopher:\/\/[\w-]+\.[\w-\.]*[A-Za-z]+[\S]*)/i)) { $FoundAddress = $1; ($FoundAddress, $Junk) = split (/\"/, $FoundAddress, 2); ($FoundAddress, $Junk) = split (/"/, $FoundAddress, 2); ($FoundAddress, $Junk) = split (//, $FoundAddress, 2); ($FoundAddress, $Junk) = split (/</, $FoundAddress, 2); ($FoundAddress, $Junk) = split (/>/, $FoundAddress, 2); $FoundWebAddress = &EscapeCharsInString ($FoundAddress); # remove trailing comma, period(s) and other common punctuation while ($FoundWebAddress =~ /[\.,\(\)!:;\"\'\*\[\]\{\}\`<>]$/) { $FoundWebAddress =~ s/[\.,\(\)!:;\"\'\*\[\]\{\}\`<>]$//; } $EscFoundWebAddress = &DoubleEscapeCharsInString ($FoundWebAddress); push (@FoundWebAddresses, $EscFoundWebAddress); $TextLine =~ s/$EscFoundWebAddress//; } else { $AllAddressesFound = 1; } } foreach $FoundWebAddress (@FoundWebAddresses) { $OrigLine =~ s/$FoundWebAddress/$FoundWebAddress<\/a>/; } } sub EntifyVariable { local ($ToEntify) = @_; $ToEntify =~ s//>/g; $ToEntify =~ s/"/"/g; return ($ToEntify); } sub EscapeCharsInString { local ($StringToEscape) = @_; $StringToEscape =~ s/\$/\$/g; $StringToEscape =~ s/\+/\+/g; $StringToEscape =~ s/\@/\@/g; $StringToEscape =~ s/\./\./g; $StringToEscape =~ s/\?/\?/g; $StringToEscape =~ s/\*/\*/g; $StringToEscape =~ s/\|/\|/g; $StringToEscape =~ s/\{/\{/g; $StringToEscape =~ s/\}/\{/g; $StringToEscape =~ s/\[/\[/g; $StringToEscape =~ s/\]/\]/g; $StringToEscape =~ s/\//\//g; $StringToEscape =~ s/\!/\!/g; $StringToEscape =~ s/\#/\#/g; $StringToEscape =~ s/\%/\%/g; $StringToEscape =~ s/\&/\&/g; return ($StringToEscape); } sub DoubleEscapeCharsInString { local ($StringToEscape) = @_; $StringToEscape =~ s/\$/\\\$/g; $StringToEscape =~ s/\+/\\\+/g; $StringToEscape =~ s/\@/\@/g; $StringToEscape =~ s/\./\./g; $StringToEscape =~ s/\?/\?/g; $StringToEscape =~ s/\*/\*/g; $StringToEscape =~ s/\|/\|/g; $StringToEscape =~ s/\{/\{/g; $StringToEscape =~ s/\}/\{/g; $StringToEscape =~ s/\[/\[/g; $StringToEscape =~ s/\]/\]/g; $StringToEscape =~ s/\//\//g; $StringToEscape =~ s/\!/\!/g; $StringToEscape =~ s/\#/\#/g; $StringToEscape =~ s/\%/\%/g; $StringToEscape =~ s/\&/\&/g; return ($StringToEscape); }