root / pykota / trunk / bin / pykota @ 1177

Revision 1177, 14.2 kB (checked in by jalet, 21 years ago)

CUPS backend added for people to experiment.

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