Subject: Script to automate your local veronica menu (maltshop-0.2).
From: Steven Foster

#!/usr/local/bin/perl
#
# Copyright (C) Steven Foster  1994
#
# You are welcome to modify this software in any way you desire, for 
# your use or for use AT YOUR SITE.  However, you MAY NOT redistribute
# any modified forms of this program.  To maintain consistency, please 
# forward your changes, improvements and suggestions to me
# ( [email-masked] ).
#
# DISCLAIMER
# No warranty is expressed or implied that this software will be suitable
# for any particular purpose, or that it will work at all. This source
# code is supplied "as-is", and you should ascertain that it is suitable
# for your purpose before running it.  Neither I nor any other person or
# institution assumes any responsibility or liability for any undesirable 
# consequences of the use of this software.
#
# ----------------   What is this program?   -------------------------
#
#  This script will build a veronica-access menu on your local gopher server.
#  This does not mean you will run a veronica server of your own; rather, you
#  will have a locally-held menu of veronica servers.  The menu will be 
#  updated automatically, to show only those veronica servers currently 
#  operating.
#
#  Reasons to have a local veronica menu:
#
#	1.  Your users will have veronica access in spite of downtime at 
#	    Nevada or Minnesota.
#
#       2.  The veronica access menus at Nevada and Minnesota
#           are getting too busy.  Everyone's response time will be 
#           better if gopher sites have locally-held links to the veronica
#           servers.
#
#       3.  You have the most up-to-date menu.  You can configure this
#           program to check all veronica servers as often as you like.
#           If a veronica server is down, or too slow, it will not appear
#           on your menu.
#
#  What it does:
#
#       This script runs as a cron job.  It polls a list of known, public 
#       veronica servers, determining which are reachable.  It then builds a 
#       link-file in the data heirarchy of your gopher server, at a location 
#       you specify.  You should specify a folder called something like:
#       "Search GopherSpace using veronica".  
#       We recommend that you set the crontab to run the script every 
#       twenty minutes or so.
#
#       Periodically, the script will get a new, updated list of public
#       veronica servers from an authoritative site.  You can set this 
#       update frequency to your liking.  The program is shipped with a 
#       setting for six-day intervals.  New veronica servers do not come 
#       online all that often, so this is probably often enough.
#       You can set the update interval ( in days ) by editing a variable 
#       at the top of the program file.
#
#       This program needs no maintenance.  After you have edited the
#	configuration variables, it will run by itself.
#
#  INSTALLATION:
#
#	The script has been tested with perl-4.036 on Ultrix, Solaris,
#	and NeXT-OS.  
#
#	Install this script in a convenient location, for instance as
#	/usr/local/bin/mkvermenu.  Edit the crontab file to run the
#	script at about 20 minute intervals.  A sample crontab entry is:
#
#       18,38,58 * * * * root /usr/local/bin/mkvermenu
#
#	There is some variation is crontab formats for various systems.
#	Check the format to be sure.
#
#	Edit the configuration options at the beginning of the program.
#	You need to specify a file name where the script can keep its
#	list of known servers, and a directory and filename for the 
#       file of gopher links that constitute the veronica menu. The latter
#	directory and file must be in your gopher server's data heirarchy,
#	at the location where you want the veronica menu to appear.
#	Both these files must be writable by the PID that executes the 
#	script.  If you run as root, no problem.
#	Also, specify a writable tmp directory for the script to use.
#
#	The remaining configuration variables are simple.  
#	A maximum of eight server entries is enough, since each server 
#	needs two lines on the menu, one for item searches and one for 
#	directory-only searches.  
#	Leave the six-day update period as is, unless you have a 
#	good reason to change it.
#	There is an option that allows you to turn off the presentation
#	of the search-gopher-directories items on the menu, should you
#	want to do that.
#	
# --------------------------------------------------------------------
# ---   Set the following paths and options for your system  ---------
#
# Full pathname for the local list of known veronica servers.
# If not present, this file will be created when program is installed.
# NOTE:  This program must have write permission to this file.
$veronicaList = '/usr/local/etc/veronica.sites';

# Directory (in your gopher menu) where you want to put the
# list of veronica servers.
# NOTE:  This program must have write permission to this directory.
$VeronicaDir   = '/usr/local/gopher-data/veronica';

# Name for the file of links that will be built in $VeronicaDir.
# NOTE:  This program must have write permission to this file.
$LinkFile = '.Links-veronica';

# readable/writable tmp directory for use by this script.
$TmpDir = '/tmp';

# Maximum number of servers you want to appear on your menu:
$MaxServers = 8;

#  Interval in days between auto-updating the veronica site list
#  This MUST be at least one.
$UpdateInterval = 6;

#  Include "Search gopher directories" searches on the links menu.
#  Set to 0 to omit directory-type searches from your menu.
$Include_gopher_dir_srch = 1;

#  Include menu items for two veronica documents on the Nevada menu.
#  Set to 0 to skip them....
$Include_docs = 1;

# Set this to see program's progress
$Debug = 0;

# -------------  End of configuration options  ---------------------------
# -------------- Don't edit the stuff below.   ---------------------------

$master_veronica_list = "veronica.scs.unr.edu  70 0/veronica/.veronica.sites\n";
$master_veronica_list2 = "gopher.tc.umn.edu  70 0/Other Gopher and Information Servers/Veronica/.veronica.sites\n";

&Initialize;
if ( ! ( $#verList >= 0 ) ) {  exit; }
if ($Debug){ print "BEFORE connect:\n"; }
if ($Debug){ print "   servers are: \n\r @verList \n"; }
#  Try the veronica servers
&Connect;

#  make the file of links
&Write;

#  move file of links into place on gopher menu
$cmd = "cp $TmpDir/verlinks.tmp  $VeronicaDir/$LinkFile";
system($cmd);
unlink ("$TmpDir/verlinks.tmp");
$rm_cache_cmd = "rm $VeronicaDir/.cac*";
system ("$rm_cache_cmd");
exit;

#==========================================================================
sub Initialize
{
    $SIG{'INT'} = 'IGNORE';
    $SIG{'ALRM'} = 'Alarm';
    require 'sys/socket.ph';
    $Date = `date`;
    chop ($Date);

    ($Name, $Aliases, $Proto) = getprotobyname('tcp');
    
    push ( @Listhosts, $master_veronica_list );
    push ( @Listhosts, $master_veronica_list2 );
    $sockaddr = 'S n a4 x8';

# read list of known veronica servers
    open (veronicas, "< $veronicaList");
    while (<veronicas>)
    {
      if ( /^\#/ )  {  next;  }
      if ( /^\+/ ) {  s/^\+\s*//; push ( @Listhosts, $_ ); next;  }
      if ( /^\!/ ) {  s/^\!\s*//; push ( @verList,  $_);  }
    }
    close (veronicas);

# open temp file for new links list.
    open (Link, "> $TmpDir/verlinks.tmp");
    select (Link); $| = 1; select (STDOUT);
    print Link "# This file automatically created by crontab on\n";
    print Link "# $Date\n"; 

#  Is it time to get a new master list of veronica sites?

#  If no file of links, this is probably first startup, so get new list
	if ( ! (-e "$VeronicaDir/$LinkFile" ))
	{ &UpDateHostList; return;  }

#  If no veronica sites file, get new list
	if ( ! (-e "$veronicaList" ))
	{ &UpDateHostList;  return;    }

#  If no servers were found in the veronica sites file, get new list
	if ( ! ( $#verList >= 0 ) ) 
	{ &UpDateHostList;  return;    }

#  If sites file is too old, get new list
	if ( $UpdateInterval < 1 ) { $UpdateInterval = 6; }
	if ( ( -M $veronicaList) >= $UpdateInterval )
	{ &UpDateHostList;  return;}

}  # end initialize
# ----------------------------------------------------------------------

sub UpDateHostList
{
foreach $host ( @Listhosts )
{
	( $Lhost, $Lport, $Lpath ) = split (  /\s+/, $host, 3);
	if ($Debug) { print "list host:  $Lhost  $Lport  $Lpath"; }
	chop ( $Lpath );
	$! = '';
	if ( &OpenServer ( $Lhost, $Lport ) )
	{
	if ( !$! )
	{
		print SERVER "$Lpath\r\n";
		@NewList = <SERVER>; 
		close (SERVER);
#  return must be at least two lines long
		if (  !( $#NewList >= 1) ) { next; } 
#   line format must be ok
    		$BADFORMAT = 0;
    		foreach   (@NewList)
    		{
      		s/\r//;
      		if ( /^\#/ )  {  next;  }
      		if ( /^\./ )  {  next;  }
      		if ( /^\s*$/ )  {  next;  }
      		if ( /^\+/ ) {  s/^\+\s*//; push ( @NewListhosts, $_ ); next;  }
      		if ( /^\!/ ) {  s/^\!\s*//; push ( @NewverList,  $_); next;  }
      		$BADFORMAT = 1;
    		}
	
    		if ($BADFORMAT)  { next; }

		# Merge old and new lists of site-file-servers
		foreach $a ( @NewListhosts )
		{ 
		  ( $NLhost, $NLport, $NLpath ) = split (  /\s+/, $a, 3);
		  chop ($NLpath);
		  $HL{"$NLhost\t$NLport\t$NLpath"} = $a;
		}
		foreach $a ( @Listhosts )
		{ 
		  ( $NLhost, $NLport, $NLpath ) = split (  /\s+/, $a, 3);
		  chop ($NLpath);
		  $HL{"$NLhost\t$NLport\t$NLpath"} = $a;
		}
 if ($Debug) { print "writing new veronica sites list\n"; }
		open ( TMPLIST, ">$TmpDir/newhostlist.tmp" );
		print TMPLIST "# File $veronicaList created on: \n"; 
    		print TMPLIST "#     $Date\n"; 
    		print TMPLIST "# Using veronica site data from:\n"; 
    		print TMPLIST "#     $Lhost  $Lport  $Lpath\n"; 
		print TMPLIST "# Do not change the format of lines in this file;\n";
		print TMPLIST "# the special characters are required by the program\n";
		print TMPLIST "#\n";
		# write list of site-file-servers
		foreach $uniq ( keys ( %HL) )
		{ print TMPLIST "+$HL{$uniq}";  }
		# write list of veronica servers
		foreach ( @NewverList  )
		{ print TMPLIST "!$_";  }
		close (TMPLIST);
		#  working list of veronicas is now new list
		undef @verList;
		@verList = @NewverList;
		#  here copy tempfile to real sites file.
		$cmd = "cp $TmpDir/newhostlist.tmp $veronicaList";
		system($cmd);
		unlink ("$TmpDir/newhostlist.tmp");
		last;
	}
	}
	close ( SERVER );
}  
}

sub OpenServer
{
    local ( $server, $port ) = @_;
    $sockaddr = 'S n a4 x8';
    (($name, $aliases, $type, $len, $saddr) = gethostbyname($server))
      || do {warn "Bad gethostbyname!\n";  return (0); };
    $sin = pack($sockaddr, &AF_INET, $port, $saddr);
    socket(SERVER, &AF_INET, &SOCK_STREAM, $Proto) 
      || do {warn "Bad socket call!\n";  return (0);};
    connect ( SERVER, $sin) 
      || do {warn "Bad connect call!\n";  return (0);};
    select (SERVER); $| = 1; select (STDOUT); $| = 1;
    return (1);
}

sub Connect
{
    $Slimit = ( $#verList + 1 ) - $MaxServers;
    if ($Slimit < 0) { $Slimit = 0; }
    while  ( $#verList >= $Slimit )
    {    
    	alarm ('40');
        $Line =  shift(@verList);
        chop $Line;
	if($Debug){ print "Now call $Line \n"; }
        next unless ($Line);
        
           ($Host,$Port,$Description,$Version) = split (/\s*\!\s*/, $Line, 4);
           ($Name, $Aliases, $Type, $Length, $RHostAddr) = 
                gethostbyname($Host);
    
           $RHostSocketAddr = pack ($sockaddr, &AF_INET, $Port, $RHostAddr);
    
           socket (S, &AF_INET, &SOCK_STREAM, $Proto) 
                    || warn "Can't create socket: $!\n";
           #bind (S, $HostSocketAddr)
           #        || warn "Can't bind socket: $!\n";
     
           $! = '';
           connect (S, $RHostSocketAddr);
           if (!$!)
           {
               select (S); $| = 1; select (STDOUT);
               print S "-m?\r\n";
               $Reply = <S>; 
               if ($Reply =~ /^.*\t.*\t.*\t\d+/)
               {
                    push (@veronicas, "$Host $Port $Description"); 
               }
            }
            else
            {
                print Link "# Can't connect to $Description: $!\n";
            } 
    }
    alarm(0);
}

sub Write
{
    $Numb = 0;
    if ( $Include_docs )
    {
        $Numb++;
        print Link "Type=0\n";
        print Link "Name=veronica FAQ (from Nevada)\n";
        print Link "Path=0/veronica/veronica-faq\n";
        print Link "Host=veronica.scs.unr.edu\n";
        print Link "Port=70\n";
        print Link "Numb=$Numb\n";
        print Link "#\n";
        $Numb++;
        print Link "Type=0\n";
        print Link "Name=How to compose veronica queries (from Nevada)\n";
        print Link "Path=0/veronica/how-to-query-veronica\n";
        print Link "Host=veronica.scs.unr.edu\n";
        print Link "Port=70\n";
        print Link "Numb=$Numb\n";
        print Link "#\n";
    }
    foreach (@veronicas)
    {
        next unless ($_);
        $Numb++;
        ($Host, $Port, $Description) = split (/\s+/, $_, 3);
        print Link "Type=7\n";
        print Link "Name=veronica server at $Description\n";
        print Link "Path=\n";
        print Link "Host=$Host\n";
        print Link "Port=$Port\n";
        print Link "Numb=$Numb\n";
        print Link "#\n";
    }
    if ( $Include_gopher_dir_srch )
    {
    foreach (@veronicas)
    {
        next unless ($_);
        $Numb++;
        ($Host, $Port, $Description) = split (/\s+/, $_, 3);
        print Link "Type=7\n";
        print Link "Name=Search Gopher Directory Titles at $Description\n";
        print Link "Path=-t1  \n";
        print Link "Host=$Host\n";
        print Link "Port=$Port\n";
        print Link "Numb=$Numb\n";
        print Link "#\n";
    }
    }
    close (Link);
}

sub Alarm
{
    alarm (0);
    print Link "# Alarm $Description\n";
    &Connect;
}