root / pykota / trunk / bin / cupspykota @ 1180

Revision 1180, 12.1 kB (checked in by jalet, 20 years ago)

More work on new backend. This commit may be unstable.

  • 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# CUPSPyKota accounting backend
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.3  2003/11/12 23:27:44  jalet
27# More work on new backend. This commit may be unstable.
28#
29# Revision 1.2  2003/11/12 09:33:34  jalet
30# New CUPS backend supports device enumeration
31#
32# Revision 1.1  2003/11/08 16:05:31  jalet
33# CUPS backend added for people to experiment.
34#
35#
36#
37
38import sys
39import os
40import time
41import cStringIO
42import shlex
43
44from pykota.tool import PyKotaTool, PyKotaToolError
45from pykota.config import PyKotaConfigError
46from pykota.storage import PyKotaStorageError
47from pykota.accounter import openAccounter, PyKotaAccounterError
48from pykota.requester import openRequester, PyKotaRequesterError
49
50class PyKotaBackend(PyKotaTool) :   
51    """Class for the PyKota backend."""
52    def __init__(self) :
53        PyKotaTool.__init__(self)
54        (self.printingsystem, \
55         self.printerhostname, \
56         self.printername, \
57         self.username, \
58         self.jobid, \
59         self.inputfile, \
60         self.copies, \
61         self.title, \
62         self.options, \
63         self.originalbackend) = self.extractCUPSInfo()
64        self.accounter = openAccounter(self)
65   
66    def extractCUPSInfo(self) :   
67        """Returns a tuple (printingsystem, printerhostname, printername, username, jobid, filename, title, options, backend).
68       
69           Returns (None, None, None, None, None, None, None, None, None, None) if no printing system is recognized.
70        """
71        # Try to detect CUPS
72        if os.environ.has_key("CUPS_SERVERROOT") and os.path.isdir(os.environ.get("CUPS_SERVERROOT", "")) :
73            if len(sys.argv) == 7 :
74                inputfile = sys.argv[6]
75            else :   
76                inputfile = None
77               
78            # the DEVICE_URI environment variable's value is
79            # prefixed with "cupspykota:" otherwise we wouldn't
80            # be called. We have to remove this from the environment
81            # before launching the real backend.
82            fulldevice_uri = os.environ.get("DEVICE_URI", "")
83            device_uri = fulldevice_uri[len("cupspykota:"):]
84            os.environ["DEVICE_URI"] = device_uri
85            # TODO : check this for more complex urls than ipp://myprinter.dot.com:631/printers/lp
86            try :
87                (backend, destination) = device_uri.split(":", 1) 
88            except ValueError :   
89                raise PyKotaToolError, "Invalid DEVICE_URI : %s\n" % device_uri
90            while destination.startswith("/") :
91                destination = destination[1:]
92            printerhostname = destination.split("/")[0].split(":")[0]
93            return ("CUPS", \
94                    printerhostname, \
95                    os.environ.get("PRINTER"), \
96                    sys.argv[2].strip(), \
97                    sys.argv[1].strip(), \
98                    inputfile, \
99                    int(sys.argv[4].strip()), \
100                    sys.argv[3], \
101                    sys.argv[5], \
102                    backend)
103        else :   
104            self.logger.log_message(_("Printing system unknown, args=%s") % " ".join(sys.argv), "warn")
105            return (None, None, None, None, None, None, None, None, None, None)   # Unknown printing system         
106       
107def format_commandline(prt, usr, cmdline) :           
108    """Passes printer and user names on the command line."""
109    printer = prt.Name
110    user = usr.Name
111    # we don't want the external command's standard
112    # output to break the print job's data, but we
113    # want to keep its standard error
114    return "%s >/dev/null" % (cmdline % locals())
115
116def main(thebackend) :   
117    """Do it, and do it right !"""
118    #
119    # Get the last page counter and last username from the Quota Storage backend
120    printer = thebackend.storage.getPrinter(thebackend.printername)
121    if not printer.Exists :
122        # The printer is unknown from the Quota Storage perspective
123        # we let the job pass through, but log a warning message
124        thebackend.logger.log_message(_("Printer %s not registered in the PyKota system") % thebackend.printername, "warn")
125        action = "ALLOW"
126    else :   
127        for dummy in range(2) :
128            user = thebackend.storage.getUser(thebackend.username)
129            if user.Exists :
130                break
131            else :   
132                # The user is unknown from the Quota Storage perspective
133                # Depending on the default policy for this printer, we
134                # either let the job pass through or reject it, but we
135                # log a message in any case.
136                (policy, args) = thebackend.config.getPrinterPolicy(thebackend.printername)
137                if policy == "ALLOW" :
138                    action = "POLICY_ALLOW"
139                elif policy == "EXTERNAL" :   
140                    commandline = format_commandline(printer, user, args)
141                    thebackend.logger.log_message(_("User %s not registered in the PyKota system, applying external policy (%s) for printer %s") % (thebackend.username, commandline, thebackend.printername), "info")
142                    if os.system(commandline) :
143                        # if an error occured, we die without error,
144                        # so that the job doesn't stop the print queue.
145                        thebackend.logger.log_message(_("External policy %s for printer %s produced an error. Job rejected. Please check PyKota's configuration files.") % (commandline, thebackend.printername), "error")
146                        return 0
147                    else :   
148                        # here we try a second time, because the goal
149                        # of the external action was to add the user
150                        # in the database.
151                        continue 
152                else :   
153                    action = "POLICY_DENY"
154                thebackend.logger.log_message(_("User %s not registered in the PyKota system, applying default policy (%s) for printer %s") % (thebackend.username, action, thebackend.printername), "warn")
155                if action == "POLICY_DENY" :
156                    # if not allowed to print then die, else proceed.
157                    # we die without error, so that the job doesn't
158                    # stop the print queue.
159                    return 0
160                # when we get there, the printer policy allows the job to pass
161                break   
162                   
163        if user.Exists :
164            # Is the current user allowed to print at all ?
165            action = thebackend.warnUserPQuota(thebackend.storage.getUserPQuota(user, printer))
166        elif policy == "EXTERNAL" :               
167            # if the extenal policy produced no error, but the
168            # user still doesn't exist, we die without error,
169            # so that the job doesn't stop the print queue.
170            thebackend.logger.log_message(_("External policy %s for printer %s couldn't add user %s. Job rejected.") % (commandline, thebackend.printername, thebackend.username), "error")
171            return 0
172       
173    if action not in ["ALLOW", "WARN"] :   
174        # if not allowed to print then die, else proceed.
175        # we die without error, so that the job doesn't
176        # stop the print queue.
177        retcode = 0
178    else :
179        # pass the job untouched to the underlying layer
180        # and starts accounting at the same time
181        thebackend.accounter.beginJob(printer, user)
182       
183        # executes original backend
184        #mustclose = 0   
185        #if thebackend.inputfile is not None :   
186        #    if hasattr(thebackend.inputfile, "read") :
187        #        infile = thebackend.inputfile
188        #    else :   
189        #        infile = open(thebackend.inputfile, "rb")
190        #    mustclose = 1
191        #else :   
192        #    infile = sys.stdin
193           
194        realbackend = os.path.join(os.path.split(sys.argv[0])[0], thebackend.originalbackend)
195        retcode = os.spawnve(os.P_WAIT, realbackend, [os.environ["DEVICE_URI"]] + sys.argv[1:], os.environ)
196   
197    # stops accounting.
198    thebackend.accounter.endJob(printer, user)
199       
200    # retrieve the job size   
201    jobsize = thebackend.accounter.getJobSize()
202   
203    # update the quota for the current user on this printer
204    if printer.Exists :
205        if jobsize :
206            userquota = thebackend.storage.getUserPQuota(user, printer)
207            if userquota.Exists :
208                userquota.increasePagesUsage(jobsize)
209       
210        # adds the current job to history   
211        printer.addJobToHistory(thebackend.jobid, user, thebackend.accounter.getLastPageCounter(), action, jobsize)
212   
213    return retcode
214
215if __name__ == "__main__" :   
216    # This is a CUPS backend, we should act and die like a CUPS backend
217    if len(sys.argv) == 1 :
218        # we will execute each existing backend in device enumeration mode
219        # and generate their PyKota accounting counterpart
220        (directory, myname) = os.path.split(sys.argv[0])
221        for backend in [os.path.join(directory, b) for b in os.listdir(directory) if os.path.isfile(os.path.join(directory, b)) and (b != myname)] :
222            answer = os.popen(backend, "r")
223            try :
224                devices = [line.strip() for line in answer.readlines()]
225            except :   
226                devices = []
227            status = answer.close()
228            if status is None :
229                for d in devices :
230                    # each line is of the form : 'xxxx xxxx "xxxx xxx" "xxxx xxx"'
231                    # so we have to decompose it carefully
232                    fdevice = cStringIO.StringIO("%s" % d)
233                    tokenizer = shlex.shlex(fdevice)
234                    tokenizer.wordchars = tokenizer.wordchars + r".:,?!~/\_$*-+={}[]()#"
235                    arguments = []
236                    while 1 :
237                        token = tokenizer.get_token()
238                        if token :
239                            arguments.append(token)
240                        else :
241                            break
242                    fdevice.close()
243                    try :
244                        (devicetype, device, name, fullname) = arguments
245                    except ValueError :   
246                        pass    # ignore this 'bizarre' device
247                    else :   
248                        if name.startswith('"') and name.endswith('"') :
249                            name = name[1:-1]
250                        if fullname.startswith('"') and fullname.endswith('"') :
251                            fullname = fullname[1:-1]
252                        print '%s cupspykota:%s "PyKota+%s" "PyKota managed %s"' % (devicetype, device, name, fullname)   
253        retcode = 0
254    elif len(sys.argv) not in (6, 7) :   
255        sys.stderr.write("ERROR: %s job-id user title copies options [file]\n" % sys.argv[0])
256        retcode = 1
257    else :   
258        try :
259            # Initializes the backend
260            kotabackend = PyKotaBackend()   
261            retcode = main(kotabackend)
262        except (PyKotaToolError, PyKotaConfigError, PyKotaStorageError, PyKotaAccounterError, AttributeError, KeyError, IndexError, ValueError, TypeError, IOError), msg :
263            sys.stderr.write("ERROR : cupspykota backend failed (%s)\n" % msg)
264            sys.stderr.flush()
265            retcode = 1
266       
267        try :
268            kotabackend.storage.close()
269        except (TypeError, NameError, AttributeError) :   
270            pass
271       
272    sys.exit(retcode)   
Note: See TracBrowser for help on using the browser.