Печать через Интернет - альтернатива

  Автор: © Graham Jenkins
Перевод: © Павел В. Ступин (Кобальт).


 

Проблема

Вы работаете на вашем домашнем ПК, соединенном с вашим же любимым провайдером - и вдруг вы решаете, что было бы неплохо распечатать документ Word на скоростном цветном принтере в вашем офисе. Принтер подсоединен к корпоративной ЛВС, но вы не можете соединиться с ней, использую LPR или IPP, потому что сеть спрятана за корпоративным брандмауэром.

Конечно, можно сохранить документ в файл, потом послать его по почте кому-нибудь в офис, и устроить так, чтобы этот кто-нибудь распечатал документ, отправив его на принтер. Однако в таком случае придется пройти целый ряд шагов - и ситуация может стать еще сложнее, если существует ограничение на размер почтовых сообщений, на одном из серверов, через которые письмо переправляется в корпоративную сеть. Тогда придется заняться манипуляциями по разбиению файла на части и посылать его по кусочкам.

Клиентское программно обеспечение

Люди, которые работают над изготовлением принтеров Brother в курсе всего этого, и поэтому они разработали набор соответствующих драйверов под Windows. Драйверы позволяют пользователям печатать напрямую на заданный почтовый адрес. Таким образом получившееся задание для принтера при необходимости автоматически разбивается на части, каждая из которых перед отправлением кодируется в base64. Пользователи также могут назначать почтовый адрес для получения на него подтверждения об отправке задания.

Эти Windows-драйверы для принтера (для Windows 95/98 и Windows NT-4.0/2000) можно загрузить с сайта Brother.

Возможности принтера

Как вы думаете, по-мнению разработчиков из Brother, на чем вы должны печатать? Разумеется, на принтере Brother - точнее, в данном случае, на том, который оснащен сетевой картой, способной принимать, декодировать и собирать в одно целое почтовые сообщения, посланные на принтер.

Так что же делать в том случае, если вы пользуетесь принтером другого производителя?

Решение с помощью программного обеспечения

Моей первой штыковой атакой в решении проблемы был скрипт для Korn-shell, к которому в виде конвейера (pipe) подсоединялись все исходящие сообщения через sendmail-овский алиас. Программа использовала 'awk' для распознавания информации, такой как номера заданий и частей, затем декодировала каждое почтовое сообщение в соответствующим образом названный файл, оставляя его в заданной директории.

После получения части, программа помечала ее как "завершенная", потом ставила блокировку, запрещающую одновременный доступ к файлам, и начинала процедуру, устанавливающую, были ли все необходимые части получены полностью. Если да, то они последовательно объединялись, объединный файл отправлялся на принтер, а потом файлы удалялись.

И вот именно тогда я подумал: "А что если не хватит дискового пространства для хранения всех заданий, поступающих в данный момент?" И еще я задумался: "А как же это сделали ребята из Brother с их сетевой карточкой?"

Решаем проблему без использования места на локальном диске

Ответ на мой второй вопрос звучит так: "Они используют POP3-сервер!". Части каждого задания остаются на сервере до тех пор, пока сетевая карта не определит, что все необходимые части уже прибыли, после чего объединит в один файл, последовательно декодируя их, а потом пошлет результат на принтер, попутно запрося удаление файлов с сервера.

Итак, на Linux-машине все это решается описанным ниже образом. Программа была написана на Perl, поэтому может использоваться модуль NET::POP3 для простого доступа к POP3-серверу. Я протестировал программу на машинах с NetBSD и Solaris, так что она должна по идее работать практически везде; все, что вам нужно изменить - это местоположение интерпретатора Perl, имя, используемое для 'awk', и формат команды 'lpr'. [Текстовая версия листинга.]

#!/usr/bin/perl -w
# @(#) BIPprint.pl      Получает файлы заданий Интернет-печати с помощью Brother 
#                       c POP3-сервера и передает их на указазнный(-ые) принтер(-ы). 
#                       Версия с экономным использованием памяти. 
#                       Предназначена для вызова через inittab.
#                       Graham Jenkins, IBM GSA, Февраль. 2001. Пересмотрено: 17 Марта. 2001.

use strict;
use File::Basename;
use Net::POP3;
use Date::Manip;
use IO::File;
my $host="bronzeback.in.telstra.com.au";        # Один и тот же хост и пароль для
my $pass="MySecret";                            # каждого принтера.
my $limit=30*1024*1024;                         # Максимум байтов на каждое задание печати.
my ($printer,$awkprog);
defined($ARGV[0]) || die "Usage: ", basename($0). " printer1 [ printer2 ..]\n";
open(LOG,"|/usr/bin/logger -p local7.info -t ".basename($0)); autoflush LOG 1;
$awkprog="";                            
while (<DATA>) {$awkprog = $awkprog . $_};      # Создаем программу на awk для последующего использования,
while (1) {                                     # затем запускаем бесконечный цикл, обрабатывая 
  sleep 30;                                     # все принтеры в каждом заходе, и
  foreach $printer (@ARGV) {process($printer);} # "отдыхаем" 30 секунд
}                                               # между каждым заходом.

sub process {
  my ($flag,$i,$j,$k,$l,$m,$allparts,$user,$pop,@field,@part,$count,$top15,
      $msgdate,$parsdate,$notify,$reply,%slot,$fh);
  $user = $_[0];
  $pop = Net::POP3->new($host);                 # Входим на POP3-сервер и получаем
  $count = $pop->login($user,$pass) ;           # заголовок плюс первые 15 строк
  $count = -1 if ! defined ($count) ;              # каждого сообщения. Используем apop,
      for ($i = 1; $i <= $count; $i++ ) {       # если он поддерживается сервером.     
    $top15=$pop->top($i,15) ;                    
    if ($top15) {                       
      $msgdate = ""; $notify="None"; $reply="";
      for ($j = 0; $j < 99; $j++ ) {
        if (@$top15[$j]) {                      # Используем дату прибытия на POP3-сервере
          if($msgdate eq "") {                  # для определения возраста письма;
            (@field) = split(/;/,@$top15[$j]);  # если письмо устарело - 
            if ( defined($field[1])) {          # удаляем его и переходим к следующему.
              $parsdate=&ParseDate($field[1]);  # (Ищем точку с запятой, после которой
              if( $parsdate ) {                 # идет корректная дата.)
                $msgdate="Y";
                if(&Date_Cmp($parsdate, &DateCalc("today","-3 days") ) lt 0 ) {
                  print LOG "Stale msg: $user $parsdate\n";
                  $pop->delete($i);
                  goto I;                       # Если POP3-сервер автоматически
                }                               # фильтрует письма на предмет устаревания,
              }                                 # то эту часть можно полностью
            }                                   # опустить.
          }
          (@field) = split(/=/, @$top15[$j]);
          if ( defined($field[0]) ) {   
            if ($field[0] eq "BRO-NOTIFY") {chomp $field[1];$notify=$field[1];}
            if ($field[0] eq "BRO-REPLY")  {chomp $field[1];$reply =$field[1];}
            if ( $field[0] eq "BRO-PARTIAL" ) { # Закомментируйте предыдующую строку,
              ( @part )=split("/", $field[1]);  # чтобы запретить уведомление по почте.
              chomp $part[1];           
            }
            if ( $field[0] eq "BRO-UID" ) {     # Определяем задание для печати и какая часть задания
              chomp $field[1];                  # содержится в письме.
              $slot{$field[1]."=".$part[0]} = $i ;
              $allparts = "Y";                  # После просмотра всех писем проверяем,
              for ($k=1;$k<=$part[1];$k++) {    # все ли части получены.
                $allparts = "N" if ! defined($slot{$field[1]."=".$k}) ; 
              }
              if ( $allparts eq "Y" ) {         # Распечатываем и удаляем все части.
                print LOG "$field[1] $part[1] => $user\n";
                if(($notify ne "None") && ($reply ne "")) {system 
                  "echo Print Job Received, $part[1] pcs|Mail -s$user $reply";}
                $fh=new IO::File "|awk '{$awkprog}' Limit=$limit |lpr -P $user";
                for ($k = 1;$k<=$part[1];$k++) {
                  $pop->get($slot{$field[1]."=".$k},$fh) ;
                  $pop->delete($slot{$field[1]."=".$k}) ;
                }                               # Если есть достаточно места на диске,
                $fh->close;                     # перенаправляем вывод awk через gzip во
              }                                 # временный файл, потом распечатываем его
            }                                   # и удаляем все части; это на случай
          }                                     # неудачного соединения.
        }                       
      }                                         # Последующая awk-программа
    }                                           # используется для выделения частей из файла,
I:}                                             # содержащего несколько фрагментов.
$pop->quit() if ($count >= 0);                # Затем части декодируются и направляются
}                                               # на стандартный выход.
__DATA__
if( Flag == 2 ) {
    Size=Size+length
    if(length == 0) { Flag=0; close("mmencode -u 2>/dev/null") }
    else if(Size<=Limit*4/3) print $0 |"mmencode -u 2>/dev/null" }
  if( Flag == 1 ) if(length == 0) Flag=2
  if( Flag == 0 ) if($1 ~ /^Content-Transfer-Enc/) if($NF == "base64") Flag=1

Небольшой анализ программы.

Программа создает скрипт на awk для дальнейшего использования, затем для каждого объявленного в командной строке (при вызове программы) принтера программа обращается к почтовому ящику с идентичным имени принтера названием и проверяет его на наличие сообщений. Если сообщения устарели - они удаляются. В обратном случае - вычленяется содержимое специфических для Brother строк: они содержат информацию о том, необходимо ли уведомление по почте и какая часть задания содержится в данном почтовом сообщении.

Если в процессе анализа сообщений устанавливается, что все части задания обнаружены в почтовом ящике, то при необходимости генерируется почтовое уведомление, а части последовательно "вытягиваются" из ящика и перенаправляются через awk-программу (которая декодирует каждую часть по мере их прибытия) на соответствующую команду печати. Таким образом, каждая часть удаляется, как только она была обработана.

В идеале, конечно, нужно подождать до того момента, пока мы не получим уведомление об успешной (или нет) передаче задания на принтер, и только после этого заниматься почтой и удалением заданий. Однако, как это уже отмечалось в листинге, для реализации такого подхода необходимо дополнительное место на диске. Также, в то время как клиентское ПО Brother позволяет посылать уведомления, содержащие различную информацию (успех/неудача/др.), моя программа ограничивается простой констатацией того, что задание передано на принтер (ну и можно отменить это уведомление вообще).

Заключительные замечания

Программа использует пароль, поэтому она должна быть доступна для чтения только пользователю, который запускает ее. Никаких специальных привилегий не требуется для запуске программы, а соответствующая запись в вашем /etc/inittab должна выглядеть примерно так:

bi:345:respawn:su - nobody -c "/usr/local/bin/BIPprint.pl lp1 lp2 >/dev/null 2>&1"

Если вы дочитали до этого места, то, наверное, вы подумали: "Хорошо. Программа не требует много места на диске для своей работы. Но она же посылает свой вывод в спулер печати! Как же на счет этого?" Если пространство, отведенное спулу, предмет головной боли, то можно использовать что-нибудь типа 'netcat' или 'hpnpout' для отправки заданий на прямую на принтер вместо спула. Или можно перенаправить задание через FTP-соединение к вашему принтеру. Если конкретно нужно обойти использование спула таким способом, то нужно использовать отдельную копию программы для каждого принтера.

Это, конечно, не высший пилотаж, и здесь нет аутентификации пользователя или шифрования данных. Но - эта программа может немного облегчить вашу жизнь. Пользуйтесь и получайте удовольствие!

 


Copyright © 2001, Graham Jenkins.
Copying license http://www.linuxgazette.com/copying.html
Published in Issue 65 of Linux Gazette, April 2001

Вернуться на главную страницу