#!/usr/bin/perl # $Id: exporter 1043 2007-12-19 21:52:34Z jhealy $ =head1 NAME exporter =head1 SYNOPSIS exporter [dns] [dhcp] [tftp] [cups] =head1 ABSTRACT Given a data file of printer information, this script autogenerates files for other services that printing depends upon. =head1 DESCRIPTION This script autogenerates configuration files for services that work with printing. This includes: =over 4 =item DNS We use DNS-SD (aka "Zeroconf" or "Bonjour") to automate the discovery of services on the network, including printers. This script will generate DNS zone information to advertise printers. =item DHCP We configure printers on the network using DHCP. This script will generate DHCP host statements (for ISC's DHCP server) that will configure and identify printers on the network. If TFTP configurations are being used (see below), those will be added to the host statements as well. =item TFTP Printer Configurations HP printers can be configured via DHCP by downloading their configuration from a TFTP server. This script will create configuration files with a base level of settings applied. =item CUPS For clients that don't support DNS-SD (e.g., Mac OS X clients before 10.4), we provide a script that users can run that will manually add the printers to the system. This script will generate a script that will add printers to the local system. =back =head2 DATA FILE FORMAT To generate all the necessary information, we need a data file that provides everything we need. The input data file is simply a valid Perl file containing a hash of all the values we need to generate all the other files. The file must contain a single hash called PRINTERS. Each entry in this hash should consist of the "friendly" name for the printer, associated with anonymous hash of all the values for the printer. Here's a sample of the global hash with a single entry listing each of the possible keys in the inner hash: %PRINTERS = ( # "Friendly" name of printer (shown to end users) "Multimedia Lab" => { # printer's hardware ethernet address macaddr => "de:ad:be:ef:f0:0d", # hostname to assign to the printer (optional, can be auto-generated) hostname => "printer-multimedia-lab-hplj4100n", # physical location of the printer location => "", # Type of printer; shown to user and used to auto-generate other values type => "HPLJ 4100n", # Model of printer from PPD file (use "*Product" line) # used to allow auto-selection of PPD by clients product => "(HP LaserJet 4100 Series )", # Boolean: is printer accessible via HTTP for configuration? web => 1, # Boolean: Does printer accept jobs via IPP? ipp => 1, # Boolean: Does printer accept jobs via LPR? lpd => 1, # MIME types (comma-separated) that the printer accepts on it's custom # port (e.g., PCL). If undefined, assume that only LPR/IPP should be # advertised pdl => "application/vnd.hp-PCL", # Does the printer support duplex printing? duplex => "F", # Does the printer support color printing? color => "T", # Extra key/value pairs to insert into DNS-SD TXT record (optional) dnssdtxt => {} } ); =cut # Be strict about syntax use warnings; use strict; # Master hash of printer data (will be populated from file on disk) my %PRINTERS = (); # Domain to add to unqualified names my $DOMAIN = "gear.suffieldacademy.org"; # Network Administrator contact info my $NETADMIN = 'Network Administrator (netadmin@suffieldacademy.org)'; # Printer contact info my $PRINTER_ADMIN = 'CRC Help Desk'; my $PRINTER_PHONE = 'x4420'; # Default password for printers my $PASSWORD = 'leonard'; if ($#ARGV < 1) { print "Usage:\n"; print "\texporter [dns] [dhcp] [tftp] [cups] \n\n"; exit 1; } # Sanity check on output dir my $output = pop(@ARGV); if ( -e $output ) { die "Output directory already exists; please move it out of the way\n"; } else { mkdir "$output" or die "Couldn't create output dir '$output': $!\n"; } # Read in the specified data file my $datafile = pop(@ARGV); open (PRINTERS, $datafile) or die "Could not open printer data file: $!\n"; eval join("", ); close(PRINTERS); =head2 lint($string) [returns I] Given a string containg a printer name and/or model number, convert it into a DNS- and filesystem-safe string (no spaces, all lower-case, etc). =cut sub lint($;) { my $name = shift(@_); # convert to all lower case $name =~ tr /A-Z/a-z/; # convert spaces and underscores to dashes $name =~ s/\s+/-/g; $name =~ s/_+/-/g; # pave everything else $name =~ s/[^a-z0-9-]//g; return $name; } # end sub lint =head2 hostname($printer) [returns I] Given a single printer table, this method looks up or auto-generates the hostname for the printer. The auto-generated value is only created if an explicit value is not provided, and is built from the name and model of the printer. =cut sub hostname($;) { my $printer = shift(@_); my %table = %{$PRINTERS{$printer}}; if (defined($table{hostname})) { return $table{hostname}; } # otherwise, fall back to auto-generation my $hostname = join("-", ("printer", $printer, $table{type})); # convert to DNS-friendly format $hostname = lint($hostname); # store the new hostname for quicker access next time $PRINTERS{$printer}->{hostname} = $hostname; return $hostname; } # end sub hostname my %PPD_PRODUCTS = (); =head2 hashPPDs() [returns I] Searches through all PPD files to build a hash of PPD -> product information. This allows us to easily find the PPD that goes with a particular model. =cut sub hashPPDs() { foreach my $ppd (glob("cups-ppds/*.ppd")) { open (PPD, $ppd) or warn "Could not open PPD file '$ppd': $!\n"; while (defined(my $line = )) { if ($line =~ /^\*Product:\s*"([^\"]+)"/) { my $model = $1; $PPD_PRODUCTS{$1} = $ppd; last; } } close(PPD); } } # end sub hashPPDs =head2 findPPD($printer) [returns I] Given a single printer table, this method finds the corresponding PPD file for the given model of printer. =cut sub findPPD($) { my $printer = shift(@_); my %table = %{$PRINTERS{$printer}}; # populate PPD hash, if necessary if (keys %PPD_PRODUCTS == 0) { hashPPDs(); } if (defined($PPD_PRODUCTS{$table{product}})) { return $PPD_PRODUCTS{$table{product}}; } # fall back to generic PPD elsif (defined($PPD_PRODUCTS{"(PostScript Printer)"})) { return $PPD_PRODUCTS{"(PostScript Printer)"}; } else { warn "Unable to find PPD for $printer\n"; return ""; } } # end sub findPPD =head2 getTFTPTemplate($printer) [returns I] Given a single printer table, this method finds the corresponding TFTP configuration file template. It does this by first searching for a template that matches the printer's name, and then falls back to finding it based on the printer's model. The template file is read into memory and returned as a string, ready to be eval()-ed. =cut sub getTFTPTemplate($;) { my $printer = shift(@_); my %table = %{$PRINTERS{$printer}}; my $file = "tftp-templates/" . hostname($printer); # first, look for host-specific config if (! -f $file) { # next, try by model $file = "tftp-templates/" . lint($table{type}); if (! -f $file) { warn "No config template available for $printer\n"; return undef; } } # now, read in the file, and add eval wrapper info my $template = '$tftpConfig = << "ENDTFTPCONFIGTEMPLATE"' . "\n"; open (TEMPLATE, $file) or die "Could not open tftp template file '$file': $!\n"; $template .= join("",