root / pykota / trunk / bin / pykota @ 1117

Revision 1117, 11.8 kB (checked in by jalet, 21 years ago)

New pychecker pass, on the tools this time.

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