libhideprinters: Hides printers from applications Copyright (C) 2005 Revolution Linux inc, Jean-Michel Dault jmdault@revolutionlinux.com This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ---------------------------------------------------------------------------- RATIONALE: Most organizations have multile printers. When an organization has a mix of machines under different operating systems, setting up a central Cups server makes sense. Mac and Linux users can natively use Cups, while Windows users can print to a Samba server that is mapped to Cups. Unfortunately, even though Cups has an access mechanism to control who can print to which printers, it has no such mechanism for browsing. Even if the user doesn't have access to a printer, he still sees it on the printer list of his application. Imagine a school district with 50 different schools, and multiple printers per school. Some printers are available only to students, some are for administrative use, etc. If we setup all these printers on a Cups server, all applications will see every printer. Imagine having 1,000 printers in Mozilla and having to choose the right one! Was it 007450OptraN, or maybe 069352HP4L? PARTIAL SOLUTIONS: Some programs, like gtklp and kdeprint in kiosk mode, have configurable filters so that the user will only see the printers he has access to. However, these programs replace only lpr, and that's not enough: - Mozilla, Gimp and Scribus call cupsGetDests to fill its printer list - Some versions of OpenOffice use cups, while others fallback to doing a "lpc status" - kprinter uses cupsDoRequest and directly parses the cups attributes So even though lpr is modified, they can still see all the printers! It's possible to use brute-force and do a: perl -pi -e "s|libcups|libkups|g;" on the binaries to get rid ot the printer list and have only a default printer and to call gtklp/kprinter and choose the printer afterwards, but then the problem is you have a double window problem: the user first prints on the default printer, then needs choose the right printer. But with this approach, you have to modify every application and redo the job each time someone upgrades any application. Another problem is that Mozilla and OpenOffice call lpr in a blocking way: they wait for the result code of the program. Meanwhile, if the user moves the kprinter or gtklp window, the backgound does not refresh and it gives the impression that the application is frozen. OK, it's possible to write a wrapper script that calls gtklp or lpr in the background, but then you have to make sure that printing a file works as well as printing from standard output, that everything is escaped properly, etc. So we needed to find something better! THE FULL SOLUTION: We decided to create a shim that wraps around libcups, and lists only specified printers. It works, since: - lpr-cups, lpstat-cups, lpc-cups, lp-cups are all replacement for standard lp/lpr programs, and use cups directly - gtklp uses cups - kprinter uses cups - Mozilla uses cups - OpenOffice uses cups HOW IT WORKS: We modify the /etc/profile scripts to have: LD_PRELOAD=libhidebups.so The library is loaded by the linker for every program and overrides functions in the standard cups library. We disable listing of every printer unless there is a file with the printer name in the .gtklp/accept directory in the user's home. We chose this scructure because that's what gtklp uses, and it's much easier to do a stat() on a file than it is to parse a config file. For example, if there are three printers (foo, bar and baz), there is a file called /home/peter/.gtklp/accept/bar on the system, peter can only print on the printer called "bar". This way, we can easily have scripts that fetch the permissions from an LDAP directory and create a file for each printer the user has access to. This is an example of a script that is placed in Xsession: ---------- snip ------------- rm -f $HOME/.gtklp/accept/* export PRINTER=`/usr/bin/ldap_getdefaultprinter $USER $MACHINE` touch $HOME/.gtklp/accept/$PRINTER for printer in `/usr/bin/ldap_getallprinters $USER $MACHINE` do touch $HOME/.gtklp/accept/$printer done ---------- snip ------------- The script ldap_getallprinters returns a list of printers, one by line, while the ldap_getdefaultprinter returns only the default printer. The $MACHINE variable lets us select printers depending on where the user is logged on. These scripts are not supplied with this software, because they vary greatly from organization to organization. However, our consulting department can integrate our system into any environment: see below for our contact information. If a user has no /home or no .gtklp/accept/*, he will not be able to see any printers, but will be able to save in PostScript of PDF via most applications. LIMITATIONS: - This version is limited to 2048 printers. If there are more than 2048 printers, some will not be shown. Modify the MAX_PRT definition in libhideprinters.c to increase it. TECHNICAL INFO: The libhideprinters.so shim does this: - Override cupsDoFileRequest so we can remove unwanted printers. This function intercepts all requests to the Cups server. For every printer, it checks for the presence of a file named $HOME/.gtklp/accept/<NAME>. If the file doesn't exist, it removes the printer from the Cups request. A cups request consists of a series of attributes, grouped together. The structure in memory is a list, and each record contains a pointer to the next attribute. To hide a printer, we set the pointer so it skips to the next printer's first attribute, or NULL if it is the last printer. - Override cupsGetDest because if we remove the default printer in the previous function, there will be no default printer. Mozilla absolutely needs one, otherwise it will segfault. Cups uses the PRINTER environment variable by default. If the variable is not present, or the printer does not exist, it will use the default printer in Cups. If there is none, it returns NULL. We check for this NULL value and replace it instead with the first available printer. CREDITS: - This program is based on some code found in Linux Journal: "Modifying a Dynamic Library Without Changing the Source Code" http://www.linuxjournal.com/article/7795 - Code for showprinters.c was taken from the Mozilla Cups patch from Michael Kaply (IBM) <mozilla@kaply.com> CONTACT US: Don't hesitate to contact the authors for more information, questions or suggestions for this program. Commercial support contracts are also available for this product. Jean-Michel Dault jmdault@revolutionlinux.com Revolution Linux inc. 145 Sauve Sherbrooke (Quebec) J!J 1L6 CANADA (819) 780-8955