Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 9 Dec 2009 08:37:41 +0700 (ICT)
From:      Olivier Nicole <Olivier.Nicole@cs.ait.ac.th>
To:        david@vizion2000.net
Cc:        freebsd-questions@freebsd.org
Subject:   Re: set up of hp laserjet 2200dn with  Jetdirect 610N on network
Message-ID:  <200912090137.nB91bfPG082751@banyan.cs.ait.ac.th>
In-Reply-To: <200912081426.25130.david@vizion2000.net> (message from David Southwell on Tue, 8 Dec 2009 14:26:25 %2B0000)
References:  <200912081040.02500.david@vizion2000.net> <19230.20297.454639.6513@jerusalem.litteratus.org> <200912081426.25130.david@vizion2000.net>

next in thread | previous in thread | raw e-mail | index | archive | help
David,

Here it is:

- in /etc/printcap:

big:hp4300:\
        :sd=/var/spool/big:\
        :mx=0:rs:sh:\
        :lp=/dev/null:\
        :if=/usr/spool/big_new:\

- in /etc/rc.conf: 

lpd_enable="YES"

- the script /usr/spool/big_new. The location of the script is not the
  best one, it should ideally go into /usr/local/spool.

  This script does much, much more than what you expect: it has a
  print quota system, based on a LDAP directory; but also does what
  you want:

  - starting around the line "Use Socket;" is the part sending the
    file to the printer;

  - all the jobs are encapsulated in a PCL job that forces duplex
    printing; note if the file you are printing specify that the job
    shiould be printed single sided, then it will be single sided, but
    if nothing is sayd, it is printed double sided;

  - if the file is plain text (not PostScript, not PCL), the end of
    lines are converted to \r\n;

  - this script has several timeout when trying to connect to the
    printer, and a general timeout of 20 minutes, so no job should
    exceed that time once it started (which is theorically 1000 pages
    on the newer/fastest printers);

  - the script should be able to tell you how many pages were used by
    a job (I did a minus one there because some of our users are
    touchy regarding their print quota, and some jobs were reported to
    be two pages while only printed on the recto, must be something
    with CTRL-L at the end of the job);

  - the script needs a number of Perl modules, all available from the
    ports; it does a fair job at loging to syslog when $dbg is set to
    1;

  - in a previous version, I had been using this script with a HP
    2200dn.

I hope you speak Perl :)

Bests,

Olivier

#!/usr/local/bin/perl -w
use strict;

my $printer="hp4300dtn.cs.ait.ac.th";
my $timeout=20*60; # longest job in seconds
my $dbg=1;

use Unix::Syslog qw(:macros);  # Syslog macros
use Unix::Syslog qw(:subs);    # Syslog functions
openlog "print_quota", LOG_PID, LOG_LPR;

use Mail::SendEasy ;
my $mail = new Mail::SendEasy(smtp => 'mail.cs.ait.ac.th') ;

syslog LOG_DEBUG, "DBG staring with ARGV @ARGV" if $dbg;
syslog LOG_DEBUG, "DBG printer $printer" if $dbg;

while ($#ARGV>=0 && $ARGV[0] ne "-n") {
    shift @ARGV;
}

our $user=$ARGV[1];
if ($user eq "nobody") {
    # Purge the print job
    open IN, "-";
    while (<IN>) {
    }
    exit;
}

our $host=$ARGV[3];

use Net::LDAP;
our $ldap=Net::LDAP->new('ldaps://ldap.cs.ait.ac.th/');
if (! defined $ldap) {
    syslog LOG_ERR, "Cannot connect to LDAP server";
    open IN, "-";
    while (<IN>) {
    }
    exit 1;
}
my $mesg=$ldap->bind("cn=PrintQuotaAdmin,ou=Administrator,ou=csim,dc=cs,dc=ait,dc=ac,dc=th", password=>"******");
if (! defined $mesg) {
    syslog LOG_ERR, "Cannot bind to LDAP server";
    open IN, "-";
    while (<IN>) {
    }
    exit 1;
}
syslog LOG_DEBUG, "DBG binding LDAP done" if $dbg;

$mesg=$ldap->
    search(base=>"ou=PrintQuota,ou=Resources,ou=csim,dc=cs,dc=ait,dc=ac,dc=th",
	   scope=>'one',
	   attrs=>['ou', 'description'],
	   filter=>"(ou=*)");
if ($mesg->code) {
    syslog LOG_ERR, "Error while reading PrintQuota base in LDAP: ".$mesg->error;
    # Purge the print job
    open IN, "-";
    while (<IN>) {
    }
    exit 1;
}
my $entry;
our %basequota;
foreach $entry ($mesg->entries) {
    my $type=$entry->get_value('ou');
    my $quota=$entry->get_value('description');
    $basequota{$type}=$quota;
}

syslog LOG_DEBUG, "DBG loading quota base done" if $dbg;

$mesg=$ldap->
    search(base=>"ou=people,ou=csim,dc=cs,dc=ait,dc=ac,dc=th",
	   attrs=>['csimPrintQuotaType', 'csimPrintQuotaAdditional', 
		   'csimPrintQuotaUsed'],
	   filter => "(uid=$user)");

if ($mesg->code) {
    syslog LOG_ERR, "Error while accessing user $user in LDAP: ".$mesg->error;
    # Purge the print job
    open IN, "-";
    while (<IN>) {
    }
    exit 1;
}
if ($mesg->count() != 1) {
    syslog LOG_ERR, "Found more than one user $user in LDAP";
    # Purge the print job
    open IN, "-";
    while (<IN>) {
    }
    exit 1;
}
$entry=$mesg->entry(0);
if (! defined $entry) {
    syslog LOG_ERR, "Could not find the user $user in LDAP";
    # Purge the print job
    open IN, "-";
    while (<IN>) {
    }
    exit 1;
}
our $type=$entry->get_value('csimPrintQuotaType');
our $add=$entry->get_value('csimPrintQuotaAdditional');
our $used=$entry->get_value('csimPrintQuotaUsed');
$type=~s/^[^=]*=\s*//;
$type=~s/\s*,.*$//;
our $base=$basequota{$type};

syslog LOG_DEBUG, "DBG user $user found and quota loaded" if $dbg;

if ($type=~/blocked/i) {
    # We could have a warning here, but I doubt people will read it
    #	print "User $user is over quota: $used{$user} ($quot{$user})\n";
    my $status = $mail->send(
			     from    => 'daemon@cs.ait.ac.th' ,
			     from_title => 'Print Daemon',
			     to      => "$user\@cs.ait.ac.th" ,
			     subject => "Your print quota is blocked" ,
			     msg     => "Your print quota is blocked.

Printing has been disabled from your account.

You should contact a system administrator to get your account unblocked.
");
    
   # Purge the print job
    open IN, "-";
    while (<IN>) {
    }
    syslog LOG_ERR, "Quota blocked for user $user in LDAP";
    exit;
}
elsif ($type!~/unlimited/i && $base+$add<$used) {
    # We could have a warning here, but I doubt people will read it
    #	print "User $user is over quota: $used{$user} ($quot{$user})\n";
    my $avail=$base+$add;
    my $status = $mail->send(
			     from    => 'daemon@cs.ait.ac.th' ,
			     from_title => 'Print Daemon',
			     to      => "$user\@cs.ait.ac.th" ,
			     subject => "Print quota exceeded" ,
			     msg     => "Your print usage exceeds your print quota.

So far you have printed $used pages when your quota is $base + $add.

To resume printing, you can buy more print quota, please look at the web page 
http://www.cs.ait.ac.th/laboratory/printer/purchase.shtml
");
    # Purge the print job
    open IN, "-";
    while (<IN>) {
    }
    syslog LOG_ERR, "Quota exceeded for user $user, type: $type, used: $used, base: $base, additional: $add";
    exit;
}

use Socket;
use IO::Socket::INET;

my $count=0;
our $sock=IO::Socket::INET->new(PeerAddr => "$printer:jetdirect(9100)");
while ((! defined $sock) && ($count<=10)) {
    sleep 20;
    syslog LOG_DEBUG, "DBG connecting to the printer $printer, try $count" if $dbg;
    $sock=IO::Socket::INET->new(PeerAddr => "$printer:jetdirect(9100)");
    $count++;
}
if ($count>10) {
    syslog LOG_ERR, "The printer $printer cannot be connected";
    # Purge the print job
    open IN, "-";
    while (<IN>) {
    }
    exit 1;
}

syslog LOG_DEBUG, "DBG printer connected" if $dbg;
$sock->autoflush(1);
$sock->timeout(10);


our $ID_STRING="TIME=".localtime()." PID=$$ USER=$user HOST=$host";

# $SIG{'ALRM'}='do_alarm';

my $child=fork;
if (! defined $child) {
    syslog LOG_ERR, "Cannot fork a process";
    # Purge the print job
    open IN, "-";
    while (<IN>) {
    }
    exit 1;    
}
syslog LOG_DEBUG, "DBG process forked successfully" if $dbg && (! $child);

if ($child==0) {
    # parent process
    $sock->print("%-12345X\@PJL JOB NAME=\"$ID_STRING\"\r\n");
    $sock->print("\@PJL USTATUS JOB=ON\r\n");
    $sock->print("\@PJL SET DUPLEX=ON\r\n");
    syslog LOG_DEBUG, "DBG parent header printed" if $dbg;
    open IN, "-";
    my $l=<IN>;
    my $t=0;
    $t=1 unless $l=~/^(\\%.*\@PJL)|(%!PS)/;
    $l=~s/([^\r])?\n/$1\r\n/g if $t;
    $sock->print($l);
    while (<IN>) {
	s/([^\r])?\n/$1\r\n/g if $t;
	$sock->print($_);
    }
    syslog LOG_DEBUG, "DBG parent job printed" if $dbg;
    $sock->print("%-12345X\@PJL EOJ NAME=\"$ID_STRING\"\r\n");
    $sock->print("%-12345X\r\n");
    syslog LOG_DEBUG, "DBG footer printed, parent waiting for end the child" if $dbg;
    wait;
    syslog LOG_DEBUG, "DBG child has terminated" if $dbg;
}
else {
    # child process
    # $sock->blocking(0);
    my $pages;
    my $end=0;
    my $job=0;
    my $what;
    $SIG{'ALRM'}='do_alarm';
    alarm $timeout;
    syslog LOG_DEBUG, "DBG in child" if $dbg;
    while ($sock->opened && defined($_=$sock->getline)) {
	syslog LOG_DEBUG, "DBG child received line: $_" if $dbg;
	if (/^NAME=\"([^\"]+)\"/  && ($1 ne $ID_STRING)) {
	    $what=$1;
	    syslog LOG_DEBUG, "DBG child original job name $what" if $dbg;
	}
	$end=1 if $_ =~ /^END/;
	$job=1 if $end && $_ =~ /^NAME=\"$ID_STRING\"/;
	$pages=$1 if $end && $job && $_ =~ /^PAGES=(\d+)/;
	if ($end && $job && ((defined $pages) || //)) {
	    $sock->shutdown(SHUT_RDWR) if defined $sock && $sock->opened;
	    last;
	}
    }
    $SIG{'ALRM'}='IGNORE';
    $sock->shutdown(SHUT_RDWR) if defined $sock && $sock->opened;

    if (! defined $pages) {
	syslog LOG_ERR, "the job existed with no page count";
	$sock->shutdown(SHUT_RDWR) if $sock->opened;
	exit;
    }
    syslog LOG_DEBUG, "DBG print job terminated properly" if $dbg;
    $pages-=1; # because 1 pages counts for 2, 3 pages count for 4 etc.
    exit if $pages<1;
    
    # Update the page count for the user
    $mesg=$ldap->modify
	("uid=$user,ou=People,ou=csim,dc=cs,dc=ait,dc=ac,dc=th",
	 add =>{csimPrintQuotaLock => "$ID_STRING"});
    if ($mesg->code) {
	syslog LOG_ERR, "Error while writing the lock string $ID_STRING for user $user in LDAP: ".$mesg->error;
	exit;
    }
    syslog LOG_DEBUG, "DBG lock $ID_STRING written" if $dbg;
    my $lock;
    $count=0;
    do {
	# read the first lock string in csimPrintQuotaLock
	# if this string is not our ID_STRING, it means another printer
	# is updating quota
	# it seems that LDAp organize the strings in a FIFO
	$mesg=$ldap->search(base=>"ou=people,ou=csim,dc=cs,dc=ait,dc=ac,dc=th",
			       attrs=>['csimPrintQuotaLock', 
				       'csimPrintQuotaUsed'],
			       filter => "(uid=$user)");
	
	if ($mesg->code) {
	    syslog LOG_ERR, "Error while accessing lock of user $user in LDAP: ".$mesg->error;
	    &do_exit_bad_lock;
	}
	if ($mesg->count() != 1) {
	    syslog LOG_ERR, "Found more than one user $user in LDAP when reading quota lock";
	    &do_exit_bad_lock;
	}
	$entry=$mesg->entry(0);
	if (! defined $entry) {
	    syslog LOG_ERR, "Could not find the user $user in LDAP when reading quota lock";
	    &do_exit_bad_lock;
	}
	$lock=($entry->get_value('csimPrintQuotaLock'))[0];
	$count++;
	syslog LOG_DEBUG, "DBG lock $lock read on $count try" if $dbg;
    } until ($lock eq $ID_STRING) || $count>=10;
    if ($count>=10) {
	syslog LOG_ERR, "Could not acquire the lock $ID_STRING, found $lock";
	&do_exit_bad_lock;
    }
    # Now we have the lock!

    $used=$pages + $entry->get_value('csimPrintQuotaUsed');
    $entry->replace('csimPrintQuotaUsed', $used);
    $entry->delete('csimPrintQuotaLock', [$ID_STRING]);
    my $msg=$entry->update($ldap);
    if ($msg->code) {
	syslog LOG_ERR, "Could not update used quota for user $user in LDAP: ".$msg->error;
    }
    syslog LOG_DEBUG, "DBG quota updated and lock removed" if $dbg;

    # send and email if the usage has reached 90% of the quota
    if ($type!~/unlimited/i && (($add+$base)*0.9<$used)) {
	my $avail=$add+$base;
	my $left=$avail-$used;
	my $status = $mail->send(
				 from    => 'daemon@cs.ait.ac.th' ,
				 from_title => 'Print Daemon',
				 to      => "$user\@cs.ait.ac.th" ,
				 subject => "Print usage over 90% of quota" ,
				 msg     => "Your print usage is over 90% of your print quota.

So far you have printed $used pages out of your quota of $base + $add.

You have $left pages left to print before you get over quota.

To prevent being blocked, you should buy more print quota, please look at
the web page http://www.cs.ait.ac.th/laboratory/printer/purchase.shtml
");
        syslog LOG_INFO, "Quota low for user $user, type: $type, used: $used, base: $base, additional: $add.";
    }
    
    # log the job
    my $log="Print job ";
    $log.="\"$what\" " if defined $what;
    $log.=", on printer $printer, for user $user, pages=$pages, from host $host";
    syslog LOG_INFO, $log;
}

sub do_alarm {
    $sock->shutdown(SHUT_RDWR) if defined $sock && $sock->opened;
    syslog LOG_ERR, "The print job for user $user has timed out";
	my $status = $mail->send(
				 from    => 'daemon@cs.ait.ac.th' ,
				 from_title => 'Print Daemon',
				 to      => "root\@cs.ait.ac.th" ,
				 subject => "Print job exited on time out" ,
				 msg     => "A print job has exited on time out.
grep print_quota /var/log/all on sysl.cs.ait.ac.th.
");
    exit 1;
}

sub do_exit_bad_lock {
    # Something failed when reading the lock, we force remove it
    # A trailing lock in the csimPrintQuotaLock FIFO would cause
    # a infinite loop for next print job
    $sock->shutdown(SHUT_RDWR) if defined $sock && $sock->opened;
    my $status = $mail->send(
			     from    => 'daemon@cs.ait.ac.th' ,
			     from_title => 'Print Daemon',
			     to      => "root\@cs.ait.ac.th" ,
			     subject => "Print job cannot acquire the lock" ,
			     msg     => "A print job cannot acquire the lock.

Check the attribute csimPrintQuotaLock for all users in LDAP 
(use advanced Search with the filter csimPrintQuotaLock=*).
");
    $mesg=$ldap->modify
	("uid=$user,ou=People,ou=csim,dc=cs,dc=ait,dc=ac,dc=th",
	 delete =>{csimPrintQuotaLock => "$ID_STRING"});
    if ($mesg->code) {
	syslog LOG_ERR, "Error while removing the lock by FORCE string $ID_STRING for user $user in LDAP: ".$mesg->error;
    }
    exit;
}



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200912090137.nB91bfPG082751>