#!/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 # # grg@boombox.micro.umn.edu # #### # # 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 minuet@boombox.micro.umn.edu # # ########## ############### # # 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 ($_); $_= ; 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 ( ) { 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( ) { 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, ); "; $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( ) { 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( ) { 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( ) { ( $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;