Tim Waugh b5b101
#!/usr/bin/perl
Tim Waugh b5b101
# The above Perl path may vary on your system; fix it!!! -*- perl -*-
Tim Waugh b5b101
Tim Waugh b5b101
# dnssd - Search for network printers with the avahi-browse command
Tim Waugh b5b101
#         (Zeroconf, DNS-SD)
Tim Waugh b5b101
Tim Waugh b5b101
# Printer discovery CUPS backend (like the SNMP backend)
Tim Waugh b5b101
# See also http://qa.mandriva.com/show_bug.cgi?id=21812
Tim Waugh b5b101
Tim Waugh b5b101
# Copyright 2007 Till Kamppeter <till.kamppeter@gmail.com>
Tim Waugh b5b101
#
Tim Waugh b5b101
#  This program is free software; you can redistribute it and/or modify it
Tim Waugh b5b101
#  under the terms of the GNU General Public License as published by the
Tim Waugh b5b101
#  Free Software Foundation; either version 2 of the License, or (at your
Tim Waugh b5b101
#  option) any later version.
Tim Waugh b5b101
#
Tim Waugh b5b101
#  This program is distributed in the hope that it will be useful, but
Tim Waugh b5b101
#  WITHOUT ANY WARRANTY; without even the implied warranty of
Tim Waugh b5b101
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
Tim Waugh b5b101
#  Public License for more details.
Tim Waugh b5b101
#
Tim Waugh b5b101
#  You should have received a copy of the GNU General Public License
Tim Waugh b5b101
#  along with this program; if not, write to the Free Software
Tim Waugh b5b101
#  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
Tim Waugh b5b101
#  USA.
Tim Waugh b5b101
Tim Waugh b5b101
# Usage: 
Tim Waugh b5b101
#
Tim Waugh b5b101
# cp dnssd /usr/lib/cups/backend/
Tim Waugh b5b101
# chmod 755 /usr/lib/cups/backend/dnssd
Tim Waugh b5b101
# killall -HUP cupsd (or "/etc/init.d/cups restart", CUPS 1.1.x only)
Tim Waugh b5b101
# lpinfo -v (or use any printer setup tool)
Tim Waugh b5b101
Tim Waugh b5b101
#use strict;
Tim Waugh b5b101
Tim Waugh b5b101
$0 =~ m!^(.*)/([^/]+)\s*$!;
Tim Waugh b5b101
my $progname = ($2 || $0 || "dnssd");
Tim Waugh b5b101
my $progpath = ($1 || "/usr/lib/cups/backend");
Tim Waugh b5b101
Tim Waugh b5b101
if ($ARGV[0]){
Tim Waugh b5b101
    die "This backend is only for printer discovery, not for actual printing.\n";
Tim Waugh b5b101
}
Tim Waugh b5b101
Tim Waugh b5b101
my $avahicmd = "avahi-browse -k -t -v -r -a 2> /dev/null";
Tim Waugh b5b101
Tim Waugh b5b101
# IPs which are for computers, consider their printer entries as queues
Tim Waugh b5b101
# set up with the local printing system (CUPS, LPD, Windows/Samba SMB, ...)
Tim Waugh b0dd15
my @computerips = ();
Tim Waugh b5b101
my $output;
Tim Waugh b0dd15
my $hosts;
Tim Waugh b5b101
my ($interface, $nettype, $ip, $host, $make, $model, $description, $cmd, $makemodel, $deviceid, $protocol, $port, $uriext, $uri);
Tim Waugh b5b101
Tim Waugh b0dd15
open (AVAHI, "$avahicmd |") or exit 0;
Tim Waugh b5b101
while (my $line = <AVAHI>) {
Tim Waugh b5b101
    chomp ($line);
Tim Waugh b5b101
    if ($line =~ /^\s*=\s+(\S+)\s+(\S+)\s+(.*?)\s+(\S+)\s+(\S+)\s*$/) {
Tim Waugh b5b101
	# New item
Tim Waugh b5b101
	$interface = $1;
Tim Waugh b5b101
	$nettype = $2;
Tim Waugh b5b101
	my $itemname = $3;
Tim Waugh b5b101
	my $protocolinfo = $4;
Tim Waugh b5b101
        if ($protocolinfo =~ /_workstation/) {
Tim Waugh b5b101
	    $protocol = "computer";
Tim Waugh b5b101
	} elsif ($protocolinfo =~ /_pdl-datastream/) {
Tim Waugh b5b101
	    $protocol = "socket";
Tim Waugh b5b101
	} elsif ($protocolinfo =~ /_printer/) {
Tim Waugh b5b101
	    $protocol = "lpd";
Tim Waugh b5b101
	} elsif ($protocolinfo =~ /_ipp/) {
Tim Waugh b5b101
	    $protocol = "ipp";
Tim Waugh b5b101
	}
Tim Waugh b5b101
    } elsif ($line =~ /^\s*hostname\s*=\s*\[([^\]]+)\]\s*$/) {
Tim Waugh b5b101
	$host = $1;
Tim Waugh b5b101
	$host =~ s/\.local\.?$//;
Tim Waugh b5b101
    } elsif ($line =~ /^\s*address\s*=\s*\[([^\]]+)\]\s*$/) {
Tim Waugh b5b101
	$ip = $1;
Tim Waugh b5b101
	if ($protocol eq "computer") {
Tim Waugh b0dd15
	    push (@computerips, $ip);
Tim Waugh b5b101
	    $protocol = "";
Tim Waugh b5b101
	}
Tim Waugh b5b101
    } elsif ($line =~ /^\s*port\s*=\s*\[([^\]]+)\]\s*$/) {
Tim Waugh b5b101
	$port = $1;
Tim Waugh b5b101
    } elsif ($line =~ /^\s*txt\s*=\s*\[(.+)\]\s*$/) {
Tim Waugh b5b101
	my $info = $1;
Tim Waugh b5b101
	if ($protocol) {
Tim Waugh b5b101
	    my ($ty, $product, $pdls, $usb_MFG, $usb_MDL, $usb_DES, $usb_CMD) = 
Tim Waugh b5b101
		("", "", "", "", "", "", "");
Tim Waugh b5b101
	    while ($info =~ s/^\s*\"([^\"]+)\"\s*//) {
Tim Waugh b5b101
		my $infoitem = $1;
Tim Waugh b5b101
		if ($infoitem =~ /^([^=]*)=(.*)$/) {
Tim Waugh b5b101
		    my $field = $1;
Tim Waugh b5b101
		    my $content = $2;
Tim Waugh b5b101
		    if ($field eq "ty") {
Tim Waugh b5b101
			$ty = $content;
Tim Waugh b5b101
		    } elsif ($field eq "product") {
Tim Waugh b5b101
			$product = $content;
Tim Waugh b5b101
			$product =~ s/^\((.*)\)$/$1/;
Tim Waugh b5b101
		    } elsif ($field eq "usb_MFG") {
Tim Waugh b5b101
			$usb_MFG = $content;
Tim Waugh b5b101
		    } elsif ($field eq "usb_MDL") {
Tim Waugh b5b101
			$usb_MDL = $content;
Tim Waugh b5b101
		    } elsif ($field eq "usb_DES") {
Tim Waugh b5b101
			$usb_DES = $content;
Tim Waugh b5b101
		    } elsif ($field eq "usb_CMD") {
Tim Waugh b5b101
			$usb_CMD = $content;
Tim Waugh b5b101
		    } elsif ($field eq "rp") {
Tim Waugh b5b101
			$uriext = $content;
Tim Waugh b5b101
		    } elsif ($field eq "pdl") {
Tim Waugh b5b101
			while ($content =~ s/^\s*([^\,]+?)\s*\,\s*//) {
Tim Waugh b5b101
			    my $i = $1;
Tim Waugh b5b101
			    if ($i =~ m!\b(postscript|ps)\b!i) {
Tim Waugh b5b101
				$pdls .= "POSTSCRIPT,";
Tim Waugh b5b101
			    } elsif ($i =~ m!\b(pdf)\b!i) {
Tim Waugh b5b101
				$pdls .= "PDF,";
Tim Waugh b5b101
			    } elsif ($i =~ m!\b(pcl6|pclxl|pxl)\b!i) {
Tim Waugh b5b101
				$pdls .= "PCLXL,";
Tim Waugh b5b101
			    } elsif ($i =~ m!\b(pcl[345][ce]?|pcl)\b!i) {
Tim Waugh b5b101
				$pdls .= "PCL,";
Tim Waugh b5b101
			    }
Tim Waugh b5b101
			}
Tim Waugh b5b101
			$pdls =~ s/\,$//;
Tim Waugh b5b101
		    }
Tim Waugh b5b101
		}
Tim Waugh b5b101
	    }
Tim Waugh b5b101
	    $usb_MDL ||= $ty;
Tim Waugh b5b101
	    $usb_DES ||= $product;
Tim Waugh b5b101
	    if ($usb_MFG) {
Tim Waugh b5b101
		$make = $usb_MFG;
Tim Waugh b0dd15
	    } elsif ($usb_DES =~ /^KONICA\s*MINOLTA\b/i) { 
Tim Waugh b5b101
		$make = "KONICA MINOLTA";
Tim Waugh b5b101
	    } elsif ($usb_DES) {
Tim Waugh b5b101
		$usb_DES =~ /^\s*(\S*)\b/;
Tim Waugh b5b101
		$make = $1;
Tim Waugh b5b101
	    }
Tim Waugh b5b101
	    $model = $usb_MDL;
Tim Waugh b5b101
	    if (!$model) {
Tim Waugh b5b101
		$usb_DES =~ /^\s*\S*\s*(.*)$/;
Tim Waugh b5b101
		$model = $1;
Tim Waugh b5b101
	    }
Tim Waugh b5b101
	    $usb_CMD ||= $pdls;
Tim Waugh b5b101
	    my $extra;
Tim Waugh b5b101
	    if ($protocol eq "socket") {
Tim Waugh b0dd15
		if ($port eq "9100") {
Tim Waugh b0dd15
		    $uri = "socket://$ip";
Tim Waugh b0dd15
		} else {
Tim Waugh b0dd15
		    $uri = "socket://$ip:$port";
Tim Waugh b0dd15
		}
Tim Waugh b5b101
		$extra = "Port $port";
Tim Waugh b5b101
	    } elsif ($protocol eq "lpd") {
Tim Waugh b5b101
		$uri = "lpd://$ip" . ($uriext ? "/$uriext" : "");
Tim Waugh b5b101
		$extra = ($uriext ? "Queue: $uriext" : "Default queue");
Tim Waugh b5b101
	    } elsif ($protocol eq "ipp") {
Tim Waugh b5b101
		$uri = "ipp://$ip:$port" . ($uriext ? "/$uriext" : "");
Tim Waugh b5b101
		$extra = ($uriext ? "Queue: $uriext" : "Default queue");
Tim Waugh b5b101
	    }
Tim Waugh b5b101
	    if ($make && $model) {
Tim Waugh b5b101
		$make =~ s/Hewlett.?Packard/HP/i;
Tim Waugh b5b101
		$make =~ s/Lexmark.?International/Lexmark/i;
Tim Waugh b5b101
		$model =~ s/Hewlett.?Packard/HP/i;
Tim Waugh b5b101
		$model =~ s/Lexmark.?International/Lexmark/i;
Tim Waugh b5b101
		while ($model =~ s/^\s*$make\s*//i) {};
Tim Waugh b5b101
		$makemodel = "$make $model";
Tim Waugh b5b101
	    } elsif ($usb_DES) {
Tim Waugh b5b101
		$makemodel = $usb_DES;
Tim Waugh b5b101
	    } else {
Tim Waugh b5b101
		$makemodel = "Unknown";
Tim Waugh b5b101
	    }
Tim Waugh b5b101
	    $deviceid = ($usb_MFG ? "MFG:$usb_MFG;" : "") .
Tim Waugh b5b101
		($usb_MDL ? "MDL:$usb_MDL;" : "") .
Tim Waugh b5b101
		($usb_DES ? "DES:$usb_DES;" : "") .
Tim Waugh b5b101
		($usb_CMD ? "CMD:$usb_CMD;" : "");
Tim Waugh b5b101
	    $deviceid .= "CLS:PRINTER;" if $deviceid;
Tim Waugh b0dd15
	    $hosts->{$ip} = $hostname if ($hostname);
Tim Waugh b0dd15
	    $output->{$ip}{$protocol}{$uriext} =
Tim Waugh b5b101
		"network $uri \"$makemodel\" \"$makemodel $ip ($extra)\" \"$deviceid\"\n";
Tim Waugh b5b101
	    ($interface, $nettype, $ip, $host, $make, $model, $description, $cmd, $makemodel, $deviceid, $protocol, $port, $uriext, $uri) =
Tim Waugh b5b101
		("", "", "", "", "", "", "", "", "", "", "", "", "", "");
Tim Waugh b5b101
	}
Tim Waugh b5b101
    }
Tim Waugh b5b101
}
Tim Waugh b5b101
Tim Waugh b0dd15
my $localqueues = {};
Tim Waugh b0dd15
my $queue = undef;
Tim Waugh b0dd15
if (open LPSTAT, "LC_ALL=C lpstat -l -p -v |") {
Tim Waugh b0dd15
    while (my $line = <LPSTAT>) {
Tim Waugh b0dd15
	chomp $line;
Tim Waugh b0dd15
	if ($line =~ /^printer\s+(\S+)/i) {
Tim Waugh b0dd15
	    $queue = $1;
Tim Waugh b0dd15
	    $localqueues->{$queue} = {};
Tim Waugh b0dd15
	} elsif ($queue) {
Tim Waugh b0dd15
	    if ($line =~ /^\s+Connection:\s+remote/i) {
Tim Waugh b0dd15
		$localqueues->{$queue}{remote} = 1;
Tim Waugh b0dd15
	    } elsif ($line =~ /^\s+Interface:\s+(\S+)/i) {
Tim Waugh b0dd15
		$localqueues->{$queue}{interface} = $1;
Tim Waugh b0dd15
	    } elsif ($line =~ /^device\s+for\s+(\S+)\s*:\s*(\S+)/i) {
Tim Waugh b0dd15
		$localqueues->{$1}{uri} = $2;
Tim Waugh b0dd15
	    }
Tim Waugh b0dd15
	}
Tim Waugh b0dd15
    }
Tim Waugh b0dd15
    close LPSTAT
Tim Waugh b0dd15
}
Tim Waugh b0dd15
Tim Waugh b0dd15
my @localips = ();
Tim Waugh b0dd15
if (open IFCONFIG, "LC_ALL=C /sbin/ifconfig |") {
Tim Waugh b0dd15
    while (my $line = <IFCONFIG>) {
Tim Waugh b0dd15
	chomp $line;
Tim Waugh b0dd15
	if ($line =~ /^\s*inet\s+addr:\s*(\S+)/i) {
Tim Waugh b0dd15
	    push (@localips, $1);
Tim Waugh b0dd15
	}
Tim Waugh b0dd15
    }
Tim Waugh b0dd15
    close IFCONFIG;
Tim Waugh b0dd15
}
Tim Waugh b0dd15
Tim Waugh b5b101
foreach my $ip (keys(%{$output})) {
Tim Waugh b0dd15
    # Do not list print queues of the local machine
Tim Waugh b0dd15
    next if member($ip, @localips);
Tim Waugh b5b101
    if ($output->{$ip}{"socket"}) {
Tim Waugh b0dd15
	foreach my $uriext (keys(%{$output->{$ip}{"socket"}})) {
Tim Waugh b0dd15
	    if (keys(%{$output->{$ip}{"socket"}}) == 1) {
Tim Waugh b0dd15
		$output->{$ip}{"socket"}{$uriext} =~
Tim Waugh b5b101
		    s/^(\s*\S*\s*\S*\s*\"[^\"]*\"\s*\"[^\"\(]*?)\s*\([^\)]*\)\s*(\"\s*.*)$/$1$2/;
Tim Waugh b5b101
	    }
Tim Waugh b0dd15
	    print $output->{$ip}{"socket"}{$uriext};
Tim Waugh b5b101
	}
Tim Waugh b5b101
    } elsif ($output->{$ip}{"lpd"}) {
Tim Waugh b0dd15
	foreach my $uriext (keys(%{$output->{$ip}{"lpd"}})) {
Tim Waugh b0dd15
	    if (keys(%{$output->{$ip}{"lpd"}}) == 1) {
Tim Waugh b0dd15
		$output->{$ip}{"lpd"}{$uriext} =~
Tim Waugh b5b101
		    s/^(\s*\S*\s*\S*\s*\"[^\"]*\"\s*\"[^\"\(]*?)\s*\([^\)]*\)\s*(\"\s*.*)$/$1$2/;
Tim Waugh b5b101
	    }
Tim Waugh b0dd15
	    print $output->{$ip}{"lpd"}{$uriext};
Tim Waugh b5b101
	}
Tim Waugh b5b101
    } elsif ($output->{$ip}{"ipp"}) {
Tim Waugh b0dd15
	foreach my $uriext (keys(%{$output->{$ip}{"ipp"}})) {
Tim Waugh b0dd15
	    if ($uriext =~ /^(printers|classes)\/(\S+)$/) {
Tim Waugh b0dd15
		# Queue from a CUPS server. We should suppress it if it
Tim Waugh b0dd15
		# is a queue received via CUPS broadcast
Tim Waugh b0dd15
		$queue=$2;
Tim Waugh b0dd15
		if (defined($localqueues->{"$queue\@$ip"})) {
Tim Waugh b0dd15
		    $queue = "$queue\@$ip";
Tim Waugh b0dd15
		} elsif (defined($localqueues->{"$queue\@$hosts->{$ip}"})) {
Tim Waugh b0dd15
		    $queue = "$queue\@$hosts->{$ip}";
Tim Waugh b0dd15
		}
Tim Waugh b0dd15
		if (defined($localqueues->{$queue})) {
Tim Waugh b0dd15
		    if ($localqueues->{$queue}{remote} &&
Tim Waugh b0dd15
			($localqueues->{$queue}{uri} =~
Tim Waugh b0dd15
			 /^ipp:\/\/([^\/:]+)(:\d+|)\/(\S+)/)) {
Tim Waugh b0dd15
			my $host = $1;
Tim Waugh b0dd15
			my $ue = $3;
Tim Waugh b0dd15
			if ($host !~ /\d+\.\d+\.\d+\.\d+/) {
Tim Waugh b0dd15
			    $host =
Tim Waugh b0dd15
				join(".", unpack("C4", gethostbyname($host)));
Tim Waugh b0dd15
			}
Tim Waugh b0dd15
			next if ($host eq $ip) && ($ue eq $uriext);
Tim Waugh b0dd15
		    }
Tim Waugh b0dd15
		}
Tim Waugh b0dd15
	    }
Tim Waugh b5b101
	    if (keys(%{$output->{$ip}{"ipp"}}) == 1) {
Tim Waugh b0dd15
		$output->{$ip}{"ipp"}{$uriext} =~
Tim Waugh b5b101
		    s/^(\s*\S*\s*\S*\s*\"[^\"]*\"\s*\"[^\"]*?)\s*\([^\)]*\)\s*(\"\s*.*)$/$1$2/;
Tim Waugh b5b101
	    }
Tim Waugh b0dd15
	    print $output->{$ip}{"ipp"}{$uriext};
Tim Waugh b5b101
	}
Tim Waugh b5b101
    }
Tim Waugh b5b101
}
Tim Waugh b5b101
Tim Waugh b5b101
exit 0;
Tim Waugh b5b101
Tim Waugh b5b101
# member( $a, @b ) returns 1 if $a is in @b, 0 otherwise.
Tim Waugh b5b101
sub member { my $e = shift; foreach (@_) { $e eq $_ and return 1 } 0 };