root / pykota / trunk / bin / pykota @ 1144

Revision 1144, 12.0 kB (checked in by jalet, 21 years ago)

Character encoding added to please latest version of Python

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