#!/usr/local/bin/perl
#
#
# NewsInfo server
#
# (C) 1993 University of Minnesota
#
# Version 0.9
#
###############
#
# "NewsInfo" provides compacted NNTP server information to
# Minuet clients.
#
# George R. Gonzalez
#
#
[email protected]
#
####
#
# To install:
# choose a directory for it (/usr/local/bin/newsinfo is our choice)
# change the directories below to your liking.
# change the name of the news server to be your local news server.
# copy this file to the dir
# put "perl /thedir/newsinfo.pl &" in some system startup script
#
# no need to add lines to "services" or "inetd.conf"
#
# this script runs a server without the need of either one.
#
#
# Questions, brickbats to
[email protected]
#
#
##########
###############
#
# Site configurable parameters:
#
#
$OurDir = "/usr/local/bin/newsinfo"; # our home base
$nntp_server = "news.cis.umn.edu"; # the suggested NNTP news server
$nntp_port = 119; # the port it runs on
$maxage = 1.0; # How often to refresh info (in days)
###############
#
# optional configuration:
#
# Most sites can live with these choices
$LogDir = "$OurDir"; # where the log files go
$LogFileName = "$LogDir/log"; # our log file
$errors = "$LogDir/errors"; # log of groups with errors
$zeroes = "$LogDir/zeroes"; # log of groups that are empty
$version = "0.9";
$askserver = 1;
$our_port = 7119; # Temporary port number, will change to permanent someday
$using_inetd = 0;
$EINTR = 4;
$nntp_groups = "$OurDir/all.groups"; # our main data file
$nntp_temp = "$OurDir/temp.groups"; # the one being built
#
$separator = "\001";
$eol = "\r\n";
@okcmds = ( "help", "quit", "where", "dir", "find" );
sub Gabort {
&Log( "Server exiting, reason is $_[0]" );
exit;
}
sub GopenServer {
local($server,$port) = @_;
$sockaddr = 'S n a4 x8';
(($name, $aliases, $type, $len, $saddr) = gethostbyname($server))
|| &Gabort("Can't get address of: $server");
$sin = pack($sockaddr, 2, $port, $saddr);
socket(GSERVER, 2, 1, 0) || &Gabort("Can't create socket: $!");
connect(GSERVER, $sin) || &Gabort("Can't connect to server: $!");
select(GSERVER);
$| = 1;
}
sub GcloseServer {
close(GSERVER);
}
sub Gsend {
print "send -> |$_[0]|\n" if (defined($Gdebug));
print GSERVER "$_[0]$eol";
}
sub Grecv {
local ($_);
$_= <GSERVER>;
s/\n$//;
s/\r$//;
print "recv -> |$_|\n" if (defined($Gdebug));
return $_;
}
sub OutToNet {
print $NETOUT @_[0];
}
sub fileok {
local( $ok );
$ok = -f @_[0];
if( ! $ok ) {
&flush(1);
&OutToNet( "-invalid command, file @_[0] not available.$eol" );
}
return( $ok );
}
sub parsenums {
local($_);
&Gsend( "GROUP " . $gname );
$_ = &Grecv;
$errline = $_;
($stat,$lo,$hi,$num) = /(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/;
}
sub massage {
$gname =~ s/\./$separator/g;
$desc = $_;
$desc =~ s/^\S+\s+//;
$desc =~ s/^\?+$//;
if( $desc eq "" ) {
$desc = "?";
}
}
sub serverstuff {
$ok = 0;
&parsenums;
if ( $stat != 211 )
{
$badstatus++;
print ERR "$gname gave error $errline\n";
}
else {
if ( $lo == 0 || $hi == 0 || $num == 0 )
{
$zeroval++;
print ZER "Group $gname has zero messages\n";
}
else
{
&massage;
$ok = 1;
}
}
}
sub getgroupinfo {
&Log( "getting groups...");
$enterwall = &JulianSeconds;
&GopenServer( $nntp_server, $nntp_port );
$a = &Grecv;
&Gsend( "LIST NEWSGROUPS" );
$_ = &Grecv;
&Gabort($_) if !/^215/;
@groups = ();
while ( <GSERVER> ) {
chop;
chop;
last if /^\.$/;
push( @groups, $_ );
}
&Log( "now looking up each one..." );
open( ERR, ">$errors" )||die "help me with errors!";
open( ZER, ">$zeroes" )||die "help me with zeroes";
@outlist = ();
$inx = 0;
$tot = 0;
$badstatus = 0;
$zeroval = 0;
foreach( @groups ) {
@res = split (' ', $_ );
$gname = shift( @res );
$inx++;
$tot++;
if( $askserver ) {
&serverstuff;
}
else {
&massage;
$ok = 1;
}
if( $ok ) {
push( @outlist, $gname . $separator . $desc );
}
}
&Log( "all looked up, now sorting..." );
close( ERR );
close( ZER );
@groups = ();
@sorted = sort( @outlist );
&Log( "Sorted, now writing file" );
@outlist = ();
&Gsend( "QUIT" );
&GcloseServer;
open( GROUPS, ">$nntp_temp" );
foreach( @sorted ) {
print GROUPS $_,"\n";
}
close GROUPS;
&maketop;
rename( $nntp_temp, $nntp_groups ) || die "cant rename $nntp_temp to $nntp_groups$eol";
&Log( "Total groups in incoming list: $tot" );
&Log( "Groups that dont exist: $badstatus" );
&Log( "Zero size groups: $zeroval" );
$exitwall = &JulianSeconds;
$elapsed = $exitwall - $enterwall;
&Log( "Wall clock time elapsed: $elapsed seconds." );
}
sub loadgroups{
open( G, "$nntp_groups" );
@sorted = ();
while( <G> ) {
chop;
push( @sorted, $_ ) };
close G;
}
sub maketop {
open( TOP, ">$OurDir/top.cache" ) || die "cant create top cache file";
open( INX, ">$OurDir/index" ) || die "cant create index file";
$lastg = "?";
$pos = 0;
&flush(1);
foreach( @sorted ) {
@gps = split( $separator, $_ );
$g = @gps[0];
if( $g ne $lastg ) {
$numflds = @gps;
print TOP "$g";
if ($numflds > 2 ) {
print TOP ".";
};
print TOP "\n";
$lastg = $g;
print INX "$g $pos\n";
}
$pos += length($_) + 1;
}
close TOP;
close INX;
}
sub loadorget {
if( -e $nntp_groups ) {
&loadgroups;
}
else
{
&getgroupinfo;
}
}
#####################
#
#
sub CMD_find {
local( $limit ) = @cmds[1];
local( $pat ) = @cmds[2];
if( &fileok( "$nntp_groups" ) ) {
open( A, "$nntp_groups" )||die "help me with groups!";
@list = eval "grep ( /$pat/io, <A> ); ";
$sent = 0;
foreach( @list ) {
@f = split( $separator, $_ );
$c = @f;
$out = "";
for( $i = 0; $i < $c-1; $i++ ) {
$out = $out . @f[$i];
if( $i < $c-2 ) {
$out = $out . ".";
}
}
$d = @f[ $c - 1 ];
chop( $d );
$out = "$out|$d$eol";
&OutToNet( $out );
$sent += length($out);
last if $sent >= $limit;
}
close( A );
&SendEOM;
}
}
sub CMD_dir {
local($_);
$numargs = @cmds;
if( $numargs == 1 ) {
&SendTop;
} else
{
if( &fileok( "$nntp_groups" ) ) {
open( F, $nntp_groups );
$s = @cmds[1];
&Log( "Searching for /$s/" );
$s =~ s/\./$separator/;
$oldf = "?";
@grp = split( $separator, $s );
$firstgrp = @grp[0];
$place = $index{ $firstgrp };
seek( F, $place, 0 );
while( <F> ) {
chop;
$line = $_;
last if ! /^$firstgrp/;
if( /^$s/ ) {
$line =~ s/^$s//;
$line =~ s/^$separator//;
@flds = split( $separator, $line );
$num = @flds;
if($num == 1 ) {
# ignore duplicate upper group
}
else
{
$f = @flds[0];
if( $f ne $oldf ) {
&OutToNet( "$f" );
if( @flds[2] ne "" ) {
&OutToNet( "." );
$oldf = $f;
}
&OutToNet( "|" );
$desc = @flds[$num-1];
if( $desc ne "?" ) {
&OutToNet( $desc );
};
&OutToNet( "$eol" );
}
}
}
}
close( F );
&SendEOM;
}
}
}
sub CMD_where {
&flush(1);
&OutToNet( "+ $nntp_server $nntp_port$eol" );
}
sub CMD_help {
local( $_ );
&OutToNet( "HELP:$eol Commands are:$eol" );
foreach( @okcmds ) {
&OutToNet( "$_$eol" );
}
&SendEOM;
}
sub CMD_quit {
&OutToNet( "+ Bye for now...$eol" );
$done = 1;
}
sub SendTop {
local( $_);
if( &fileok( "$OurDir/top.cache" ) ) {
open( TOP, "$OurDir/top.cache" ) || die "cant open top cache";
while( <TOP> ) {
chop;
&OutToNet( "$_$eol" );
}
&SendEOM;
close( TOP );
}
}
sub flush {
local( $mode ) = @_[0];
select( $NETOUT );
$| = $mode;
select( STDOUT );
}
sub SendEOM{
&flush(1);
&OutToNet( ".$eol" );
}
sub GetInput {
$_ = <$NETIN>;
&flush(0);
return $_;
}
sub server {
$NETOUT = @_[1];
$NETIN = @_[0];
&flush(1);
&OutToNet( "+ Welcome to the NewsInfo server - version $version$eol" );
$done = 0;
while ( &GetInput ) {
&readindex;
tr/A-Z/a-z/;
@cmds = split( ' ', $_ );
$cmd = @cmds[0];
&Log( "Command: $cmd" );
$ok = 0;
foreach( @okcmds ) {
if( $_ eq $cmd ) {
eval( "&CMD_$cmd" );
$ok = 1;
};
}
last if $done;
if( $ok != 1 ) {
&OutToNet( "- Invalid command$eol" );
&Log( "Invalid command: /$cmd/" );
}
}
}
sub refresh {
if( -e $nntp_temp ) # refresher is busy.. don't run again
{ $fileok = 1; }
else {
if( -e $nntp_groups )
{ $fileok = 0; $age = -C $nntp_groups; }
else
{ $fileok = 0; $age = 9999999; }
}
if( ! $fileok )
{
if( $age > $maxage )
{
if( fork() == 0 )
{ &Log( "refreshing grp file" );
&getgroupinfo;
exit(0);
}
}
}
}
sub JulianSeconds {
return( time );
}
sub Log{
local( $line ) = @_[0];
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdist) = localtime;
$now = "$hour:$min:$sec";
$who = "PID $$";
print LOG "$now $who: $line\n";
}
sub openlog {
$fn = "$LogFileName";
if( -e $fn ) {
$size = -s $fn;
} else {
$size = 999999999; };
if( $size > 1000000 ) {
$mode = ">";
} else {
$mode = ">>";
}
$fname = "$mode$fn";
open( LOG, $fname ) || die "Died: Opening log file $fname " ;
select(LOG);
$| = 1;
select( STDOUT );
&Log( "-------------------------------" );
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdist) = localtime;
$time = "$mday/$mon/$year $hour:$min:$sec ";
&Log( "NewsInfo server started at $time" );
}
sub f4{
local( $param ) = @_;
$sub = substr( $param, 1, 5 );
return $sub;
}
sub closelog {
($user, $sys ) = times;
$utime = &f4($user);
$stime = &f4($sys);
&Log( "Used $utime CPU seconds and $stime system seconds" );
close( LOG );
}
sub ReadTheIndex {
&Log( "Reread index, old was $indexage, latest is $nowindexage.\n" );
$indexage = $nowindexage;
&flush(1);
open( INX, "$OurDir/index" );
while( <INX> ) {
( $g, $place ) = /(\S+)\s+(\d+)/;
$index{ $g } = $place;
}
close INX;
}
sub readindex{
$nowindexage = -C "$OurDir/index";
if( $nowindexage != $indexage )
{ &ReadTheIndex };
}
sub termhandler {
&Log( "Server killed, exiting" );
&Log( "---------------------------------" );
exit(1);
}
sub childhandler {
&Log("Child @_[0] died" );
wait;
}
sub TrueServer {
$SIG{ 'TERM' } = 'termhandler';
$SIG{ 'CHLD' } = 'childhandler';
$AF_INET = 2;
$SOCK_STREAM = 1;
$sockaddr = 'S n a4 x8';
($name, $aliases, $proto) = getprotobyname('tcp');
$this = pack($sockaddr, $AF_INET, $our_port, "\0\0\0\0" );
select(NS); $| = 1; select( STDOUT );
socket( S, $AF_INET, $SOCK_STREAM, $proto) || die "bad socket: $!";
bind(S, $this) || die "Bad bind: $!";
listen(S,5) || die "listen failed: $!";
select(S); $| = 1; select( STDOUT );
$con = 0;
&Log( "NewsInfo server starting" );
&Log( "Listening on port $our_port" );
for(;;) {
$noconn = 1;
while( $noconn ) {
$addr = accept( NS, S );
if( $addr )
{ $noconn = 0; }
else {
$noconn = 1;
$accerr = $! + 0;
if( $accerr != $EINTR ) { die "accept failed: $!";}
}
}
$con++;
&readindex;
$pid = fork();
if( $pid != 0 )
{
close( NS );
$child[ $con ] = $pid;
&refresh;
}
else
{
($af, $our_port, $inetaddr) = unpack( $sockaddr, $addr );
@inetaddr = unpack( "C4", $inetaddr );
&Log( "Connection from: @inetaddr" );
&server( NS, NS );
close( NS );
exit;
}
}
}
sub main {
&openlog;
if( $using_inetd )
{ &server( STDIN, STDOUT ); }
else
{ &TrueServer };
&closelog;
}
&main;