#!/usr/local/bin/perl
# gophertree v1.0
# Prints pretty indented listings of a Gopher menu tree
# Copyright (C) 1992, Trustees of Michigan State University
# 
# Modifications:
# Original author unknown
# 07/07/92 Boone      Major conversion from gopherls
# 08/14/92 Boone      Fixes: 
#                        added code to allow command line limit on recursion 
#                           depth
#                        quit indenting after 15 levels to avoid filling the
#                           title field with spaces
#                        changed to use IP address instead of hostname when
#                           checking for off-host links; this should make
#                           aliased machine names (e.g. gopher.someschool.edu)
#                           work much better
#                     Enhancements:
#                        Added option to list only directories
#                        Changed limit on number of items listed to apply to
#                           all types except directories, instead of just
#                           files; still no per-type limits though
#                        Changed command line processing to use Getopts, 
#                        allowing better option processing
# End Modifications

require "getopts.pl";

sub dokill
{
    kill 9,$child if $child;
}

sub Opengopher
{
    $sockaddr++;
    local($them,$port) = @_;	
    $them = 'localhost' unless $them;

    $AF_INET = 2;
    $SOCK_STREAM = 1;

    $SIG{'INT'} = 'dokill';

    $sockaddr = 'S n a4 x8';

    chop($hostname = `hostname`);

    ($name,$aliases,$proto) = getprotobyname('tcp');
    ($name,$aliases,$port) = getservbyname($port,'tcp')
	unless $port =~ /^\d+$/;;
    ($name,$aliases,$type,$len,$thisaddr) = gethostbyname($hostname);
    ($name,$aliases,$type,$len,$thataddr) = gethostbyname($them);

    $this = pack($sockaddr, $AF_INET, $sockaddr, $thisaddr);
    $that = pack($sockaddr, $AF_INET, $port, $thataddr);

    # Make the socket filehandle.
    socket(S, $AF_INET, $SOCK_STREAM, $proto) || die $!;

    # Give the socket an address.
    bind(S, $this) || die $!;

    # Call up the server.
    connect(S,$that) || die $!;

    # Set socket to be command buffered.
    select(S); $| = 1; select(STDOUT);

}

sub GetList 
{
	local($CurrentHost, $Port, $Path, $indent) = @_;
	local(@dirx, $Name, $Obj, $fname, $ftype, $fhost, %i, $truncated);
	&Opengopher($CurrentHost, $Port);
	print S "$Path\n";
	@dirx = <S>;
	close(S);
	$truncated = 0;
	foreach (@dirx) 
	{
		last if /^\./;
		chop; chop;
		($ObjName, $Path, $CurrentHost, $Port) = split('\t', $_);
		$Name = substr($ObjName, 1);
		$Obj = substr($ObjName, 0, 1);
		$fname = $indent . $Name;
		$ftype = "";
		$ftype = "File" if ($Obj eq "0");
		$ftype = "Dir" if ($Obj eq "1");
		$ftype = "Phone" if ($Obj eq "2");
		$ftype = "Error" if ($Obj eq "3");
		$ftype = "MacHqx" if ($Obj eq "4");
		$ftype = "PcHqx" if ($Obj eq "5");
		$ftype = "Uue" if ($Obj eq "6");
		$ftype = "Index" if ($Obj eq "7");
		$ftype = "Telnet" if ($Obj eq "8");
		$ftype = "Bin" if ($Obj eq "9");
		$ftype = "File" if ($Obj eq "R");
		$ftype = "TN3270" if ($Obj eq "T");
		$ftype = "File" if ($Obj eq "e");
		$ftype = "Ftp" if ($Obj eq "f");
		$ftype = "HTML" if ($Obj eq "h");
		$ftype = "Info" if ($Obj eq "i");
		$ftype = "Mail" if ($Obj eq "m");
		$ftype = "Sound" if ($Obj eq "s");
		$ftype = "Index" if ($Obj eq "w");
		$fhost = $CurrentHost;

		$writeme = 1;
		if ((! $opt_d) && ($Obj ne "1") && ($i{$Obj} > $breaklong))
		{
			$writeme = 0;
			$truncated = 1;
		}
		if ($Obj eq "i")		{ $writeme = 0; }
		if ($opt_d && ($Obj ne "1"))	{ $writeme = 0; }
		if ($writeme)			{ write; }

		if ($hostable{$CurrentHost} eq "")
		{
			$hostable{$CurrentHost} =
				unpack("L", (gethostbyname($CurrentHost))[4]);
		}

		if (($Obj eq "1") && 
			($hostable{$CurrentHost} eq $hostable{$firsthost}) && 
			(($Port != $firstport) || ($Path != ""))) 
		{
			$newindent = $indent;
			if ($depth < $nomoreindent)
			{
				$newindent .= $iadd;
			}
			$depth++;
			if ($depth <= $maxdepth)
			{
				&GetList($CurrentHost, $Port, 
					$Path, $newindent);
			}
			$depth--;
		}

		$i{$Obj}++;
	}
	if ($truncated) 
	{
		print "$indent    . . .\n";
		--$-;
	}
}

# **************************************************************************
# * Main
# **************************************************************************

# Parse command line

	&Getopts("b:l:dr:");
	if ($#ARGV < 1) 
	{
		print "Usage: gophertree [-d -bn -ln -rn] host port path\n";
		exit(1);
	}

	$firsthost = $CurrentHost = $ARGV[0];
	$firstport = $Port = $ARGV[1];
	$Path = "";
	if ($#ARGV == 2) 
	{
		$Path = $ARGV[2];
	}

# Initialize some variables

	($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
	$today = $mon+1 . "/" . $mday . "/" . $year;

	$^ = 'TOP';         # Report header format
	$~ = 'STDOUT';      # Report body format
	$indent = "";       # Goes in front of description to indicate treeness
	$depth = 1;         # How deep into the maze are we?
	$hostable{$firsthost} = unpack("L", (gethostbyname($firsthost))[4]);

# CHANGE--User-configurable defaults

	$breaklong = 15;    # CHANGE--Where to break long lists
	$= = 55;            # CHANGE--Lines per page
	$iadd = "  ";       # CHANGE--Amount to indent each new level
	$nomoreindent = 15; # CHANGE--How far down before we quit indenting
	$maxdepth = 999;    # CHANGE--How deep to go before "pruning" layers

# Stuff command line changes into the config variables

	if ($opt_b) { $breaklong = $opt_b; }
	if ($opt_l) { $= = $opt_l; }
	if ($opt_r) { $maxdepth = $opt_r; }

# Real work

	&GetList($CurrentHost, $Port, $Path, $indent);
	exit(0);

#
# Formats
#

format TOP =

MSU Gopher Road Map                      Host: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$firsthost
Last Update: @<<<<<<<                                               Page: @>>
$today $%

Lev Name                                       Type   Destination
--- ------------------------------------------ ------ -------------------------
.

format STDOUT =
@>> @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<
$depth $fname $ftype $fhost
.
