Cover V09, I06
Article

jun2000.tar


LPRng

Wyman Eric Miles

In the beginning, UNIX printing was nothing more than a line printer attached to a serial port. The system was rugged, reliable, and the points of failure were known. As printers became network aware, they could be placed near their users without the need for a nearby print server. Berkeley LPR, long the mainstay of UNIX printing, struggled to keep pace with printer technology. Maintaining a large network of printers spread over an entire campus became increasingly difficult.

The UNIX vendors created their own variations on the theme. The Solstice printing software has undergone major revisions with each new release of Solaris. Berkeley LPR is continually improved but still suffers from some of its early flaws.

At Rice University, we maintain an academic network that enjoys heavy use 24 hours a day, 7 days a week, year round. In response to the varied needs of class work and faculty printing, we've deployed upwards of 50 different printers across our 40-acre campus. Some labs have several HP printers that we'd like to operate as a single gang to distribute the load. Other labs have various types of printers, each supporting several different kinds of media: letterhead, legal, drilled, and tabloid papers. Our operations center has several Tektronix color printers that provide resume, transparency, glossy-film, and architectural-size color poster prints. We've recently added two new printers for high-volume drilled, collated, and stapled work. Under our previous printing system, Berkeley LPR under SunOS 4, maintaining such an infrastructure was time consuming. Without LPRng, which we started using two years ago, our older printing system was unreliable and inflexible to the degree that some of our fancier equipment would be an unthinkable addition.

In our environment, which consists of several varieties of Sun and SGI workstations. LPR created several headaches. Not only did it create unnecessary security holes but, by requiring local spool space for each machine, frequently prevented large jobs from reaching the print server. LPRng solved both issues with little more than a few symbolic links on each workstation. Also, because LPRng allows printcaps to be disstributed through NIS or NIS+, we could potentially manage new printer rollouts easily. Under Berkeley LPR, we had to deliver a new printcap to each host. Worse still, when our Solaris hosts were using the Sunsoft PrintClient, we had to generate a new /etc/printers.conf each time a printcap was changed.

LPRng is the next generation LPR. It is designed to support large scale printing infrastructures, network printing, printer accounting, printer banking, hold queues, and routed queues. It is fully compliant with RFC 1179, meaning that LPRng functions as a drop-in replacement on any system that would normally support Berkeley LPR. LPRng offers improved security because it doesn't run setuid-root. It also supports a variety of authentication mechanisms, including Kerberos 4 and 5. LPRng is generally faster than vendor LPR implementations and is much easier to maintain. LPRng was written by Patrick Powell (http://www.astart.com/lprng/LPRng.html) and is Open Source software distributed under the GNU Public License.

Configuring and Installing LPRng

The following steps have been tested under Solaris 2.6. There should be little difference under any other UNIX. Copy the latest stable distribution to the appropriate directory (/usr/local, for example):

# cd /usr/local
Unpack the source:

# gunzip LPRng-stable.tgz
Untar the source:

# tar xvf LPRng-stable.tar
Change directories:

# cd LPRng-3.6.12
The INSTALL file gives a thorough explanation of compiler requirements, installation locations, and GNU autoconfigure options. By default, the LPRng makefiles will require gnumake or BSD make. LPRng does not need to be setuid-root. For maximum security, it should be configured to run without requiring root privileges. To configure the build process for your operating system:

# ./configure  --disable-setuid
The autoconfigure script may generate error messages along the way. Generally, all goes well, however. Finally:

# make
# make install
LPRng will be placed in ..

Configuring LPRng: Permissions and Printcaps

In contrast to Berkeley LPR, with its /etc/printcap and /etc/hosts.lpd, or the Solstice printers.conf, LPRng uses three intuitive configuration files:

lpd.conf -- This file is read by the LPRng lpd daemon at startup and defines such things as printcap path, permissions file, and log file paths.

lpd.perms -- Similar in purpose to Berkeley's hosts.lpd, the lpd.perms file allows administrators to specify hosts that may submit or remove jobs, specify users and groups that may control print queues, and delegate printer maintenance tasks.

lpd_printcap -- The lpd_printcap is used on the print server to define print queues and their characteristics. It contains entries for spool directories, print filters, banner page generators, log file locations, printer connect time-outs, and a plethora of other details for each printer. Client machines use a standard /etc/printcap like Berkeley LPR or Solstice.

The first configuration issue is the lpd.conf. Since most administrators are familiar with traditional LPR implementations, we've configured our LPRng installation to closely mimic earlier printing systems. The example distributed with LPRng includes elaborate definitions of each configuration option. Ours is very simple:

# this file:
config_file = /etc/lpd.conf
# location of the lpd_printcap
lpd_printcap_path = /etc/lpd_printcap
# the log file used by lpd
logfile = /var/spool/lpd/log
# the permissions file
perms_path = /etc/lpd.perms
Next is the lpd.perms file. One of the best features of LPRng is the ability to finely tune control of the printing system. It is possible to grant lpc access and the ability to start and stop print queues to a trusted base of user services, consultants, or operators while restricting access from the user community at large. The lpd.perms file consists of a series of directives along with their values. For example:

Reject Service or Accept Service

These directives expect a service type, which may be one of the following:

X -- instructs LPRng to compare the remote hostname to those on the list.

M -- applies the permission to lprm, the ability to remove jobs.

R -- applies to lpr, the ability to submit jobs.

C -- applies to lpc, which is used to control the queuing system as a whole.

Q -- corresponds to lpq, which may be useful to allow or prevent certain hosts or groups from checking the print queue once jobs have been entered.

REMOTEHOST

The remote host line is a comma-separated list of hosts or wild cards from which LPRng will accept or refuse service.

REMOTEUSER

LPRng accepts a comma-separated list of usernames to which the current rule applies. This is useful for granting enhanced control over LPRng to certain trusted users.

SAMEUSER

SAMEUSER implies that access to a specific function, generally deleting jobs, may only be done by the individual who originally submitted them.

REMOTEGROUP

REMOTEGROUP expands the rule to include any regular user who is a member of the specified UNIX group.

Beware that it is easy to grant queue management permissions to hosts or groups that really shouldn't have it. On a student Linux machine, root can remove jobs, start and stop queues, and cause as much disruption as root on a protected server can unless a great deal of thought is applied to the lpd.perms directives. Accordingly, we allow users to add or remove their own jobs from any machine on the academic network. Likewise, root can add or remove jobs from any host over which we have control. Stripped of its copious comments, here is our lpd.perms:

# accept jobs only from allowed servers at Rice
REJECT SERVICE=X NOT \ REMOTEHOST=*.owlnet.rice.edu,  /
    *.is.rice.edu,*.ruf.rice.edu,localhost
The above directive instructs the print server to reject any job that does not originate from one of the listed domains or the printer host itself. Because we control those three domains and have a single print server for our entire group of networked printers, the above rule works quite well. Users on the academic, staff, or information systems networks can submit jobs to the printing system.

# allow root on server to control jobs
ACCEPT SERVICE=C SERVER REMOTEUSER=root
Because we control the above domains, we'd like to be able to control the printing system from any workstation we commonly use. This allows root to control jobs from anywhere. However, if there is an LPRng-equipped UNIX host within the range of hosts specified in the first line, this will grant root control of the print queues. It's best to confine LPRng only to those hosts you explicitly control.

# allow same user to remove a job
ACCEPT SERVICE=M SAMEUSER \  / 
   REMOTEHOST=*.owlnet.rice.edu, /
   *.is.rice.edu,is.rice.edu, *.ruf.rice.edu
 # allow root to remove a job
ACCEPT SERVICE=M REMOTEUSER=root \   /
   REMOTEHOST=*.owlnet.rice.edu,*.is.rice.edu, /
   is.rice.edu,*.ruf.rice.\
edu,localhost
The SAMEUSER directive allows a user to submit a job from any host on the list and later remove the job from the queue from any other host on the list. In contrast to our previous printing system, which required the user to remove the job from the same host that submitted it, this is a big bonus for LPRng. Root can also remove jobs from any host we trust, rather than only from the print server. LPRng gives us considerable flexibility and eliminates a user education issue by behaving in an intuitive, “network-aware” manner.

Additionally, we have a group of student and staff consultants who assist the user community on a variety of issues. Printing is invariably the final stage of almost any project, so it becomes a common source of questions and difficulty. Someone may accidentally send a job several times or, while waiting for a heavily loaded printer to work its way to their job, send the job repeatedly in the hopes that computers can sense impatience. In any event, we'd like to delegate one of the biggest clean up tasks to the group that has the closest interaction with the users. With LPRng, we can allow consultants to remove jobs and, under some circumstances, give them control of the print queue:

ACCEPT SERVICE=M REMOTEGROUP=consult \  /
   REMOTEHOST=*.owlnet.rice.edu, /
   *.is.rice.edu,is.rice.edu,*.ruf.rice. \
edu,localhost
This grants any member of group “consult” the ability to remove jobs. To give them full control of the print queues, including the ability to start and stop queuing, hold jobs, and redirect one printer to another:

ACCEPT SERVICE=C REMOTEGROUP=consult \ /
   REMOTEHOST=*.owlnet.rice.edu,  /
   *.is.rice.edu,is.rice.edu,*.ruf.rice. \
edu,localhost
Finally, the lpd_printcap is used to describe the characteristics of each print queue. Particularly important is the tc= directive, which includes an entire previous series of configuration values with a single entry. A typical example for one of our Appletalk printers is:

@commonLW
        :server:\
        :lp=/var/spool/lpd/%P/lock:\
        :if=/usr/site/cap/bin/psif:\
        :sd=/var/spool/lpd/%P:\
        :lf=/var/spool/lpd/%P/errs:\
        :af=/var/spool/lpd/%P/acct:\
        #turn off “local printer accounting”
        :la@
The %P value is a replaceable parameter that contains the queue name. lp is the lock file path and is used by lpd to ensure two jobs are not sent to the printer simultaneously. if, the network filter, is responsible for delivering the data directly to the printer. sd is the spool directory. lf is the error log file. af is the accounting file where LPRng will record the user, date, time, and number of pages printed for each job. Disabling local printer accounting prevents LPRng from entering distracting but occasionally useful debugging information in the accounting file. The server directive instructs LPRng that this entry is for the use of lpd, not by client programs like lpr or lpq.

To define a single printer entry like the above, we add:

md200n:\
      :tc=@commonLW:
The printer md200n will inherit the configuration we've called “commonLW”. After defining each printer in the lpd_printcap, it's time to create a startup script and kick off the daemon. The LPRng package includes an example startup script suitable for SYS V systems. It can easily be adapted for BSD use. At the most basic level, the script needs to start the lpd daemon:

# /usr/local/sbin/lpd
Another convenience of LPRng is the ability to force it to reread the lpd.conf, lpd.perms, and lpd_printcap on the fly by sending it a HUP signal. As a trusted user from an appropriate host, this can also be accomplished via lpc:

# /usr/local/sbin/lpc reread
For sites with dozens or even hundreds of printers, creating each spool directory and populating it with the necessary skeleton files can become tiresome and prone to errors, even when scripted. LPRng handles this expertly with checkpc. checkpc stands for “check printcap” and exists to read through the lpd_printcap and display any discrepancies between the current configuration and the spool directory structure and permissions on the print server. When run as root with the -f flag, checkpc will automatically create all necessary spool directories, populate them with files, and set the correct ownership and permissions on the fly:

# /usr/local/sbin/checkpc -f
That's it! Try it out:

% lpr -Pmd200n /etc/motd
LPRng can replace existing lp and lpr binaries on most systems. You can either remove /usr/bin/lp and /usr/bin/lpstat or symbolically link those to /usr/local/sbin/lp and /usr/local/sbin/lpstat. The same is true for /usr/ucb/lpr and /usr/ucb/lprm. We replace the existing binaries here to eliminate path confusion and pick up the bonus that a few more questionable setuid-root programs are out of service.

Once the print server is working correctly, all that remains is to create and distribute printcaps to every client workstation. Each client will need LPRng installed either on the local disk or from some NFS-mounted location. Remote printcaps follow the same familiar syntax we've always used:

md200n:\
      :lp=:rm=is.rice.edu:rp=md200n:sd=/var/spool/lpd/md200n:
Such an entry tells lpr to submit jobs for md200n to a queue with that name on is.rice.edu. In a pinch, it's possible to send jobs to the print server without having a local printcap entry:

lpr -Pmd101ps@marsh.owlnet.rice.edu netscape.ps
That is the equivalent of having the following in your printcap:

md101ps:\
  :lp=:rm=marsh.owlnet.rice.edu:rp=md101ps:sd=  /
   /var/spool/lpd/md101ps:
For our large clusters of workstations, we distribute printcaps via SSLrsh, but passwordless ssh or even rcp will work. LPRng even allows you to include printcaps as an NIS map for those sites already using NIS.

Print Server Performance Issues

Our original printing system, prior to the day we embraced LPRng and never looked back, was built around a Sun LX. From that machine, we served 30 different printers spread over a 40-acre campus and took jobs via lpr , Samba, and CAP from hundreds of machines. CPU performance was not, by and large, a limiting factor. This doesn't mean that anemic hardware makes an acceptable printing system. It does illustrate that sites on a budget can easily serve large printing demands with something as simple as an older Pentium running Linux.

When we moved to LPRng, we also shifted printing from the LX to a Sun Enterprise 450. Postscript filters, dozens of lpd processes, network delivery to nearly 50 printers of various types ranging from HP4s to the latest Tektronix color and HP Mopier line, and ultimately a few thousand jobs dispatched per day doesn't begin to tax the machine.

In our experience, the most critical issue (aside from hardware and operating system reliability) is plenty of spool space. Today's applications, coupled with fast CPUs and faster networks, are able to churn out vast quantities of Postscript in short order. Even simple jobs are (once downloadable fonts are thrown in) nearly a megabyte each. Color prints on letter or legal-sized paper rapidly exceed 10 MB and full tabloid bleeds, architectural plots, and color transparencies seem to grow without bound. During our busiest printing season, a dozen jobs queued for a single printer, each at a megabyte, times 50 printers is common. Without a massive amount of spool space, at least a gigabyte (and far more if your site prints a lot of color) printing will rapidly grind to a halt.

As unlikely as it would seem, a printing system generates a great deal of network congestion. A single print server must receive a job then direct it back out to the printer, doubling the impact on bandwidth. There is no good way to predict the impact a large printing system will cause. Only experience and MRTG can tell. It is wise to account for this in the planning stage, however.

A great deal of planning involves your own systems administration philosophy. Printing tasks can be divided among several servers that employ LPRng's excellent failover capabilities. Or, as we have done here, a single server forms a funnel for all our printing work. It gives us the advantage that printer accounting, diagnostics, and new printer deployment are all handled from a single location. Provided that host meets the availability requirements, “putting all your eggs in one basket” is not necessarily risky. Multiple servers reduce the chance that any single failure will cripple printing. Disk space needs and bandwidth impact are also spread across multiple hosts.

Printer Filters and Drivers

Printing filters are necessary to convert, or tidy up, incoming jobs so that they are suitable for the printer. They can consist of scripts to convert plain text to Postscript. Some filters remove extraneous end-of-file markers from the job (HP printer drivers under Windows are notorious for this) or convert TBCP (Tagged Binary Control Postscript -- Postscript for serial printers) to a more benign form. The LPRng Web page has links to a2ps (http://www.infres.enst.fr/~demaille/a2ps). A2ps, the Any to Postscript package, handles just about any form of input from plain text to nroff or HTML and generates Postscript output. It can also do various forms of pretty-printing, including 2- and 4-up, borders and frames, etc. The package includes style sheets for just about any language from awk to yacc, to print source with syntax highlighting. If the majority of your work consists of output from office suites, page layout packages, or anything else that generates Postscript or PCL, a formatting filter like a2ps may not be necessary.

Most network printers use a protocol called Appsocket. The printer listens on TCP port 9100 for incoming jobs and processes them as they arrive. Appsocket provides some limited two-way communication so the printer can answer queries from the driver for page count, consumable status, configuration, and error history. The LPRng Web page has links to printer drivers for the most common Appsocket-capable printers available today, including Hewlett-Packard, Tektronix, Xerox, and others. These drivers are able to handle printer accounting by asking the printer the start and end page counts -- each printer keeps an internal odometer of its page count. As vendors change printer designs, the drivers are updated.

LPRng Advanced Tricks

Improved security and ease of configuration barely scratch the surface of LPRng's capabilities. LPRng opens an unlimited world of printing capabilities. One of the most important to us is printer banking. In the Berkeley LPR days, a lab that required three printers to meet the demand had three independent printers. If the users selected one preferentially over the others, or if a printer failed, the efficiency of the whole process dropped almost to zero. With LPRng, we can treat a cluster of printers as a logical unit and allow the printing system to deliver jobs to printers as they become ready. Print jobs enter a single queue and are delivered to each printer in turn. A typical printer bank in lpd_printcap looks like this:

ryonps:\
        :server
        :lf=/var/spool/lpd/ryonps/errs
        :sv=ryon1.1ps,ryon1.2ps
        :sd=/var/spool/lpd/ryonps:


ryon1.1ps:\
        :tc=@commonHP:

ryon1.2ps:\
        :tc=@commonHP:
This tells the lpd daemon that the ryonps queue actually consists of two physical printers that sit side-by-side in the lab -- ryon1.1ps and ryon1.2ps As print jobs arrive, they are delivered to each printer in turn. This distributes the load among the two machines equally and makes the whole system resistant to a single failure.

LPRng takes printer banking a step further with a more generic concept known as routed print queues. In a routed print queue, incoming jobs are handed to an administrator-defined script for processing. The script is given a copy of the job's control file, that includes the queue name, remote user name, remote host name, and location of the data file containing the job itself. Here's a routed queue in lpd_printcap:

md101ps:\
        :server:\
        :bq=bounce:\
        :lf=/var/spool/lpd/md101ps/errs:\
        :destinations=md101ps1@localhost,  /
  md101ps2@localhost,md101ps3@localhost:\
        :sd=/var/spool/lpd/md101ps:\
        :router=/usr/site/mdriver/bin/bank.pl mudd101ps:
The destinations line tells the lpd daemon which physical printers are likely to receive jobs as they are processed by this queue. In the above example, the bank.pl script randomly selects one of the three physical printers from a configuration file, attempts to reach it with ping, and dispatches the job to that printer if it is reachable, or to the next living printer if it is not. In a public space, where printers are frequently turned off, this technique keeps jobs flowing around a failure.

We have extended the routed queue concept to include email notification and a mechanism to determine job type. While lpd can perform its own email notification, we prefer our own solution since we can customize the message. When a job enters the routed queue, a Perl script immediately forks a child process to monitor the queue. The parent process instructs LPRng by returning a few simple instructions through stdout, exits while the child waits for the job to finish, then sends mail. All a routing script needs to do is to tell the lpd process something like this:

dest md101ps1@localhost
copies 1
end
In another use of the routed queue, we want to take both paper and transparency jobs in from a single printer name presented to the users, but divide them behind the scenes to the appropriate printer. Because the routing script knows where the spooled data exists, it can quickly analyze the Postscript for “(Paper)” or “(Transparency)” phrases gleaned from the manufacturer's own PPD (a printer definition file format originally used on the Macintosh but now quite ubiquitous). The script then bounces the job over into the appropriate queue for processing.

Finally, LPRng has the ability to redirect print queues on the fly. Suppose a printer has failed but one nearby is still in service; the simplest solution is to move all the jobs from the failed printer to its backup, then automatically redirect all incoming jobs to the other machine:

lpc move md101ps1 * muddps
lpc redirect md101ps1 muddps
When the printer is finally repaired, everything returns to normal:

lpc redirect md101ps1 off
Conclusion

LPRng is a flexible, powerful printing implementation for UNIX that can seamlessly replace vendor printing systems. It offers greatly improved security and reliability. Through printer banking, routed queues, automatic redirection, and a myriad of configuration options, LPRng can serve just about any printing need on any scale.

About the Author

Wyman Eric Miles works as a Senior Systems Administrator for Rice University. In 8 years of work as a UNIX administrator for several universities, he's maintained large networks of SCO, AT&T Sys V, Sun, SGI, Linux, and HP systems.