File Coverage

lib/Net/Printer.pm
Criterion Covered Total %
statement 24 244 9.8
branch 0 62 0.0
condition 0 16 0.0
subroutine 8 25 32.0
pod 5 5 100.0
total 37 352 10.5


line stmt bran cond sub pod time code
1              
2             =head1 NAME
3              
4             Net::Printer - Perl extension for direct-to-lpd printing.
5              
6             =head1 SYNOPSIS
7              
8             use Net::Printer;
9              
10             # Create new Printer Object
11             $lineprinter = new Net::Printer(
12             filename => "/home/jdoe/myfile.txt",
13             printer => "lp",
14             server => "printserver",
15             port => 515,
16             lineconvert => "YES"
17             );
18              
19             # Print the file
20             $result = $lineprinter->printfile();
21              
22             # Optionally print a file
23             $result = $lineprinter->printfile("/home/jdoe/myfile.txt");
24              
25             # Print a string
26             $result =
27             $lineprinter->printstring("Smoke me a kipper, I'll be back for breakfast.");
28              
29             # Did I get an error?
30             $errstr = $lineprinter->printerror();
31              
32             # Get Queue Status
33             @result = $lineprinter->queuestatus();
34              
35             =head1 DESCRIPTION
36              
37             Perl module for directly printing to a print server/printer without
38             having to create a pipe to either lpr or lp. This essentially mimics
39             what the BSD LPR program does by connecting directly to the line
40             printer printer port (almost always 515), and transmitting the data
41             and control information to the print server.
42              
43             Please note that this module only talks to print servers that speak
44             BSD. It will not talk to printers using SMB, SysV, or IPP unless they
45             are set up as BSD printers. CUPS users will need to set up
46             B to provide legacy access. ( See L
47             with CUPS"> )
48              
49             =cut
50              
51 1     1   43373 use strict;
  1         2  
  1         42  
52 1     1   4 use warnings;
  1         2  
  1         113  
53              
54             package Net::Printer;
55              
56             our @ISA = qw( Exporter );
57              
58 1     1   27 use 5.006;
  1         8  
  1         46  
59              
60 1     1   5 use Carp;
  1         2  
  1         89  
61 1     1   4433 use File::Temp;
  1         62423  
  1         115  
62 1     1   1670 use FileHandle;
  1         4346  
  1         8  
63 1     1   477 use IO::Socket;
  1         3  
  1         9  
64 1     1   2455 use Sys::Hostname;
  1         2409  
  1         4272  
65              
66             our $VERSION = '1.12';
67              
68             # Exported functions
69             our @EXPORT = qw( printerror printfile printstring queuestatus );
70              
71             # ----------------------------------------------------------------------
72              
73             =head1 METHODS
74              
75             =head2 new
76              
77             Constructor returning Net::Printer object
78              
79             =head3 Parameters
80              
81             A hash with the following keys:
82              
83             =over
84              
85             =item * filename
86              
87             [optional] absolute path to the file you wish to print.
88              
89             =item * printer
90              
91             [default: "lp"] Name of the printer you wish to print to.
92              
93             =item * server
94              
95             [default: "localhost"] Name of the printer server
96              
97             =item * port
98              
99             [default: 515] The port you wish to connect to
100              
101             =item * lineconvert
102              
103             [default: "NO"] Perform LF -> LF/CR translation
104              
105             =item * rfc1179
106              
107             [default: "NO"] Use RFC 1179 compliant source address. Default
108             "NO". see L<"RFC-1179 Compliance Mode and Security Implications">.
109              
110             =back
111              
112             =head3 Returns
113              
114             The blessed object
115              
116             =cut
117              
118             sub new
119             {
120              
121 0     0 1   my (%vars) = ("filename" => "",
122             "lineconvert" => "No",
123             "printer" => "lp",
124             "server" => "localhost",
125             "port" => 515,
126             "rfc1179" => "No",
127             "debug" => "No",
128             "timeout" => 15,
129             );
130              
131             # Parameter(s);
132 0           my $type = shift;
133 0           my %params = @_;
134 0           my $self = {};
135              
136             # iterate through each variable
137 0           foreach my $var (keys %vars) {
138 0 0         if (exists $params{$var}) { $self->{$var} = $params{$var}; }
  0            
139 0           else { $self->{$var} = $vars{$var}; }
140             }
141              
142 0           $self->{errstr} = undef;
143              
144 0           return bless $self, $type;
145              
146             } # new
147              
148             =head2 printerror
149              
150             Getter for error string, if any.
151              
152             =head3 Returns
153              
154             String containing error text, if any. Undef otherwise.
155              
156             =cut
157              
158             sub printerror
159             {
160              
161             # Parameter(s)
162 0     0 1   my $self = shift;
163 0           return $self->{errstr};
164              
165             } # printerror()
166              
167             =head2 printfile
168              
169             Transmits the contents of the specified file to the print server
170              
171             =head3 Parameters
172              
173             =over
174              
175             =item * file
176              
177             Path to file to print
178              
179             =back
180              
181             =head3 Returns
182              
183             1 on success, undef on fail
184              
185             =cut
186              
187             sub printfile
188             {
189 0     0 1   my $dfile;
190              
191 0           my $self = shift;
192 0           my $pfile = shift;
193              
194 0           $self->_logDebug("invoked ... ");
195              
196             # Are we being called with a file?
197 0 0         $self->{filename} = $pfile if ($pfile);
198 0           $self->_logDebug(sprintf("Filename is %s", $self->{filename}));
199              
200             # File valid?
201 0 0 0       if (!($self->{filename}) || (!-e $self->{filename})) {
    0          
202              
203             # Bad file name
204 0           $self->_lpdFatal(
205             sprintf("Given filename (%s) not valid",
206             $self->{filename}));
207 0           return undef;
208              
209             } elsif (uc($self->{lineconvert}) eq "YES") {
210              
211             # do newline coversion
212 0           $dfile = $self->_nlConvert();
213              
214             } else {
215              
216             # just set $dfile to the filename
217 0           $dfile = $self->{filename};
218             }
219              
220 0           $self->_logDebug(sprintf("Real Data File %s", $dfile));
221              
222             # Create Control File
223 0           my @files = $self->_fileCreate();
224              
225 0           $self->_logDebug(sprintf("Real Control File %s", $files[0]));
226 0           $self->_logDebug(sprintf("Fake Data File %s", $files[1]));
227 0           $self->_logDebug(sprintf("Fake Control File %s", $files[2]));
228              
229             # were we able to create control file?
230 0 0         unless (-e $files[0]) {
231 0           $self->_lpdFatal("Could not create control file\n");
232 0           return undef;
233             }
234              
235             # Open Connection to remote printer
236 0           my $sock = $self->_socketOpen();
237              
238             # did we connect?
239 0 0         if ($sock) { $self->{socket} = $sock; }
  0            
240             else {
241 0           $self->_lpdFatal("Could not connect to printer: $!\n");
242 0           return undef;
243             }
244              
245             # initialize LPD connection
246 0           my $resp = $self->_lpdInit();
247              
248             # did we get a response?
249 0 0         unless ($resp) {
250 0           $self->_lpdFatal(
251             sprintf("Printer %s on %s not ready!\n",
252             $self->{printer}, $self->{server}));
253 0           return undef;
254             }
255              
256 0           $resp = $self->_lpdSend($files[0], $dfile, $files[2], $files[1]);
257              
258 0 0         unless ($resp) {
259 0           $self->_lpdFatal("Error Occured sending data to printer\n");
260 0           return undef;
261             }
262              
263             # Clean up
264 0           $self->{socket}->shutdown(2);
265              
266 0           unlink $files[0];
267 0 0         unlink $dfile if (uc($self->{lineconvert}) eq "YES");
268              
269 0           return 1;
270              
271             } # printfile()
272              
273             =head2 printstring
274              
275             Prints the given string to the printer. Note that each string given
276             to this method will be treated as a separate print job.
277              
278             =head3 Parameters
279              
280             =over
281              
282             =item * string
283              
284             String to send to print queue
285              
286             =back
287              
288             =head3 Returns
289              
290             1 on succes, undef on fail
291              
292             =cut
293              
294             sub printstring
295             {
296              
297 0     0 1   my $self = shift;
298 0           my $str = shift;
299              
300             # Create temporary file
301 0           my $tmpfile = $self->_tmpfile();
302 0           my $fh = FileHandle->new("> $tmpfile");
303              
304             # did we connect?
305 0 0         unless ($fh) {
306 0           $self->_lpdFatal("Could not open $tmpfile: $!\n");
307 0           return undef;
308             }
309              
310             # ... and print it out to our file handle
311 0           print $fh $str;
312 0           $fh->close();
313 0 0         return undef unless $self->printfile($tmpfile);
314              
315             # otherwise return
316 0           unlink $tmpfile;
317              
318 0           return 1;
319              
320             } # printstring()
321              
322             =head2 queuestatus
323              
324             Retrives status information from print server
325              
326             =head3 Returns
327              
328             Array containing queue status
329              
330             =cut
331              
332             sub queuestatus
333             {
334              
335 0     0 1   my @qstatus;
336 0           my $self = shift;
337              
338             # Open Connection to remote printer
339 0           my $sock = $self->_socketOpen();
340              
341             # did we connect?
342 0 0         unless ($sock) {
343 0           push( @qstatus,
344             sprintf("%s\@%s: Could not connect to printer: $!\n",
345             $self->{printer}, $self->{server},
346             ));
347 0           return @qstatus;
348             }
349              
350             # store the socket
351 0           $self->{socket} = $sock;
352              
353             # Note that we want to handle remote lpd response ourselves
354 0           $self->_lpdCommand(sprintf("%c%s\n", 4, $self->{printer}), 0);
355              
356             # Read response from server and format
357 0           eval {
358 0     0     local $SIG{ALRM} = sub { die "timeout\n" };
  0            
359 0           alarm 15;
360 0           $sock = $self->{socket};
361 0           while (<$sock>) {
362 0           s/($_)/$self->{printer}\@$self->{server}: $1/;
363 0           push(@qstatus, $_);
364             }
365 0           alarm 0;
366 0           1;
367             };
368              
369             # did we get an error retrieving status?
370 0 0         if ($@) {
371 0 0         push( @qstatus,
372             sprintf(
373             "%s\@%s: Timed out getting status from remote printer\n",
374             $self->{printer}, $self->{server})
375             ) if ($@ =~ /timeout/);
376             }
377              
378             # Clean up
379 0           $self->{socket}->shutdown(2);
380 0           return @qstatus;
381             } # queuestatus()
382              
383             # Private Methods
384             # ----------------------------------------------------------------------
385              
386             # Method: _logDebug
387             #
388             # Displays informative messages ... meant for debugging.
389             #
390             # Parameters:
391             #
392             # msg - message to display
393             #
394             # Returns:
395             #
396             # none
397             sub _logDebug
398             {
399              
400             # Parameter(s)
401 0     0     my $self = shift;
402 0           my $msg = shift;
403              
404             # strip newlines
405 0           $msg =~ s/\n//;
406              
407             # get caller information
408 0           my @a = caller(1);
409              
410 0 0         printf("DEBUG-> %-32s: %s\n", $a[3], $msg)
411             if (uc($self->{debug}) eq "YES");
412              
413             } # _logDebug()
414              
415             # Method: _lpdFatal
416             #
417             # Gets called when there is an unrecoverable error. Sets error
418             # object for debugging purposes.
419             #
420             # Parameters:
421             #
422             # msg - Error message to log
423             #
424             # Returns:
425             #
426             # 1
427             sub _lpdFatal
428             {
429              
430 0     0     my $self = shift;
431 0           my $msg = shift;
432              
433             # strip newlines
434 0           $msg =~ s/\n//;
435              
436             # get caller information and b uild error string
437 0           my @a = caller();
438 0           my $errstr = sprintf("ERROR:%s[%d]: %s", $a[0], $a[2], $msg,);
439 0           $self->{errstr} = $errstr;
440              
441             # carp it
442 0           carp "$errstr\n";
443              
444 0           return 1;
445              
446             } # _lpdFatal()
447              
448             # Method: _tmpfile
449             #
450             # Creates temporary file returning its name.
451             #
452             # Parameters:
453             #
454             # none
455             #
456             # Returns:
457             #
458             # name of temporary file
459             sub _tmpfile
460             {
461              
462 0     0     my $self = shift;
463              
464 0           my $fh = File::Temp->new();
465 0           my $fname = $fh->filename;
466              
467             # Clean up
468 0           $fh->close();
469              
470 0           return $fname
471              
472             } # _tmpfile()
473              
474             # Method: _nlConvert
475             #
476             # Given a filename, will convert newline's (\n) to
477             # newline-carriage-return (\n\r), output to new file, returning name
478             # of file.
479             #
480             # Parameters:
481             #
482             # none
483             #
484             # Returns:
485             #
486             # name of file containing strip'd text, undef on fail
487             sub _nlConvert
488             {
489 0     0     my $self = shift;
490              
491 0           $self->_logDebug("invoked ... ");
492              
493             # Open files
494 0           my $ofile = $self->{filename};
495 0           my $nfile = $self->_tmpfile();
496 0           my $ofh = FileHandle->new("$ofile");
497 0           my $nfh = FileHandle->new("> $nfile");
498              
499             # Make sure each file opened okay
500 0 0         unless ($ofh) {
501 0           $self->_logDebug("Cannot open $ofile: $!\n");
502 0           return undef;
503             }
504 0 0         unless ($nfh) {
505 0           $self->_logDebug("Cannot open $nfile: $!\n");
506 0           return undef;
507             }
508 0           while (<$ofh>) {
509 0           s/\n/\n\r/;
510 0           print $nfh $_;
511             } # while ($ofh)
512              
513             # Clean up
514 0           $ofh->close();
515 0           $nfh->close();
516              
517 0           return $nfile;
518              
519             } # _nlConvert()
520              
521             # Method: _socketOpen
522             #
523             # Opens a socket returning it
524             #
525             # Parameters:
526             #
527             # none
528             #
529             # Returns:
530             #
531             # socket
532             sub _socketOpen
533             {
534              
535 0     0     my $sock;
536 0           my $self = shift;
537              
538             # See if user wants rfc1179 compliance
539 0 0         if (uc($self->{rfc1179}) eq "NO") {
540 0           $sock =
541             IO::Socket::INET->new(Proto => 'tcp',
542             PeerAddr => $self->{server},
543             PeerPort => $self->{port},
544             );
545             } else {
546              
547             # RFC 1179 says "source port be in the range 721-731"
548             # so iterate through each port until we can open
549             # one. Note this requires superuser privileges
550 0           foreach my $p (721 .. 731) {
551 0 0         $sock =
552             IO::Socket::INET->new(PeerAddr => $self->{server},
553             PeerPort => $self->{port},
554             Proto => 'tcp',
555             LocalPort => $p
556             ) and last;
557             }
558             }
559              
560             # return the socket
561 0           return $sock;
562              
563             } # _socketOpen()
564              
565             # Method: _fileCreate
566             #
567             # Purpose:
568             #
569             # Creates control file
570             #
571             # Parameters:
572             #
573             # none
574             #
575             # Returns:
576             #
577             # *Array containing following elements:*
578             #
579             # - control file
580             # - name of data file
581             # - name of control file
582             sub _fileCreate
583             {
584 0     0     my %chash;
585 0           my $self = shift;
586 0           my $myname = hostname();
587 0           my $snum = int(rand 1000);
588              
589             # Fill up hash
590 0           $chash{'1H'} = $myname;
591 0   0       $chash{'2P'} = getlogin || getpwuid($<) || "nobody";
592 0           $chash{'3J'} = $self->{filename};
593 0           $chash{'4C'} = $myname;
594 0           $chash{'5f'} = sprintf("dfA%03d%s", $snum, $myname);
595 0           $chash{'6U'} = sprintf("cfA%03d%s", $snum, $myname,);
596 0           $chash{'7N'} = $self->{filename};
597              
598 0           my $cfile = $self->_tmpfile();
599 0           my $cfh = new FileHandle "> $cfile";
600              
601             # validation
602 0 0         unless ($cfh) {
603 0           $self->_logDebug(
604             "_fileCreate:Could not create file $cfile: $!");
605 0           return undef;
606             } # if we didn't get a proper filehandle
607              
608             # iterate through each key cleaning things up
609 0           foreach my $key (sort keys %chash) {
610 0           $_ = $key;
611 0           s/(.)(.)/$2/g;
612 0           my $ccode = $_;
613 0           printf $cfh ("%s%s\n", $ccode, $chash{$key});
614              
615             }
616              
617             # Return what we need to
618 0           return ($cfile, $chash{'5f'}, $chash{'6U'});
619              
620             } # _fileCreate()
621              
622             # Method: _lpdCommand
623             #
624             # Sends command to remote lpd process, returning response if
625             # asked.
626             #
627             # Parameters:
628             #
629             # self - self
630             #
631             # cmd - command to send (should be pre-packed)
632             #
633             # gans - do we get an answer? (0 - no, 1 - yes)
634             #
635             # Returns:
636             #
637             # response of lpd command
638              
639             sub _lpdCommand
640             {
641              
642 0     0     my $response;
643              
644 0           my $self = shift;
645 0           my $cmd = shift;
646 0           my $gans = shift;
647              
648 0           $self->_logDebug(sprintf("Sending %s", $cmd));
649              
650             # Send info
651 0           $self->{socket}->send($cmd);
652              
653 0 0         if ($gans) {
654              
655             # We wait for a response
656 0           eval {
657 0     0     local $SIG{ALRM} = sub { die "timeout\n" };
  0            
658 0           alarm 5;
659 0 0         $self->{socket}->recv($response, 1024)
660             or die "recv: $!\n";
661 0           1;
662             };
663              
664 0           alarm 0;
665              
666             # did we get an error?
667 0 0         if ($@) {
668 0 0         if ($@ =~ /timeout/) {
669 0           $self->_logDebug("Timed out sending command");
670 0           return undef;
671             }
672             }
673              
674 0           $self->_logDebug(sprintf("Got back :%s:", $response));
675              
676 0           return $response;
677              
678             }
679              
680             } # _lpdCommand()
681              
682             # Method: _lpdInit
683             #
684             # Notify remote lpd server that we're going to print returning 1 on
685             # okay, undef on fail.
686             #
687             # Parameters:
688             #
689             # none
690             #
691             # Returns:
692             #
693             # 1 on success, undef on fail
694             sub _lpdInit
695             {
696 0     0     my $self = shift;
697              
698 0           my $buf = "";
699 0           my $retcode = 1;
700              
701 0           $self->_logDebug("invoked ... ");
702              
703             # Create and send ready
704 0   0       $buf = sprintf("%c%s\n", 2, $self->{printer}) || "";
705 0           $buf = $self->_lpdCommand($buf, 1);
706 0   0       $retcode = unpack("c", $buf || 1);
707              
708 0           $self->_logDebug("Return code is $retcode");
709              
710             # check return code
711 0 0 0       if (($retcode =~ /\d/) && ($retcode == 0)) {
712 0           $self->_logDebug(
713             sprintf("Printer %s on Server %s is okay",
714             $self->{printer}, $self->{server}));
715 0           return 1;
716             } else {
717 0           $self->_lpdFatal(
718             sprintf("Printer %s on Server %s not okay",
719             $self->{printer}, $self->{server}));
720 0   0       $self->_logDebug(sprintf("Printer said %s", $buf || "nothing"));
721              
722 0           return undef;
723             }
724             } # _lpdInit()
725              
726             # Method: _lpdSend
727             #
728             # Sends the control file and data file
729             #
730             # Parameter(s):
731             #
732             # cfile - Real Control File
733             # dfile - Real Data File
734             # p_cfile - Fake Control File
735             # p_dfile - Fake Data File
736             #
737             # Returns:
738             #
739             # 1 on success, undef on fail
740             sub _lpdSend
741             {
742 0     0     my $self = shift;
743 0           my $cfile = shift;
744 0           my $dfile = shift;
745 0           my $p_cfile = shift;
746 0           my $p_dfile = shift;
747              
748 0           $self->_logDebug("invoked ... ");
749              
750             # build hash
751 0           my $lpdhash = {
752             "3" => {
753             "name" => $p_dfile,
754             "real" => $dfile
755             },
756             "2" => {
757             "name" => $p_cfile,
758             "real" => $cfile
759             },
760             };
761              
762             # iterate through each keytype and process
763 0           foreach my $type (keys %{$lpdhash}) {
  0            
764              
765 0           $self->_logDebug(
766             sprintf("TYPE:%d:FILE:%s:",
767             $type, $lpdhash->{$type}->{"name"},
768             ));
769              
770             # Send msg to lpd
771 0           my $size = (stat $lpdhash->{$type}->{"real"})[7];
772 0           my $buf = sprintf(
773             "%c%ld %s\n", $type, # Xmit type
774             $size, # size
775             $lpdhash->{$type}->{"name"}, # name
776             );
777              
778 0           $buf = $self->_lpdCommand($buf, 1);
779              
780             # check bugger
781 0 0         unless ($buf) {
782 0           carp "Couldn't send data: $!\n";
783 0           return undef;
784             }
785              
786             $self->_logDebug(
787 0           sprintf("FILE:%s:RESULT:%s",
788             $lpdhash->{$type}->{"name"}, $buf
789             ));
790              
791             # open new file handle
792 0           my $fh = FileHandle->new($lpdhash->{$type}->{"real"});
793              
794 0 0         unless ($fh) {
795 0           $self->_lpdFatal(
796             sprintf("Could not open %s: %s\n",
797             $lpdhash->{$type}->{"real"}, $!,
798             ));
799 0           return undef;
800             }
801              
802             # set blocksize
803 0   0       my $blksize = (stat $fh)[11] || 16384;
804              
805             # read from socket
806 0           while (my $len = sysread $fh, $buf, $blksize) {
807              
808             # did we get anything back?
809 0 0         unless ($len) {
810 0 0         next if ($! =~ /^Interrupted/);
811 0           carp "Error while reading\n";
812 0           return undef;
813             }
814              
815 0           my $offset = 0;
816              
817             # write out buffer
818 0           while ($len) {
819 0           my $resp = syswrite($self->{socket},
820             $buf, $len, $offset);
821 0 0         next unless $resp;
822 0           $len -= $resp;
823 0           $offset += $resp;
824              
825             }
826             }
827              
828             # Clean up
829 0           $fh->close();
830              
831             # Confirm server response
832 0           $buf = $self->_lpdCommand(sprintf("%c", 0), 1);
833 0           $self->_logDebug(sprintf("Confirmation status: %s", $buf));
834             }
835              
836 0           return 1;
837              
838             } # _lpdSend()
839              
840             # ----------------------------------------------------------------------
841             # Standard publically accessible method
842             # ----------------------------------------------------------------------
843              
844             # Method: DESTROY
845             #
846             # called when module destroyed
847             #
848             sub DESTROY
849             {
850              
851             # Parameter(s)
852 0     0     my $self = shift;
853              
854             # Just in case :)
855 0 0         $self->{socket}->shutdown(2) if ($self->{socket});
856              
857             } # DESTROY
858              
859             1;
860              
861             =head1 TROUBLESHOOTING
862              
863             =head2 Stair Stepping Problem
864              
865             When printing text, if you have the infamous "stair-stepping" problem,
866             try setting lineconvert to "YES". This should, in most cases, rectify
867             the problem.
868              
869             =head2 RFC-1179 Compliance Mode and Security Implications
870              
871             RFC 1179 specifies that any program connecting to a print service must
872             use a source port between 721 and 731, which are I,
873             meaning you must have root (administrative) privileges to use them.
874             I
875             possible!>
876              
877             =head2 Using Net::Printer with CUPS
878              
879             Net::Printer does not natively speak to printers running CUPS (which
880             uses the IPP protocol). In order to provide support for legacy
881             clients, CUPS provides the B mini-server which can be set up
882             to run out of either B or B depending on preference.
883             You will need to set up this functionality in order to use
884             Net::Printer with CUPS server. Consult your system documentation as
885             to how to do this.
886              
887             =head1 SEE ALSO
888              
889             L, L, L, L
890              
891             RFC 1179 L
892              
893             =head1 AUTHOR
894              
895             Christopher M. Fuhrman C<< >>
896              
897             =head1 REVISION INFORMATION
898              
899             $Id: 9044ee617cffd95213cff21af410d8ea1dc3f1fd $
900              
901             =head1 COPYRIGHT & LICENSE
902              
903             Copyright (c) 2000-2005,2008,2011,2013 Christopher M. Fuhrman,
904             All rights reserved.
905              
906             This program is free software licensed under the...
907              
908             The BSD License
909              
910             The full text of the license can be found in the
911             LICENSE file included with this module.
912              
913             =cut
914              
915             __END__