root / pykota / trunk / bin / lprngpykota @ 2006

Revision 1878, 13.5 kB (checked in by jalet, 20 years ago)

Another fix for LPRng support debug messages : I'm sure I'm completely stupid now.

  • 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# LPRngPyKota accounting filter
5#
6# PyKota - Print Quotas for CUPS and LPRng
7#
8# (c) 2003-2004 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.12  2004/10/25 17:05:36  jalet
27# Another fix for LPRng support debug messages : I'm sure I'm completely stupid now.
28#
29# Revision 1.11  2004/10/25 15:14:59  jalet
30# Fixed typo in code added to debug LPRng problem
31#
32# Revision 1.10  2004/10/24 09:06:46  jalet
33# Added debug messages for LPRng support possible problem ???
34#
35# Revision 1.9  2004/10/19 21:37:57  jalet
36# Fixes recently introduced bug
37#
38# Revision 1.8  2004/10/13 20:51:27  jalet
39# Made debugging levels be the same in cupspykota and lprngpykota.
40# Now outputs more information in informational messages : user, printer, jobid
41#
42# Revision 1.7  2004/09/13 16:02:44  jalet
43# Added fix for incorrect job's size when hardware accounting fails
44#
45# Revision 1.6  2004/09/02 14:40:13  jalet
46# Another bunch of LPRng fixes
47#
48# Revision 1.5  2004/07/23 11:19:48  jalet
49# 1.19beta is out !
50#
51# Revision 1.4  2004/07/22 22:41:48  jalet
52# Hardware accounting for LPRng should be OK now. UNTESTED.
53#
54# Revision 1.3  2004/07/21 09:35:48  jalet
55# Software accounting seems to be OK with LPRng support now
56#
57# Revision 1.2  2004/07/20 22:47:38  jalet
58# Sanitizing
59#
60# Revision 1.1  2004/07/17 20:37:27  jalet
61# Missing file... Am I really stupid ?
62#
63#
64#
65
66import sys
67import os
68
69from pykota.tool import PyKotaFilterOrBackend, PyKotaToolError, crashed
70from pykota.config import PyKotaConfigError
71from pykota.storage import PyKotaStorageError
72from pykota.accounter import PyKotaAccounterError
73   
74# Exit codes
75JSUCC = 0       # filter succeeded
76JFAIL = 1       # filter failed, print server should retry later
77JABORT = 2      # filter failed, print server should suspend the queue
78JREMOVE = 3     # job will be removed from print queue
79JHOLD = 6       # job will be prevented from printing until lpc release
80
81# Environment variables
82# PRINTER = printer name
83# PRINTCAP_ENTRY = complete printcap entry for this printer
84# HF = job hold file contents
85# SPOOL_DIR = spool directory
86
87# HF contains df_name which is DataFile_Name created in SPOOL_DIR
88
89class PyKotaFilter(PyKotaFilterOrBackend) :       
90    """A class for the pykota filter for LPRng."""
91    def acceptJob(self) :       
92        """Returns the appropriate exit code to tell LPRng all is OK."""
93        return JSUCC
94           
95    def removeJob(self) :           
96        """Returns the appropriate exit code to tell LPRng job has to be removed."""   
97        return JREMOVE
98       
99    def getJobOriginatingHostname(self, printername, username, jobid) :
100        """Retrieves the job-originating-hostname if possible."""
101        try :
102            return [line[11:] for line in os.environ.get("HF", "").split() if line.startswith("remotehost=")][0]
103        except IndexError :   
104            try :
105                return [line[1:] for line in os.environ.get("CONTROL", "").split() if line.startswith("H")][0]
106            except IndexError :   
107                return None
108               
109    def firstPass(self, policy, printer, user, userpquota) :           
110        """First pass done here."""
111        # first we have to check if previous job was correctly accounted for
112        self.logdebug("First pass begins : POLICY_%s => %s => %s" % (policy, getattr(printer, "Name", None), getattr(user, "Name", None)))
113        if printer.LastJob.Exists and not printer.LastJob.JobSize :
114            # here we know that previous job wasn't accounted for correctly
115            # we are sure (?) that it was hardware accounting which was used
116            # and that the second pass didn't work or wasn't even launched
117            # we know have to act just as if we were in second pass
118            # for previous user on this printer, then we will continue
119            # with normal processing of current user.
120            self.secondPass(policy, printer, None, None)
121       
122        # export user info with initial values
123        self.exportUserInfo(userpquota)
124       
125        # tries to extract job-originating-hostname
126        clienthost = self.getJobOriginatingHostname(printer.Name, user.Name, self.jobid)
127        self.logdebug("Client Hostname : %s" % (clienthost or "Unknown"))   
128        os.environ["PYKOTAJOBORIGINATINGHOSTNAME"] = str(clienthost or "")
129       
130        # indicates first pass
131        os.environ["PYKOTAPHASE"] = "BEFORE"
132       
133        # do we want strict or laxist quota enforcement ?
134        if self.config.getPrinterEnforcement(printer.Name) == "STRICT" :
135            self.softwareJobSize = self.precomputeJobSize()
136            self.softwareJobPrice = userpquota.computeJobPrice(self.softwareJobSize)
137            self.logdebug("Precomputed job's size is %s pages, price is %s units" % (self.softwareJobSize, self.softwareJobPrice))
138        os.environ["PYKOTAPRECOMPUTEDJOBSIZE"] = str(self.softwareJobSize)
139        os.environ["PYKOTAPRECOMPUTEDJOBPRICE"] = str(self.softwareJobPrice)
140       
141        # if no data to pass to real backend, probably a filter
142        # higher in the chain failed because of a misconfiguration.
143        # we deny the job in this case (nothing to print anyway)
144        if not self.jobSizeBytes :
145            self.printMoreInfo(user, printer, _("Job contains no data. Printing is denied."), "warn")
146            action = "DENY"
147        else :   
148            # checks the user's quota
149            action = self.warnUserPQuota(userpquota)
150       
151        # exports some new environment variables
152        os.environ["PYKOTAACTION"] = action
153       
154        # launches the pre hook
155        self.prehook(userpquota)
156       
157        self.printMoreInfo(user, printer, _("Job accounting begins."))
158        self.accounter.beginJob(printer)
159       
160        jobsize = None
161        if self.accounter.isSoftware :
162            self.accounter.endJob(printer)
163            jobsize = self.accounter.getJobSize(printer)
164            self.printMoreInfo(user, printer, _("Job accounting ends."))
165           
166        if action == "DENY" :   
167            jobsize = 0
168            self.printMoreInfo(user, printer, _("Job size forced to 0 because printing is denied."))
169           
170        if (self.accounter.isSoftware) or (action == "DENY") :   
171            # update the quota for the current user on this printer
172            self.printMoreInfo(user, printer, _("Job size : %i") % jobsize)
173            self.printInfo(_("Updating user %s's quota on printer %s") % (user.Name, printer.Name))
174            jobprice = userpquota.increasePagesUsage(jobsize)
175           
176            printer.addJobToHistory(self.jobid, user, self.accounter.getLastPageCounter(), action, jobsize, jobprice, self.preserveinputfile, self.title, self.copies, self.options, clienthost, self.jobSizeBytes)
177            self.printMoreInfo(user, printer, _("Job added to history."))
178           
179            # exports some new environment variables
180            os.environ["PYKOTAPHASE"] = "AFTER"
181            os.environ["PYKOTAJOBSIZE"] = str(jobsize)
182            os.environ["PYKOTAJOBPRICE"] = str(jobprice)
183           
184            # then re-export user information with new value
185            self.exportUserInfo(userpquota)
186           
187            # Launches the post hook
188            self.posthook(userpquota)
189           
190            # here accounting was completed, either software, or hardware but over quota
191        else :
192            printer.addJobToHistory(self.jobid, user, self.accounter.getLastPageCounter(), action, filename=self.preserveinputfile, title=self.title, copies=self.copies, options=self.options, clienthost=clienthost, jobsizebytes=self.jobSizeBytes)
193            self.logdebug("Job added to history during first pass : Job's size and price are still unknown.")
194           
195        self.logdebug("First pass ends : ACTION_%s => %s => %s" % (action, getattr(printer, "Name", None), getattr(user, "Name", None)))
196        if action == "DENY" :
197            return self.removeJob()
198        else :   
199            return self.acceptJob()
200       
201    def secondPass(self, policy, printer, user, userpquota) :   
202        """Second pass done here."""
203        # Last job for current printer has the same JobId than
204        # the current job, so we know we are in the second pass
205        self.logdebug("Second pass begins : POLICY_%s => %s => %s" % (policy, getattr(printer, "Name", None), getattr(user, "Name", None)))
206        if self.accounter.isSoftware :
207            # Software accounting method was used, and we are
208            # in second pass, so all work is already done,
209            # now we just have to exit successfully
210            self.printInfo(_("Software accounting already done in first pass. Ignoring."))
211        elif printer.LastJob.JobAction == "DENY" :
212            # Hardware accounting method was used, but job
213            # was rejected during first pass, so nothing to do
214            self.printInfo(_("Hardware accounting already done in first pass. Ignoring."))
215        else :   
216            # here if user and userpquota are both None
217            # then it's a special second pass for a job
218            # which should have had one but didn't, so
219            # we need to get the last user, not the current one.
220            if (user is None) and (userpquota is None) :
221                user = printer.LastJob.User
222                userpquota = self.storage.getUserPQuota(user, printer)
223               
224            # exports user info for last user   
225            self.exportUserInfo(userpquota)
226           
227            # indicate phase change
228            os.environ["PYKOTAPHASE"] = "AFTER"
229           
230            # fakes beginning of job with old page counter
231            self.accounter.LastPageCounter = int(printer.LastJob.PrinterPageCounter or 0)
232            self.accounter.fakeBeginJob()
233            self.logdebug("Fakes beginning of job with LastPageCounter: %s" % self.accounter.getLastPageCounter())
234           
235            # stops accounting.
236            self.accounter.endJob(printer)
237            self.logdebug("Job accounting ends.")
238               
239            # retrieve the job size   
240            jobsize = self.accounter.getJobSize(printer)
241           
242            self.printMoreInfo(user, printer, _("Job size : %i") % jobsize)
243            self.printInfo(_("Updating user %s's quota on printer %s") % (user.Name, printer.Name))
244            jobprice = userpquota.increasePagesUsage(jobsize)
245           
246            self.storage.writeLastJobSize(printer.LastJob, jobsize, jobprice)
247            self.printMoreInfo(user, printer, _("Job size and price now set in history."))
248           
249            # exports some new environment variables
250            os.environ["PYKOTAPHASE"] = "AFTER"
251            os.environ["PYKOTAJOBSIZE"] = str(jobsize)
252            os.environ["PYKOTAJOBPRICE"] = str(jobprice)
253           
254            # then re-export user information with new value
255            self.exportUserInfo(userpquota)
256           
257            # Launches the post hook
258            self.posthook(userpquota)
259           
260            # here hardware accounting was completed.
261        self.logdebug("Second pass ends : POLICY_%s => %s => %s" % (policy, getattr(printer, "Name", None), getattr(user, "Name", None)))
262        return self.acceptJob()
263       
264    def doWork(self, policy, printer, user, userpquota) :   
265        """Most of the work is done here."""
266        # Two different values possible for policy here :
267        # ALLOW means : Either printer, user or user print quota doesn't exist,
268        #               but the job should be allowed anyway.
269        # OK means : Both printer, user and user print quota exist, job should
270        #            be allowed if current user is allowed to print on this printer
271        if policy == "ALLOW" :
272            # nothing to do, just accept the job
273            return self.acceptJob()
274        else :   
275            if printer.LastJob.Exists and (printer.LastJob.JobId == self.jobid) :
276                # here we know we are in second pass.
277                return self.secondPass(policy, printer, user, userpquota)
278            else :   
279                # Last job for current printer has a different JobId than
280                # the current job, so we know we are in the first pass.
281                return self.firstPass(policy, printer, user, userpquota)
282           
283if __name__ == "__main__" :   
284    retcode = JSUCC
285    try :
286        try :
287            # Initializes the backend
288            kotabackend = PyKotaFilter()   
289        except SystemExit :   
290            retcode = JABORT
291        except :   
292            crashed("lprngpykota filter initialization failed")
293            retcode = JABORT
294        else :   
295            retcode = kotabackend.mainWork()
296            kotabackend.storage.close()
297            kotabackend.closeJobDataStream()   
298    except :
299        try :
300            kotabackend.crashed("lprngpykota filter failed")
301        except :   
302            crashed("lprngpykota filter failed")
303        retcode = JABORT
304       
305    sys.exit(retcode)   
Note: See TracBrowser for help on using the browser.