root / pykota / trunk / bin / pykota @ 1176

Revision 1176, 14.1 kB (checked in by jalet, 21 years ago)

French variable name

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