#!/usr/local/bin/perl

($prog) = ($0 =~ /([^\/]*)$/);        

$first = "lpmh";		# first entry
$src = "/etc/printers";		# source dir
$dst="zone.pcap";		# destination file
$cap = "/export/local/share/pcap"; # printcap fallback directory
$user = "plp";                  # user to create printcap files etc as
$debug = 0;
$sep = "/";                     # separator character

$domain = $ENV{HES_DOMAIN};	# domain name

$udpmax = 512;			# max data size
$hdrsize = 12;			# DNS overhead
$qrysize = 4;			# query size (including name terminator)
$rrsize = 12;			# overhead per RR with compressed domain name

# set options
while ($_ = shift(@ARGV)) {
   if (/^-f(.*)/)  { $first = $1; }
   elsif (/^-s(.*)/) { $src = $1; }
   elsif (/^-d(.*)/) { $dst = $1; }
   elsif (/^-p(.*)/) { $cap = $1; }
   elsif (/^-u(.*)/) { $user = $1; }
   elsif (/^-D(.*)/)   { $debug = $1+0; }
   else {
      print STDERR "$prog: unrecognised option $_\n";
      exit 1;
   }
}

# alphabetical sort routine
sub alpha {
   $a cmp $b;
}

# debug print; to $1 if debug flag not set
sub print {
   local($handle) = shift;
   if ($debug) {
      print STDERR @_;
   } else {
      print $handle @_;
   }
}

# length of resource record containing data
sub rrlen {
   local($data) = shift;
   length($data)+1+$rrsize;
}

# make hesiod record from entry $2, with key $1
sub hesiod {
   local($key) = shift;
   local(@entry) = split('\n', shift);
   local($len) = 0;
   local($seq) = "A";
   local($this) = $key;
   local($next) = "$key$sep$seq";

   while ($_ = shift(@entry)) {
      local($maxsize) = $udpmax - $hdrsize - $qrysize -
	 length("$this.pcap.$domain..");
      if ($_ eq "+") {		# sequencing record
	 &print(OUT, "$this HS TXT \"+$next\"\n");
	 $len = 0;
	 $this = $next;
	 $next = $key . $sep . ++$seq;
      } elsif ($len+&rrlen($_) > $maxsize ||
	       ($len+&rrlen($_)+&rrlen("+$next") > $maxsize && @entry)) {
	 &print(OUT, "$this HS TXT \"+$next\"\n");
	 $this = $next;
	 $next = $key . $sep . ++$seq;
	 $len = &rrlen($_);
	 &print(OUT, "$this HS TXT \"$_\"\n");
      } else {
	 $len += &rrlen($_);
	 &print(OUT, "$this HS TXT \"$_\"\n");
      }
   }
}

# output an entry $1 to printcap file
sub printcap {
   local($pc) = join('', grep(/^[^\#]/, split('\n', shift)));
   local($head, $pc) = ($pc =~ /(^[^:]*:)(.*)/);
   local(@pc) = ($head);
   local($line);

   if ($head =~ /^:/) {		# ignore augmentation entries
      return;
   }

   while ($pc ne "") {		# divide into line of 80 characters
      $line = "";
      while ($pc ne "") {
	 ($head, $pc) = ($pc =~ /(^[^:]*:)(.*)/);
	 if (length($line) && length($line.$head) > 70) {
	    $pc = $head.$pc;
	    last;
	 }
	 $line .= $head;
      }
      push(@pc, $line);
   }
   &print(OUT, join("\\\n\t:", @pc), "\n");
}

open(IN, $src) || die "can't open printers file $src";

%pc = ();			# printcaps associated with keys
@hide = ();			# printers to hide from printers key
@hosts = ();			# all printer hosts
@printers = ();			# list of all printers encountered
@keys = ();			# keys into %pc array
%perms = ();			# permissions associated with keys
@perms = ();			# permissions keys

while (<IN>) {
   chop;			# remove newline
   if (/^\s*\#/ || /^\s*$/) {	# ignore comment or blank
      next;
   } elsif (/^!hide (.*)/) {	# add names to hidden list
      push(@hide, split(' ', $1));
   } elsif (/^permissions (.*)/) { # permissions record
      @aliases = split(' ', $1); # split names
      push(@perms, @aliases);	# shouldn't need sort & uniq
      $perm = "";		# clear record
      $type = -2;		# last record type
      while (<IN>) {
	 chop;
	 if (/^\s*$/) {		# quit loop if blank line
	    last;
	 } elsif (/^\#/) {	# ignore if comment
	    next;
	 } else {
	    s/\s*\#.*$//;	# remove comments
	    if (($index = index("!~", substr($_, 0, 1))) != $type) {
	       $perm .= "+\n" if $type != -2;
	       $type = $index;
	    }
	    $perm .= $_ . "\n";	# append to permissions entry
	 }
      }
      if ($debug > 2) {
	 print STDERR "Permissions for ", join(", ", @aliases), "\n", $perm;
      }
      grep(($perms{$_} = $perm), @aliases); # set permissions for aliases
   } else {			# start of printcap record
      @aliases = split;	# split line
      $printer = shift(@aliases); # remove printer
      push(@printers, $printer); # add printer to list; sort & uniq later
      push(@hosts, @aliases);	# add hosts to list; sort & uniq later
      grep(push(@keys, "$printer.$_"), @aliases);
      if ($debug > 2) {
	 print STDERR "Record for printer $printer on ",
	 join(", ", @aliases), "\n";
      }
      @record = ();		# clear hesiod entry
      $pc = "";		# clear printcap entry
      while (<IN>) {
	 chop;			# remove newline
	 if (/^\s*$/) {	# quit loop if blank line
	    last;
	 } elsif (/^\#/) {	# add comment line to hesiod entry
	    next;		# push(@record, $_);
	 } else {
	    s/\\$//;		# remove trailing backslash
	    $pc .= $_;		# append line to printcap entry
	 }
      }
      $pc =~ s/\s*:\s*:?/:/g;	# remove spare colons and whitespace
      # Extract the first entry, which is the list of names for the printer
      ($printername) = split(/:/,$pc,1);
      @printernames = split(/\|/,$printername);
      if(!grep($_ eq $printer,@printernames)) {
	 printf STDERR ("Warning: %s is not among the known names for %s\n",
			$printer, $printernames[0]);
      }
      local($maxsize) = $udpmax - $hdrsize - $rrsize - $qrysize -
	 length("$printer.xxxxxxxx.X.pcap.$domain");
      while (length($pc) > $maxsize) { # split record up
	 for ($head = $pc; length($head) > $maxsize; $rest = $tail.$rest) {
	    ($head, $tail) = ($head =~ /^(.*)(:[^:]*)$/);
	 }
	 push(@record, $head);
	 $pc = $rest;
	 $rest = "";
      }
      push(@record, $pc);
      if ($debug > 2) {
	 print STDERR join("\n", @record), "\n";
      }
      # Record the printcap entry under the appropriate key
      foreach $alias (@aliases) {
	 $pc{"$printer.$alias"} = join("\n", @record);
	 # For remote entries, add any alternative printer names
	 # (But miss out any containing whitespace!)
	 # Beware that this could lead to overlength records - should
	 # really do the length calculation on the longest name.  Fix
	 # that sometime.
	 if($alias eq "remote") {
	    foreach $pname (@printernames) {
	       if($pname ne $printer && $pname !~ /\s/) {
	          $pc{"$pname.remote"} = join("\n", @record);
		  # Add name to list of keys, but don't add it to list of printers
		  push(@keys,"$pname.remote");
	       }
	    }
	 }
      }
   }
}

close(IN);

$last = "";			# initialise last item tag
@printers = grep($_ ne $last && ($last = $_), sort alpha @printers);
@printers = (grep($first eq $_, @printers), grep($_ ne $first, @printers));
$last = "";			# initialise last host tag
@hosts = grep($_ ne $last && $_ ne "remote" &&
	      $_ ne "servers" && ($last = $_), sort alpha @hosts);
if ($debug > 1) {
   print STDERR "Hosts=", join(", ", @hosts), "\n\n";
   print STDERR "Printers=", join(", ", @printers), "\n\n";
}

# now output file
if (!$debug) {
   open(OUT, ">$dst") || die "can't open output file\n";
}

foreach (@keys) {		# output entries
   ($printer, $alias) = /(.*)\.(.*)/; # match printer and host
   if ($alias eq "remote") {	# remote entry just uses printer name
      &hesiod($printer, $pc{"$printer.$alias"});
   } elsif ($alias eq "servers") { # server entry overrides non-local entries
      foreach $alias (@hosts) {
	 if (!defined($pc{"$printer.$alias"})) { # check if local entry
	    ($host) = gethostbyname($alias); $host =~ s/\..*//;
	    &hesiod("$printer$sep$host", $pc{"$printer.servers"});
	 }
      }
   } else {			# local entry uses real host name
      ($host) = gethostbyname($alias); $host =~ s/\..*//;
      &hesiod("$printer$sep$host", $pc{"$printer.$alias"});
   }
}

foreach $alias (@perms) {	# output permissions
   if ($alias eq "remote") {	# remote entry just uses permissions
      &hesiod("printer_perms", $perms{$alias});
   } elsif (grep($_ eq $alias, @hosts)) { # host permission file
      ($host) = gethostbyname($alias); $host =~ s/\..*//;
      &hesiod("printer_perms$sep$host", $perms{$alias});
   } else {			# printer permission file
      &hesiod("printer_perms$sep$alias", $perms{$alias});
   }
}

@visible = @printers;		# add printers backpointers
foreach $hidden (@hide) {
   @visible = grep($hidden ne $_, @visible);
}
&hesiod("printers", join("\n", @visible)); # not hidden

if (!$debug) {
   close(OUT);
}

exit 0 if ($cap eq "");

# now create printcap fallback files
%cap = ();			# mark which printcap files were created
%local = (remote, remote);	# host -> alias list
foreach (@hosts) {		# create reverse lookup list host -> aliases
   ($host) = gethostbyname($_); $host =~ s/\..*//;
   $local{$host} = join(',', $_, split(',', $local{$host}));
}

# change to PLP before doing these
if ($< == 0) {			# Invoked as root
   ($uid, $gid) = (getpwnam($user))[2,3]; # get uid & gid
   $> = $uid; $) = $gid;		# reset user
   if ($debug) {
      print STDERR "UID=$uid GID=$gid EUID=$> EGID=$) RUID=$< RGID=$(\n";
   }
}

foreach $alias ("remote", @hosts) {
   if ($alias ne "remote") {
      ($host) = gethostbyname($alias); $host =~ s/\..*//;
      $capname = "printcap.$host";
   } else {
      $capname = "printcap";
      $host = "remote";
   }
   if (defined($cap{$capname})) {
      next;
   }
   $cap{$capname} = 1;
   if ($debug > 1) {
      print STDERR "Printcap file=$capname\n";
   }
   if (!$debug) {
      open(OUT, ">$cap/$capname");
   }
   foreach $printer (@printers) {
      foreach $type (split(',', $local{$host}), servers, remote) {
	 if (defined($pc{"$printer.$type"})) { # local/server/remote entry
	    &printcap($pc{"$printer.$type"});
	    last;
	 }
      }
   }
   if (!$debug) {
      close(OUT);
   }
}

# create permissions files
foreach $alias (@perms) {	# output permissions
   if ($alias eq "remote") {	# remote entry just uses permissions
      $name = "$cap/printer_perms";
      $cap{printer_perms} = 1;
   } elsif (grep($_ eq $alias, @hosts)) { # host permission file
      ($host) = gethostbyname($alias); $host =~ s/\..*//;
      $name = "$cap/printer_perms.$host";
      $cap{"printer_perms.$host"} = 1;
   } else {			# printer permission file
      if (defined($pc{"$alias.remote"}) &&
	  $pc{"$alias.remote"} =~ /xu=([^:]*)/) {
	 $name = $1;
      } else {
	 next;
      }
   }
   if ($debug > 1) {
      print STDERR "Permissions file=$name\n";
   }
   if (!$debug) {
      open(OUT, ">$name");
   }
   $perms{$alias} =~ s/\+\n//g;
   &print(OUT, $perms{$alias});
   if (!$debug) {
      close(OUT);
   }
}

opendir(CAP, $cap);		# remove redundant printcap files
foreach $name (grep(/^(printcap|printer_perms)/, readdir(CAP))) {
   if (!defined($cap{$name})) {
      if (!$debug) {
	 unlink("$cap/$name") || warn "can't unlink $cap/$name\n";
      } else {
	 print STDERR "unlink($cap/$name)\n";
      }
   }
}
closedir(CAP);

# change back to root
$> = $<; $) = $(;

0;
