root / pykota / trunk / bin / pykota @ 1116

Revision 1116, 11.7 kB (checked in by jalet, 21 years ago)

Improvement of the printing system detection code.

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1#! /usr/bin/env python
2
3# PyKota accounting filter
4#
5# PyKota - Print Quotas for CUPS and LPRng
6#
7# (c) 2003 Jerome Alet <alet@librelogiciel.com>
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation; either version 2 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with this program; if not, write to the Free Software
20# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
21#
22# $Id$
23#
24# $Log$
25# Revision 1.38  2003/08/18 16:20:59  jalet
26# Improvement of the printing system detection code.
27#
28# Revision 1.37  2003/07/29 20:55:17  jalet
29# 1.14 is out !
30#
31# Revision 1.36  2003/07/10 06:09:52  jalet
32# Incorrect documentation string
33#
34# Revision 1.35  2003/06/25 14:10:01  jalet
35# Hey, it may work (edpykota --reset excepted) !
36#
37# Revision 1.34  2003/06/13 18:54:17  jalet
38# Bug with remote jobs and LPRng fixed.
39#
40# Revision 1.33  2003/05/28 13:51:38  jalet
41# Better handling of errors
42#
43# Revision 1.32  2003/05/27 23:00:20  jalet
44# Big rewrite of external accounting methods.
45# Should work well now.
46#
47# Revision 1.31  2003/04/30 13:36:39  jalet
48# Stupid accounting method was added.
49#
50# Revision 1.30  2003/04/29 22:03:38  jalet
51# Better error handling.
52#
53# Revision 1.29  2003/04/29 18:37:54  jalet
54# Pluggable accounting methods (actually doesn't support external scripts)
55#
56# Revision 1.28  2003/04/26 08:41:24  jalet
57# Small code reorganisation (UNTESTED) to allow pluggable accounting
58# methods in the future.
59#
60# Revision 1.27  2003/04/25 09:23:47  jalet
61# Debug message passed through !
62#
63# Revision 1.26  2003/04/25 08:23:23  jalet
64# Multiple tries to get the printer's internal page counter, waits for
65# one minute maximum for the printer to warm up, actually.
66#
67# Revision 1.25  2003/04/24 11:53:48  jalet
68# Default policy for unknown users/groups is to DENY printing instead
69# of the previous default to ALLOW printing. This is to solve an accuracy
70# problem. If you set the policy to ALLOW, jobs printed by in nexistant user
71# (from PyKota's POV) will be charged to the next user who prints on the
72# same printer.
73#
74# Revision 1.24  2003/04/23 22:13:56  jalet
75# Preliminary support for LPRng added BUT STILL UNTESTED.
76#
77# Revision 1.23  2003/04/15 11:30:57  jalet
78# More work done on money print charging.
79# Minor bugs corrected.
80# All tools now access to the storage as priviledged users, repykota excepted.
81#
82# Revision 1.22  2003/04/15 11:09:04  jalet
83# Small bug was fixed when a printer was never used and its internal
84# page counter is not accessible.
85#
86# Revision 1.21  2003/04/12 17:20:14  jalet
87# Better formula for HP workaround
88#
89# Revision 1.20  2003/04/12 16:58:28  jalet
90# The workaround for HP printers was not correct, and there's probably no
91# correct way to workaround the problem because we can't save the internal
92# page counter in real time. The last job's size is unconditionnally set to
93# 5 pages in this case.
94#
95# Revision 1.19  2003/04/11 08:56:49  jalet
96# Comment
97#
98# Revision 1.18  2003/04/11 08:50:39  jalet
99# Workaround for the HP "feature" of saving the page counter to NVRAM
100# only every time 10 new pages are printed...
101# Workaround for printers with volatile page counters.
102#
103# Revision 1.17  2003/04/10 21:47:20  jalet
104# Job history added. Upgrade script neutralized for now !
105#
106# Revision 1.16  2003/04/08 20:38:08  jalet
107# The last job Id is saved now for each printer, this will probably
108# allow other accounting methods in the future.
109#
110# Revision 1.15  2003/03/29 13:45:27  jalet
111# GPL paragraphs were incorrectly (from memory) copied into the sources.
112# Two README files were added.
113# Upgrade script for PostgreSQL pre 1.01 schema was added.
114#
115# Revision 1.14  2003/03/07 22:16:57  jalet
116# Algorithmically incorrect : last user quota wasn't updated if current
117# user wasn't allowed to print.
118#
119# Revision 1.13  2003/02/27 23:59:28  jalet
120# Stupid bug wrt exception handlingand value conversion
121#
122# Revision 1.12  2003/02/27 23:48:41  jalet
123# Correctly maps PyKota's log levels to syslog log levels
124#
125# Revision 1.11  2003/02/27 22:55:20  jalet
126# WARN log priority doesn't exist.
127#
128# Revision 1.10  2003/02/27 22:43:21  jalet
129# Missing import
130#
131# Revision 1.9  2003/02/27 22:40:26  jalet
132# Correctly handles cases where the printer is off.
133#
134# Revision 1.8  2003/02/09 12:56:53  jalet
135# Internationalization begins...
136#
137# Revision 1.7  2003/02/07 10:23:48  jalet
138# Avoid a possible future name clash
139#
140# Revision 1.6  2003/02/06 22:54:33  jalet
141# warnpykota should be ok
142#
143# Revision 1.5  2003/02/05 22:45:25  jalet
144# Forgotten import
145#
146# Revision 1.4  2003/02/05 22:42:51  jalet
147# Typo
148#
149# Revision 1.3  2003/02/05 22:38:39  jalet
150# Typo
151#
152# Revision 1.2  2003/02/05 22:16:20  jalet
153# DEVICE_URI is undefined outside of CUPS, i.e. for normal command line tools
154#
155# Revision 1.1  2003/02/05 21:28:17  jalet
156# Initial import into CVS
157#
158#
159#
160
161import sys
162import os
163import time
164
165from pykota.tool import PyKotaTool, PyKotaToolError
166from pykota.config import PyKotaConfigError
167from pykota.storage import PyKotaStorageError
168from pykota.accounter import openAccounter, PyKotaAccounterError
169
170class PyKotaFilter(PyKotaTool) :   
171    """Class for the PyKota filter."""
172    def __init__(self) :
173        PyKotaTool.__init__(self)
174        (self.printingsystem, self.printerhostname, self.printername, self.username, self.jobid, self.inputfile, self.copies) = self.extractInfoFromCupsOrLprng()
175        self.accounter = openAccounter(self)
176   
177    def extractInfoFromCupsOrLprng(self) :   
178        """Returns a tuple (printingsystem, printerhostname, printername, username, jobid, filename) depending on the printing system in use (as seen by the print filter).
179       
180           Returns (None, None, None, None, None, None, None) if no printing system is recognized.
181        """
182        # Try to detect CUPS
183        if os.environ.has_key("CUPS_SERVERROOT") and os.path.isdir(os.environ.get("CUPS_SERVERROOT", "")) :
184            if len(sys.argv) == 7 :
185                inputfile = sys.argv[6]
186            else :   
187                inputfile = None
188               
189            device_uri = os.environ.get("DEVICE_URI", "")
190            # TODO : check this for more complex urls than ipp://myprinter.dot.com:631/printers/lp
191            try :
192                (backend, destination) = device_uri.split(":", 1) 
193            except ValueError :   
194                raise PyKotaToolError, "Invalid DEVICE_URI : %s\n" % device_uri
195            while destination.startswith("/") :
196                destination = destination[1:]
197            printerhostname = destination.split("/")[0].split(":")[0]
198            return ("CUPS", printerhostname, os.environ.get("PRINTER"), sys.argv[2].strip(), sys.argv[1].strip(), inputfile, int(sys.argv[4].strip()))
199        else :   
200            # Try to detect LPRng
201            jseen = Pseen = nseen = rseen = Kseen = None
202            for arg in sys.argv :
203                if arg.startswith("-j") :
204                    jseen = arg[2:].strip()
205                elif arg.startswith("-n") :     
206                    nseen = arg[2:].strip()
207                elif arg.startswith("-P") :   
208                    Pseen = arg[2:].strip()
209                elif arg.startswith("-r") :   
210                    rseen = arg[2:].strip()
211                elif arg.startswith("-K") or arg.startswith("-#") :   
212                    Kseen = int(arg[2:].strip())
213            if Kseen is None :       
214                Kseen = 1       # we assume the user wants at least one copy...
215            if (rseen is None) and jseen and Pseen and nseen :   
216                self.logger.log_message(_("Printer hostname undefined, set to 'localhost'"), "warn")
217                rseen = "localhost"
218            if jseen and Pseen and nseen and rseen :       
219                # job is always in stdin (None)
220                return ("LPRNG", rseen, Pseen, nseen, jseen, None, Kseen)
221        self.logger.log_message(_("Printing system unknown, args=%s") % " ".join(sys.argv), "warn")
222        return (None, None, None, None, None, None, None)   # Unknown printing system         
223       
224    def acceptJob(self) :       
225        """Returns the exit code needed by the printing backend to accept the job and print it."""
226        if self.printingsystem == "CUPS" :
227            return 0
228        elif self.printingsystem == "LPRNG" :   
229            return 0
230        else :   
231            # UNKNOWN
232            return -1
233           
234    def removeJob(self) :           
235        """Returns the exit code needed by the printing backend to refuse the job and remove it."""
236        if self.printingsystem == "CUPS" :
237            return 1
238        elif self.printingsystem == "LPRNG" :   
239            return 3
240        else :   
241            # UNKNOWN
242            return -1
243           
244def main(thefilter) :   
245    """Do it, and do it right !"""
246    #
247    # If this is a CUPS filter, we should act and die like a CUPS filter when needed
248    if thefilter.printingsystem == "CUPS" :
249        if len(sys.argv) not in (6, 7) :   
250            sys.stderr.write("ERROR: %s job-id user title copies options [file]\n" % sys.argv[0])
251            return thefilter.removeJob()
252   
253    # Get the last page counter and last username from the Quota Storage backend
254    printer = thefilter.storage.getPrinter(thefilter.printername)
255    if not printer.Exists :
256        # The printer is unknown from the Quota Storage perspective
257        # we let the job pass through, but log a warning message
258        thefilter.logger.log_message(_("Printer %s not registered in the PyKota system") % thefilter.printername, "warn")
259    else :   
260        user = thefilter.storage.getUser(thefilter.username)
261        if not user.Exists :
262            # The user is unknown from the Quota Storage perspective
263            # Depending on the default policy for this printer, we
264            # either let the job pass through or reject it, but we
265            # log a message in any case.
266            policy = thefilter.config.getPrinterPolicy(thefilter.printername)
267            if policy == "ALLOW" :
268                action = "POLICY_ALLOW"
269            else :   
270                action = "POLICY_DENY"
271            thefilter.logger.log_message(_("User %s not registered in the PyKota system, applying default policy (%s) for printer %s") % (thefilter.username, action, thefilter.printername), "warn")
272            if action == "POLICY_DENY" :
273                return thefilter.removeJob()
274        else :
275            # Now does the accounting and act depending on the result
276            action = thefilter.accounter.doAccounting(printer, user)
277           
278            # if not allowed to print then die, else proceed.
279            if action == "DENY" :
280                # No, just die cleanly
281                return thefilter.removeJob()
282       
283    # pass the job untouched to the underlying layer
284    thefilter.accounter.filterInput(thefilter.inputfile)     
285   
286    return thefilter.acceptJob()
287
288if __name__ == "__main__" :   
289    retcode = -1
290    try :
291        # Initializes the current tool
292        kotafilter = PyKotaFilter()   
293        retcode = main(kotafilter)
294    except (PyKotaToolError, PyKotaConfigError, PyKotaStorageError, PyKotaAccounterError, AttributeError, KeyError, IndexError, ValueError, IOError), msg :
295        sys.stderr.write("ERROR : PyKota filter failed (%s)\n" % msg)
296        sys.stderr.flush()
297        retcode = -1
298
299    try :
300        kotafilter.storage.close()
301    except (TypeError, NameError, AttributeError) :   
302        pass
303       
304    sys.exit(retcode)   
Note: See TracBrowser for help on using the browser.