
#!/usr/local/bin/perl
#
# switch to nobody if we are root
($<,$>) = (-2,-2) unless $>;

require 'getopts.pl';

#
# Simple gopher2nntp server. You can change the following variables.
# nntp_port is hardcoded here since this is a stateless server and
# no sense in reading it from /etc/services every time...
#
# A server that sticks around might be better...
#
# Note: The NNTP daemon must accept the XHDR command.
#
#
# You should run "gopher2nntp -g" from cron to generate the groups 
# file. If you don't generate the groups file then this server will
# grab it from the NNTP server, but thats a waste of time. 
#
#
  $nntp_server = "leland.stanford.edu";
  $nntp_port   = 119;
  $nntp_groups = "/usr/local/etc/newsgroups.gopher"; # could be active file

  @nntp_acl=(
#     ipaddress  group    access + = allow, - = deny    
     '^36\.      .*       +',
     '.*         ^clari   -',
     '.*         .*       +'
  );

#
# Commands this server responds to:
# 
# ""                         -> list top level groups
# ls $group                  -> list group's articles and sub-groups
# article $group $number     -> get 1 article
# sorry                      -> send sorry message
#

$port=&my_port();         # port this script is running on 
chop($host = `hostname`);

&Getopts('nrdga:s:');

if ($errs || $#ARGV!=-1) {

print<<EOF;

 Usage: $0 [-s server] [-a active_file] [-n] [-g] [-d] [-r]
 -s server      NNTP server to contact
 -a file        Active file to use
 -n             Don't use active file
 -g             generate new active file
 -d             turn on debug (when typing commands from a tty)
 -r             list articles in reverse order
EOF
exit;

}

$nntp_server = $opt_s if (defined($opt_s));
$nntp_port = $opt_p if (defined($opt_p));
$nntp_groups = $opt_a if (defined($opt_a));

&create_groups($opt_g) if defined($opt_g);

  $_ = <STDIN>; s/\r//; s/\n//;

  &do_ls("") if /^$/;
  &do_ls($1) if /^ls\s+(.*)/i;
  &do_article($1,$2) if /^article\s+(\S+)\s+(\d+)/i;
  &do_sorry if /^sorry$/;
  &reply("3Unknown command!");
  exit;

sub do_article {
  local($group,$number) = @_;

  if (&check_access($group) eq '-')  { 
      &do_sorry;
      &reply("."); 
      exit; 
  }

  &open_nntp;

  &send("GROUP $group");
  $_ = &recv;
  &death if !/^211/;

  &send("ARTICLE $number");
  $_ = &recv;
  &death if !/^220/;
 
  while(<SERVER>) {
    print;
    last if /^\.\r\n$/;
  }

  &close_nntp;
  exit;
}

sub list_group {
  local($group) = @_;

  &send("GROUP $group");
  $_ = &recv;
  &death if !/^211/;

  ($n,$f,$l) = /211\s+(\d+)\s+(\d+)\s+(\d+)/;

  &send("XHDR Subject $f-$l");
  $_ = &recv;
  &death if !/^221/;
 
  while(<SERVER>) {
    chop; chop;
    last if /^\.$/;
    ($article,$subject) = /^(\d+)\s+(.*)/;
    $subject =~ s/\t/ /g; # just in case!
    if (defined($opt_r)) {
       push(@reply,"0$subject\tarticle $group $article\t$host\t$port");
    } else {
       &reply("0$subject\tarticle $group $article\t$host\t$port");
    }
  }

  if (defined($opt_r)) { 
    for ($i=$#reply; $i!= -1; $i--) { &reply($reply[$i]); } 
  }

  &reply(".");
  &close_nntp;
  exit;
}


sub do_ls {
  local($prefix) = @_;

  if (&check_access($prefix) eq '-') {
      &reply("0Sorry! No no access off of campus!\tsorry\t$host\t$port");
      &reply("."); 
      exit; 
  }

  &open_nntp;
  &get_groups;

  foreach ( sort @groups) {
    if ($_ eq $prefix) { $do_list_group = $_; }
    elsif (/^$prefix\.([^.]*)\.?/) {
         $leaf=$1;
         $save{"$prefix.$leaf"} = "1$leaf\tls $prefix.$leaf\t$host\t$port";
    }
    elsif ($prefix eq '' && /([^.]*)/) {
         $save{"$1"} = "1$1\tls $1\t$host\t$port";
    }
  }

  foreach ( sort keys %save) { &reply($save{$_}); }
  &list_group($do_list_group) if ($do_list_group);

  &reply(".");
  &close_nntp;
  exit;
}

sub open_nntp {
  local($_);
  &open_server($nntp_server,$nntp_port);
  $_ = &recv;
  &death if !/^2/;
}

sub close_nntp {
  &send("QUIT");
  close(SERVER);
}

sub my_port {
   return -1 if (-t STDIN);
   $sockaddr = 'S n a4 x8';
   $mysockaddr = getsockname(STDIN);
   ($myfamily,$myport,$myaddr) = unpack($sockaddr,$mysockaddr);
   return $myport;
}

sub open_server {

 local($server,$port) = @_;
 $sockaddr = 'S n a4 x8';
 (($name, $aliases, $type, $len, $saddr) = gethostbyname($server))||&death;
 $sin = pack($sockaddr, 2, $port, $saddr);
 socket(SERVER, 2, 1, 0) || &death;
 connect(SERVER, $sin)   || &death;
 select(SERVER); $| = 1; select(STDOUT); $| = 1;

}

sub send     { 
     print "send -> |$_[0]|\n" if (defined($opt_d));
     print SERVER "$_[0]\r\n"; 
}

sub recv { 
   local ($_); 
   $_= <SERVER>; 
   chop; chop;
   print "recv -> |$_|\n" if (defined($opt_d));
   return $_; 
}

sub reply { print "$_[0]\r\n";}
sub death { &reply("."); exit; }

sub get_groups {
 if (!defined($opt_n) && open(GROUPS,$nntp_groups)) {
      while(<GROUPS>) {
          chop;
          ($grp) = /^(\S+)/;
          push(@groups,$grp);
      }
      close(GROUPS);
 } else {                  # can't open file, get list from server!
  &load_groups;
 }
}

sub load_groups {

  &open_nntp;
  &send("LIST");
  $_ = &recv;
  &death if !/^215/;

  while(<SERVER>) {
    chop; chop;
    last if /^\.$/;
    s/^(\S+).*/$1/;
    push(@groups,$_);
  }

}

sub create_groups { 

  &load_groups;
 
  open(GROUPS,">$nntp_groups") || die "$nntp_groups: $!";
  foreach (@groups) { print GROUPS "$_\n"; }
  close GROUPS;

  &close_nntp;
  exit;
}

sub check_access {
   local($group)=@_;

   return 1 if (-t STDIN);
   $sockaddr = 'S n a4 x8';
   $mysockaddr = getpeername(STDIN);
   ($ramily,$rport,$raddr) = unpack($sockaddr,$mysockaddr);
   ($a,$b,$c,$d) = unpack('C4',$raddr);
   $ipaddress = "$a.$b.$c.$d";

   foreach (@nntp_acl) {
      ($ipacl,$groupacl,$access)=split;
      return $access if  ($ipaddress =~ /$ipacl/) && ($group =~ /$groupacl/);
   }
   return '-'; #default is to restrict access
}

sub do_sorry {

print<<EOF;

Sorry! You have selected information that cannot be delivered off
of campus due to restrictions.

   -- The Mole Hole Guardian

EOF

&reply(".");
exit;

}
