root / pykota / trunk / pykota / tool.py @ 1606

Revision 1606, 51.1 kB (checked in by jalet, 20 years ago)

Sanitized a bit + use of gettext

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1# PyKota
2# -*- coding: ISO-8859-15 -*-
3
4# PyKota - Print Quotas for CUPS and LPRng
5#
6# (c) 2003-2004 Jerome Alet <alet@librelogiciel.com>
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
20#
21# $Id$
22#
23# $Log$
24# Revision 1.114  2004/07/20 22:19:45  jalet
25# Sanitized a bit + use of gettext
26#
27# Revision 1.113  2004/07/17 20:37:27  jalet
28# Missing file... Am I really stupid ?
29#
30# Revision 1.112  2004/07/16 12:22:47  jalet
31# LPRng support early version
32#
33# Revision 1.111  2004/07/06 18:09:42  jalet
34# Reduced the set of invalid characters in names
35#
36# Revision 1.110  2004/07/01 19:56:42  jalet
37# Better dispatching of error messages
38#
39# Revision 1.109  2004/07/01 17:45:49  jalet
40# Added code to handle the description field for printers
41#
42# Revision 1.108  2004/06/24 23:09:30  jalet
43# Also prints read size on last block
44#
45# Revision 1.107  2004/06/23 13:03:28  jalet
46# Catches accounter configuration errors earlier
47#
48# Revision 1.106  2004/06/22 09:31:18  jalet
49# Always send some debug info to CUPS' back channel stream (stderr) as
50# informationnal messages.
51#
52# Revision 1.105  2004/06/21 08:17:38  jalet
53# Added version number in subject message for directive crashrecipient.
54#
55# Revision 1.104  2004/06/18 13:34:49  jalet
56# Now all tracebacks include PyKota's version number
57#
58# Revision 1.103  2004/06/18 13:17:26  jalet
59# Now includes PyKota's version number in messages sent by the crashrecipient
60# directive.
61#
62# Revision 1.102  2004/06/17 13:26:51  jalet
63# Better exception handling code
64#
65# Revision 1.101  2004/06/16 20:56:34  jalet
66# Smarter initialisation code
67#
68# Revision 1.100  2004/06/11 08:16:03  jalet
69# More exceptions catched in case of very early failure.
70#
71# Revision 1.99  2004/06/11 07:07:38  jalet
72# Now detects and logs configuration syntax errors instead of failing without
73# any notice message.
74#
75# Revision 1.98  2004/06/08 19:27:12  jalet
76# Doesn't ignore SIGCHLD anymore
77#
78# Revision 1.97  2004/06/07 22:45:35  jalet
79# Now accepts a job when enforcement is STRICT and predicted account balance
80# is equal to 0.0 : since the job hasn't been printed yet, only its printing
81# will really render balance equal to 0.0, so we should be allowed to print.
82#
83# Revision 1.96  2004/06/05 22:18:04  jalet
84# Now catches some exceptions earlier.
85# storage.py and ldapstorage.py : removed old comments
86#
87# Revision 1.95  2004/06/03 21:50:34  jalet
88# Improved error logging.
89# crashrecipient directive added.
90# Now exports the job's size in bytes too.
91#
92# Revision 1.94  2004/06/03 08:51:03  jalet
93# logs job's size in bytes now
94#
95# Revision 1.93  2004/06/02 21:51:02  jalet
96# Moved the sigterm capturing elsewhere
97#
98# Revision 1.92  2004/06/02 13:21:38  jalet
99# Debug message added
100#
101# Revision 1.91  2004/05/25 05:17:52  jalet
102# Now precomputes the job's size only if current printer's enforcement
103# is "STRICT"
104#
105# Revision 1.90  2004/05/24 22:45:49  jalet
106# New 'enforcement' directive added
107# Polling loop improvements
108#
109# Revision 1.89  2004/05/21 22:02:52  jalet
110# Preliminary work on pre-accounting
111#
112# Revision 1.88  2004/05/18 14:49:20  jalet
113# Big code changes to completely remove the need for "requester" directives,
114# jsut use "hardware(... your previous requester directive's content ...)"
115#
116# Revision 1.87  2004/05/17 19:14:59  jalet
117# Now catches SIGPIPE and SIGCHLD
118#
119# Revision 1.86  2004/05/13 13:59:28  jalet
120# Code simplifications
121#
122# Revision 1.85  2004/05/11 08:26:27  jalet
123# Now catches connection problems to SMTP server
124#
125# Revision 1.84  2004/04/21 08:36:32  jalet
126# Exports the PYKOTASTATUS environment variable when SIGTERM is received.
127#
128# Revision 1.83  2004/04/16 17:03:49  jalet
129# The list of printers groups the current printer is a member of is
130# now exported in the PYKOTAPGROUPS environment variable
131#
132# Revision 1.82  2004/04/13 09:38:03  jalet
133# More work on correct child processes handling
134#
135# Revision 1.81  2004/04/09 22:24:47  jalet
136# Began work on correct handling of child processes when jobs are cancelled by
137# the user. Especially important when an external requester is running for a
138# long time.
139#
140# Revision 1.80  2004/04/06 12:00:21  jalet
141# uninitialized values caused problems
142#
143# Revision 1.79  2004/03/28 21:01:29  jalet
144# PYKOTALIMITBY environment variable is now exported too
145#
146# Revision 1.78  2004/03/08 20:13:25  jalet
147# Allow names to begin with a digit
148#
149# Revision 1.77  2004/03/03 13:10:35  jalet
150# Now catches all smtplib exceptions when there's a problem sending messages
151#
152# Revision 1.76  2004/03/01 14:34:15  jalet
153# PYKOTAPHASE wasn't set at the right time at the end of data transmission
154# to underlying layer (real backend)
155#
156# Revision 1.75  2004/03/01 11:23:25  jalet
157# Pre and Post hooks to external commands are available in the cupspykota
158# backend. Forthe pykota filter they will be implemented real soon now.
159#
160# Revision 1.74  2004/02/26 14:18:07  jalet
161# Should fix the remaining bugs wrt printers groups and users groups.
162#
163# Revision 1.73  2004/02/19 14:20:21  jalet
164# maildomain pykota.conf directive added.
165# Small improvements on mail headers quality.
166#
167# Revision 1.72  2004/01/14 15:51:19  jalet
168# Docstring added.
169#
170# Revision 1.71  2004/01/11 23:22:42  jalet
171# Major code refactoring, it's way cleaner, and now allows automated addition
172# of printers on first print.
173#
174# Revision 1.70  2004/01/08 14:10:32  jalet
175# Copyright year changed.
176#
177# Revision 1.69  2004/01/05 16:02:18  jalet
178# Dots in user, groups and printer names should be allowed.
179#
180# Revision 1.68  2004/01/02 17:38:40  jalet
181# This time it should be better...
182#
183# Revision 1.67  2004/01/02 17:37:09  jalet
184# I'm completely stupid !!! Better to not talk while coding !
185#
186# Revision 1.66  2004/01/02 17:31:26  jalet
187# Forgot to remove some code
188#
189# Revision 1.65  2003/12/06 08:14:38  jalet
190# Added support for CUPS device uris which contain authentication information.
191#
192# Revision 1.64  2003/11/29 22:03:17  jalet
193# Some code refactoring work. New code is not used at this time.
194#
195# Revision 1.63  2003/11/29 20:06:20  jalet
196# Added 'utolower' configuration option to convert all usernames to
197# lowercase when printing. All database accesses are still and will
198# remain case sensitive though.
199#
200# Revision 1.62  2003/11/25 22:37:22  jalet
201# Small code move
202#
203# Revision 1.61  2003/11/25 22:03:28  jalet
204# No more message on stderr when the translation is not available.
205#
206# Revision 1.60  2003/11/25 21:54:05  jalet
207# updated FAQ
208#
209# Revision 1.59  2003/11/25 13:33:43  jalet
210# Puts 'root' instead of '' when printing from CUPS web interface (which
211# gives an empty username)
212#
213# Revision 1.58  2003/11/21 14:28:45  jalet
214# More complete job history.
215#
216# Revision 1.57  2003/11/19 23:19:38  jalet
217# Code refactoring work.
218# Explicit redirection to /dev/null has to be set in external policy now, just
219# like in external mailto.
220#
221# Revision 1.56  2003/11/19 07:40:20  jalet
222# Missing import statement.
223# Better documentation for mailto: external(...)
224#
225# Revision 1.55  2003/11/18 23:43:12  jalet
226# Mailto can be any external command now, as usual.
227#
228# Revision 1.54  2003/10/24 21:52:46  jalet
229# Now can force language when coming from CGI script.
230#
231# Revision 1.53  2003/10/08 21:41:38  jalet
232# External policies for printers works !
233# We can now auto-add users on first print, and do other useful things if needed.
234#
235# Revision 1.52  2003/10/07 09:07:28  jalet
236# Character encoding added to please latest version of Python
237#
238# Revision 1.51  2003/10/06 14:21:41  jalet
239# Test reversed to not retrieve group members when no messages for them.
240#
241# Revision 1.50  2003/10/02 20:23:18  jalet
242# Storage caching mechanism added.
243#
244# Revision 1.49  2003/07/29 20:55:17  jalet
245# 1.14 is out !
246#
247# Revision 1.48  2003/07/21 23:01:56  jalet
248# Modified some messages aout soft limit
249#
250# Revision 1.47  2003/07/16 21:53:08  jalet
251# Really big modifications wrt new configuration file's location and content.
252#
253# Revision 1.46  2003/07/09 20:17:07  jalet
254# Email field added to PostgreSQL schema
255#
256# Revision 1.45  2003/07/08 19:43:51  jalet
257# Configurable warning messages.
258# Poor man's treshold value added.
259#
260# Revision 1.44  2003/07/07 11:49:24  jalet
261# Lots of small fixes with the help of PyChecker
262#
263# Revision 1.43  2003/07/04 09:06:32  jalet
264# Small bug fix wrt undefined "LimitBy" field.
265#
266# Revision 1.42  2003/06/30 12:46:15  jalet
267# Extracted reporting code.
268#
269# Revision 1.41  2003/06/25 14:10:01  jalet
270# Hey, it may work (edpykota --reset excepted) !
271#
272# Revision 1.40  2003/06/10 16:37:54  jalet
273# Deletion of the second user which is not needed anymore.
274# Added a debug configuration field in /etc/pykota.conf
275# All queries can now be sent to the logger in debug mode, this will
276# greatly help improve performance when time for this will come.
277#
278# Revision 1.39  2003/04/29 18:37:54  jalet
279# Pluggable accounting methods (actually doesn't support external scripts)
280#
281# Revision 1.38  2003/04/24 11:53:48  jalet
282# Default policy for unknown users/groups is to DENY printing instead
283# of the previous default to ALLOW printing. This is to solve an accuracy
284# problem. If you set the policy to ALLOW, jobs printed by in nexistant user
285# (from PyKota's POV) will be charged to the next user who prints on the
286# same printer.
287#
288# Revision 1.37  2003/04/24 08:08:27  jalet
289# Debug message forgotten
290#
291# Revision 1.36  2003/04/24 07:59:40  jalet
292# LPRng support now works !
293#
294# Revision 1.35  2003/04/23 22:13:57  jalet
295# Preliminary support for LPRng added BUT STILL UNTESTED.
296#
297# Revision 1.34  2003/04/17 09:26:21  jalet
298# repykota now reports account balances too.
299#
300# Revision 1.33  2003/04/16 12:35:49  jalet
301# Groups quota work now !
302#
303# Revision 1.32  2003/04/16 08:53:14  jalet
304# Printing can now be limited either by user's account balance or by
305# page quota (the default). Quota report doesn't include account balance
306# yet, though.
307#
308# Revision 1.31  2003/04/15 11:30:57  jalet
309# More work done on money print charging.
310# Minor bugs corrected.
311# All tools now access to the storage as priviledged users, repykota excepted.
312#
313# Revision 1.30  2003/04/10 21:47:20  jalet
314# Job history added. Upgrade script neutralized for now !
315#
316# Revision 1.29  2003/03/29 13:45:27  jalet
317# GPL paragraphs were incorrectly (from memory) copied into the sources.
318# Two README files were added.
319# Upgrade script for PostgreSQL pre 1.01 schema was added.
320#
321# Revision 1.28  2003/03/29 13:08:28  jalet
322# Configuration is now expected to be found in /etc/pykota.conf instead of
323# in /etc/cups/pykota.conf
324# Installation script can move old config files to the new location if needed.
325# Better error handling if configuration file is absent.
326#
327# Revision 1.27  2003/03/15 23:01:28  jalet
328# New mailto option in configuration file added.
329# No time to test this tonight (although it should work).
330#
331# Revision 1.26  2003/03/09 23:58:16  jalet
332# Comment
333#
334# Revision 1.25  2003/03/07 22:56:14  jalet
335# 0.99 is out with some bug fixes.
336#
337# Revision 1.24  2003/02/27 23:48:41  jalet
338# Correctly maps PyKota's log levels to syslog log levels
339#
340# Revision 1.23  2003/02/27 22:55:20  jalet
341# WARN log priority doesn't exist.
342#
343# Revision 1.22  2003/02/27 09:09:20  jalet
344# Added a method to match strings against wildcard patterns
345#
346# Revision 1.21  2003/02/17 23:01:56  jalet
347# Typos
348#
349# Revision 1.20  2003/02/17 22:55:01  jalet
350# More options can now be set per printer or globally :
351#
352#       admin
353#       adminmail
354#       gracedelay
355#       requester
356#
357# the printer option has priority when both are defined.
358#
359# Revision 1.19  2003/02/10 11:28:45  jalet
360# Localization
361#
362# Revision 1.18  2003/02/10 01:02:17  jalet
363# External requester is about to work, but I must sleep
364#
365# Revision 1.17  2003/02/09 13:05:43  jalet
366# Internationalization continues...
367#
368# Revision 1.16  2003/02/09 12:56:53  jalet
369# Internationalization begins...
370#
371# Revision 1.15  2003/02/08 22:09:52  jalet
372# Name check method moved here
373#
374# Revision 1.14  2003/02/07 10:42:45  jalet
375# Indentation problem
376#
377# Revision 1.13  2003/02/07 08:34:16  jalet
378# Test wrt date limit was wrong
379#
380# Revision 1.12  2003/02/06 23:20:02  jalet
381# warnpykota doesn't need any user/group name argument, mimicing the
382# warnquota disk quota tool.
383#
384# Revision 1.11  2003/02/06 22:54:33  jalet
385# warnpykota should be ok
386#
387# Revision 1.10  2003/02/06 15:03:11  jalet
388# added a method to set the limit date
389#
390# Revision 1.9  2003/02/06 10:39:23  jalet
391# Preliminary edpykota work.
392#
393# Revision 1.8  2003/02/06 09:19:02  jalet
394# More robust behavior (hopefully) when the user or printer is not managed
395# correctly by the Quota System : e.g. cupsFilter added in ppd file, but
396# printer and/or user not 'yet?' in storage.
397#
398# Revision 1.7  2003/02/06 00:00:45  jalet
399# Now includes the printer name in email messages
400#
401# Revision 1.6  2003/02/05 23:55:02  jalet
402# Cleaner email messages
403#
404# Revision 1.5  2003/02/05 23:45:09  jalet
405# Better DateTime manipulation wrt grace delay
406#
407# Revision 1.4  2003/02/05 23:26:22  jalet
408# Incorrect handling of grace delay
409#
410# Revision 1.3  2003/02/05 22:16:20  jalet
411# DEVICE_URI is undefined outside of CUPS, i.e. for normal command line tools
412#
413# Revision 1.2  2003/02/05 22:10:29  jalet
414# Typos
415#
416# Revision 1.1  2003/02/05 21:28:17  jalet
417# Initial import into CVS
418#
419#
420#
421
422import sys
423import os
424import fnmatch
425import getopt
426import smtplib
427import gettext
428import locale
429import signal
430import socket
431import tempfile
432import ConfigParser
433
434from mx import DateTime
435
436from pykota import version, config, storage, logger, accounter, pdlanalyzer
437
438class PyKotaToolError(Exception):
439    """An exception for PyKota config related stuff."""
440    def __init__(self, message = ""):
441        self.message = message
442        Exception.__init__(self, message)
443    def __repr__(self):
444        return self.message
445    __str__ = __repr__
446   
447def crashed(message) :   
448    """Minimal crash method."""
449    import traceback
450    lines = []
451    for line in traceback.format_exception(*sys.exc_info()) :
452        lines.extend([l for l in line.split("\n") if l])
453    msg = "ERROR: ".join(["%s\n" % l for l in (["ERROR: PyKota v%s" % version.__version__, message] + lines)])
454    sys.stderr.write(msg)
455    sys.stderr.flush()
456    return msg
457
458class PyKotaTool :   
459    """Base class for all PyKota command line tools."""
460    def __init__(self, lang=None, doc="PyKota %s (c) 2003-2004 %s" % (version.__version__, version.__author__)) :
461        """Initializes the command line tool."""
462        # locale stuff
463        try :
464            locale.setlocale(locale.LC_ALL, lang)
465            gettext.install("pykota")
466        except (locale.Error, IOError) :
467            gettext.NullTranslations().install()
468   
469        # pykota specific stuff
470        self.documentation = doc
471        try :
472            self.config = config.PyKotaConfig("/etc/pykota")
473        except ConfigParser.ParsingError, msg :   
474            sys.stderr.write("ERROR: Problem encountered while parsing configuration file : %s\n" % msg)
475            sys.stderr.flush()
476            sys.exit(-1)
477           
478        try :
479            self.debug = self.config.getDebug()
480            self.smtpserver = self.config.getSMTPServer()
481            self.maildomain = self.config.getMailDomain()
482            self.logger = logger.openLogger(self.config.getLoggingBackend())
483            self.storage = storage.openConnection(self)
484        except (config.PyKotaConfigError, logger.PyKotaLoggingError, storage.PyKotaStorageError), msg :
485            self.crashed(msg)
486            raise
487        self.softwareJobSize = 0
488        self.softwareJobPrice = 0.0
489       
490    def logdebug(self, message) :   
491        """Logs something to debug output if debug is enabled."""
492        if self.debug :
493            self.logger.log_message(message, "debug")
494           
495    def printInfo(self, message, level="info") :       
496        """Sends a message to standard error."""
497        sys.stderr.write("%s: %s\n" % (level.upper(), message))
498        sys.stderr.flush()
499       
500    def clean(self) :   
501        """Ensures that the database is closed."""
502        try :
503            self.storage.close()
504        except (TypeError, NameError, AttributeError) :   
505            pass
506           
507    def display_version_and_quit(self) :
508        """Displays version number, then exists successfully."""
509        self.clean()
510        print version.__version__
511        sys.exit(0)
512   
513    def display_usage_and_quit(self) :
514        """Displays command line usage, then exists successfully."""
515        self.clean()
516        print self.documentation
517        sys.exit(0)
518       
519    def crashed(self, message) :   
520        """Outputs a crash message, and optionally sends it to software author."""
521        msg = crashed(message)
522        try :
523            crashrecipient = self.config.getCrashRecipient()
524            if crashrecipient :
525                admin = self.config.getAdminMail("global") # Nice trick, isn't it ?
526                fullmessage = "========== Traceback :\n\n%s\n\n========== sys.argv :\n\n%s\n\n========== Environment :\n\n%s\n" % \
527                                (msg, \
528                                 "\n".join(["    %s" % repr(a) for a in sys.argv]), \
529                                 "\n".join(["    %s=%s" % (k, v) for (k, v) in os.environ.items()]))
530                server = smtplib.SMTP(self.smtpserver)
531                server.sendmail(admin, [admin, crashrecipient], \
532                                       "From: %s\nTo: %s\nCc: %s\nSubject: PyKota v%s crash traceback !\n\n%s" % \
533                                       (admin, crashrecipient, admin, version.__version__, fullmessage))
534                server.quit()
535        except :
536            pass
537       
538    def parseCommandline(self, argv, short, long, allownothing=0) :
539        """Parses the command line, controlling options."""
540        # split options in two lists: those which need an argument, those which don't need any
541        withoutarg = []
542        witharg = []
543        lgs = len(short)
544        i = 0
545        while i < lgs :
546            ii = i + 1
547            if (ii < lgs) and (short[ii] == ':') :
548                # needs an argument
549                witharg.append(short[i])
550                ii = ii + 1 # skip the ':'
551            else :
552                # doesn't need an argument
553                withoutarg.append(short[i])
554            i = ii
555               
556        for option in long :
557            if option[-1] == '=' :
558                # needs an argument
559                witharg.append(option[:-1])
560            else :
561                # doesn't need an argument
562                withoutarg.append(option)
563       
564        # we begin with all possible options unset
565        parsed = {}
566        for option in withoutarg + witharg :
567            parsed[option] = None
568       
569        # then we parse the command line
570        args = []       # to not break if something unexpected happened
571        try :
572            options, args = getopt.getopt(argv, short, long)
573            if options :
574                for (o, v) in options :
575                    # we skip the '-' chars
576                    lgo = len(o)
577                    i = 0
578                    while (i < lgo) and (o[i] == '-') :
579                        i = i + 1
580                    o = o[i:]
581                    if o in witharg :
582                        # needs an argument : set it
583                        parsed[o] = v
584                    elif o in withoutarg :
585                        # doesn't need an argument : boolean
586                        parsed[o] = 1
587                    else :
588                        # should never occur
589                        raise PyKotaToolError, "Unexpected problem when parsing command line"
590            elif (not args) and (not allownothing) and sys.stdin.isatty() : # no option and no argument, we display help if we are a tty
591                self.display_usage_and_quit()
592        except getopt.error, msg :
593            self.printInfo(msg)
594            self.display_usage_and_quit()
595        return (parsed, args)
596   
597    def isValidName(self, name) :
598        """Checks if a user or printer name is valid."""
599        invalidchars = "/@"
600        for c in invalidchars :
601            if c in name :
602                return 0
603        return 1       
604       
605    def matchString(self, s, patterns) :
606        """Returns 1 if the string s matches one of the patterns, else 0."""
607        for pattern in patterns :
608            if fnmatch.fnmatchcase(s, pattern) :
609                return 1
610        return 0
611       
612    def sendMessage(self, adminmail, touser, fullmessage) :
613        """Sends an email message containing headers to some user."""
614        if "@" not in touser :
615            touser = "%s@%s" % (touser, self.maildomain or self.smtpserver)
616        try :   
617            server = smtplib.SMTP(self.smtpserver)
618        except socket.error, msg :   
619            self.printInfo(_("Impossible to connect to SMTP server : %s") % msg, "error")
620        else :
621            try :
622                server.sendmail(adminmail, [touser], "From: %s\nTo: %s\n%s" % (adminmail, touser, fullmessage))
623            except smtplib.SMTPException, answer :   
624                for (k, v) in answer.recipients.items() :
625                    self.printInfo(_("Impossible to send mail to %s, error %s : %s") % (k, v[0], v[1]), "error")
626            server.quit()
627       
628    def sendMessageToUser(self, admin, adminmail, user, subject, message) :
629        """Sends an email message to a user."""
630        message += _("\n\nPlease contact your system administrator :\n\n\t%s - <%s>\n") % (admin, adminmail)
631        self.sendMessage(adminmail, user.Email or user.Name, "Subject: %s\n\n%s" % (subject, message))
632       
633    def sendMessageToAdmin(self, adminmail, subject, message) :
634        """Sends an email message to the Print Quota administrator."""
635        self.sendMessage(adminmail, adminmail, "Subject: %s\n\n%s" % (subject, message))
636       
637    def _checkUserPQuota(self, userpquota) :           
638        """Checks the user quota on a printer and deny or accept the job."""
639        # then we check the user's own quota
640        # if we get there we are sure that policy is not EXTERNAL
641        user = userpquota.User
642        printer = userpquota.Printer
643        enforcement = self.config.getPrinterEnforcement(printer.Name)
644        self.logdebug("Checking user %s's quota on printer %s" % (user.Name, printer.Name))
645        (policy, dummy) = self.config.getPrinterPolicy(userpquota.Printer.Name)
646        if not userpquota.Exists :
647            # Unknown userquota
648            if policy == "ALLOW" :
649                action = "POLICY_ALLOW"
650            else :   
651                action = "POLICY_DENY"
652            self.printInfo(_("Unable to match user %s on printer %s, applying default policy (%s)") % (user.Name, printer.Name, action))
653        else :   
654            pagecounter = int(userpquota.PageCounter or 0)
655            if enforcement == "STRICT" :
656                pagecounter += self.softwareJobSize
657            if userpquota.SoftLimit is not None :
658                softlimit = int(userpquota.SoftLimit)
659                if pagecounter < softlimit :
660                    action = "ALLOW"
661                else :   
662                    if userpquota.HardLimit is None :
663                        # only a soft limit, this is equivalent to having only a hard limit
664                        action = "DENY"
665                    else :   
666                        hardlimit = int(userpquota.HardLimit)
667                        if softlimit <= pagecounter < hardlimit :   
668                            now = DateTime.now()
669                            if userpquota.DateLimit is not None :
670                                datelimit = DateTime.ISO.ParseDateTime(userpquota.DateLimit)
671                            else :
672                                datelimit = now + self.config.getGraceDelay(printer.Name)
673                                userpquota.setDateLimit(datelimit)
674                            if now < datelimit :
675                                action = "WARN"
676                            else :   
677                                action = "DENY"
678                        else :         
679                            action = "DENY"
680            else :       
681                if userpquota.HardLimit is not None :
682                    # no soft limit, only a hard one.
683                    hardlimit = int(userpquota.HardLimit)
684                    if pagecounter < hardlimit :
685                        action = "ALLOW"
686                    else :     
687                        action = "DENY"
688                else :
689                    # Both are unset, no quota, i.e. accounting only
690                    action = "ALLOW"
691        return action
692   
693    def checkGroupPQuota(self, grouppquota) :   
694        """Checks the group quota on a printer and deny or accept the job."""
695        group = grouppquota.Group
696        printer = grouppquota.Printer
697        enforcement = self.config.getPrinterEnforcement(printer.Name)
698        self.logdebug("Checking group %s's quota on printer %s" % (group.Name, printer.Name))
699        if group.LimitBy and (group.LimitBy.lower() == "balance") : 
700            val = group.AccountBalance
701            if enforcement == "STRICT" : 
702                val -= self.softwareJobPrice # use precomputed size.
703            if val <= 0.0 :
704                action = "DENY"
705            elif val <= self.config.getPoorMan() :   
706                action = "WARN"
707            else :   
708                action = "ALLOW"
709            if (enforcement == "STRICT") and (val == 0.0) :
710                action = "WARN" # we can still print until account is 0
711        else :
712            val = grouppquota.PageCounter
713            if enforcement == "STRICT" :
714                val += self.softwareJobSize
715            if grouppquota.SoftLimit is not None :
716                softlimit = int(grouppquota.SoftLimit)
717                if val < softlimit :
718                    action = "ALLOW"
719                else :   
720                    if grouppquota.HardLimit is None :
721                        # only a soft limit, this is equivalent to having only a hard limit
722                        action = "DENY"
723                    else :   
724                        hardlimit = int(grouppquota.HardLimit)
725                        if softlimit <= val < hardlimit :   
726                            now = DateTime.now()
727                            if grouppquota.DateLimit is not None :
728                                datelimit = DateTime.ISO.ParseDateTime(grouppquota.DateLimit)
729                            else :
730                                datelimit = now + self.config.getGraceDelay(printer.Name)
731                                grouppquota.setDateLimit(datelimit)
732                            if now < datelimit :
733                                action = "WARN"
734                            else :   
735                                action = "DENY"
736                        else :         
737                            action = "DENY"
738            else :       
739                if grouppquota.HardLimit is not None :
740                    # no soft limit, only a hard one.
741                    hardlimit = int(grouppquota.HardLimit)
742                    if val < hardlimit :
743                        action = "ALLOW"
744                    else :     
745                        action = "DENY"
746                else :
747                    # Both are unset, no quota, i.e. accounting only
748                    action = "ALLOW"
749        return action
750   
751    def checkUserPQuota(self, userpquota) :
752        """Checks the user quota on a printer and all its parents and deny or accept the job."""
753        user = userpquota.User
754        printer = userpquota.Printer
755       
756        # indicates that a warning needs to be sent
757        warned = 0               
758       
759        # first we check any group the user is a member of
760        for group in self.storage.getUserGroups(user) :
761            grouppquota = self.storage.getGroupPQuota(group, printer)
762            # for the printer and all its parents
763            for gpquota in [ grouppquota ] + grouppquota.ParentPrintersGroupPQuota :
764                if gpquota.Exists :
765                    action = self.checkGroupPQuota(gpquota)
766                    if action == "DENY" :
767                        return action
768                    elif action == "WARN" :   
769                        warned = 1
770                       
771        # Then we check the user's account balance
772        # if we get there we are sure that policy is not EXTERNAL
773        (policy, dummy) = self.config.getPrinterPolicy(printer.Name)
774        if user.LimitBy and (user.LimitBy.lower() == "balance") : 
775            self.logdebug("Checking account balance for user %s" % user.Name)
776            if user.AccountBalance is None :
777                if policy == "ALLOW" :
778                    action = "POLICY_ALLOW"
779                else :   
780                    action = "POLICY_DENY"
781                self.printInfo(_("Unable to find user %s's account balance, applying default policy (%s) for printer %s") % (user.Name, action, printer.Name))
782                return action       
783            else :   
784                val = float(user.AccountBalance or 0.0)
785                enforcement = self.config.getPrinterEnforcement(printer.Name)
786                if enforcement == "STRICT" : 
787                    val -= self.softwareJobPrice # use precomputed size.
788                if val <= 0.0 :
789                    action = "DENY"
790                elif val <= self.config.getPoorMan() :   
791                    action = "WARN"
792                else :
793                    action = "ALLOW"
794                if (enforcement == "STRICT") and (val == 0.0) :
795                    action = "WARN" # we can still print until account is 0
796                return action   
797        else :
798            # Then check the user quota on current printer and all its parents.               
799            policyallowed = 0
800            for upquota in [ userpquota ] + userpquota.ParentPrintersUserPQuota :               
801                action = self._checkUserPQuota(upquota)
802                if action in ("DENY", "POLICY_DENY") :
803                    return action
804                elif action == "WARN" :   
805                    warned = 1
806                elif action == "POLICY_ALLOW" :   
807                    policyallowed = 1
808            if warned :       
809                return "WARN"
810            elif policyallowed :   
811                return "POLICY_ALLOW" 
812            else :   
813                return "ALLOW"
814               
815    def externalMailTo(self, cmd, action, user, printer, message) :
816        """Warns the user with an external command."""
817        username = user.Name
818        printername = printer.Name
819        email = user.Email or user.Name
820        if "@" not in email :
821            email = "%s@%s" % (email, self.maildomain or self.smtpserver)
822        os.system(cmd % locals())
823   
824    def formatCommandLine(self, cmd, user, printer) :
825        """Executes an external command."""
826        username = user.Name
827        printername = printer.Name
828        return cmd % locals()
829       
830    def warnGroupPQuota(self, grouppquota) :
831        """Checks a group quota and send messages if quota is exceeded on current printer."""
832        group = grouppquota.Group
833        printer = grouppquota.Printer
834        admin = self.config.getAdmin(printer.Name)
835        adminmail = self.config.getAdminMail(printer.Name)
836        (mailto, arguments) = self.config.getMailTo(printer.Name)
837        action = self.checkGroupPQuota(grouppquota)
838        if action.startswith("POLICY_") :
839            action = action[7:]
840        if action == "DENY" :
841            adminmessage = _("Print Quota exceeded for group %s on printer %s") % (group.Name, printer.Name)
842            self.printInfo(adminmessage)
843            if mailto in [ "BOTH", "ADMIN" ] :
844                self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
845            if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
846                for user in self.storage.getGroupMembers(group) :
847                    if mailto != "EXTERNAL" :
848                        self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), self.config.getHardWarn(printer.Name))
849                    else :   
850                        self.externalMailTo(arguments, action, user, printer, message)
851        elif action == "WARN" :   
852            adminmessage = _("Print Quota low for group %s on printer %s") % (group.Name, printer.Name)
853            self.printInfo(adminmessage)
854            if mailto in [ "BOTH", "ADMIN" ] :
855                self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
856            if group.LimitBy and (group.LimitBy.lower() == "balance") : 
857                message = self.config.getPoorWarn()
858            else :     
859                message = self.config.getSoftWarn(printer.Name)
860            if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
861                for user in self.storage.getGroupMembers(group) :
862                    if mailto != "EXTERNAL" :
863                        self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), message)
864                    else :   
865                        self.externalMailTo(arguments, action, user, printer, message)
866        return action       
867       
868    def warnUserPQuota(self, userpquota) :
869        """Checks a user quota and send him a message if quota is exceeded on current printer."""
870        user = userpquota.User
871        printer = userpquota.Printer
872        admin = self.config.getAdmin(printer.Name)
873        adminmail = self.config.getAdminMail(printer.Name)
874        (mailto, arguments) = self.config.getMailTo(printer.Name)
875        action = self.checkUserPQuota(userpquota)
876        if action.startswith("POLICY_") :
877            action = action[7:]
878        if action == "DENY" :
879            adminmessage = _("Print Quota exceeded for user %s on printer %s") % (user.Name, printer.Name)
880            self.printInfo(adminmessage)
881            if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
882                message = self.config.getHardWarn(printer.Name)
883                if mailto != "EXTERNAL" :
884                    self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), message)
885                else :   
886                    self.externalMailTo(arguments, action, user, printer, message)
887            if mailto in [ "BOTH", "ADMIN" ] :
888                self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
889        elif action == "WARN" :   
890            adminmessage = _("Print Quota low for user %s on printer %s") % (user.Name, printer.Name)
891            self.printInfo(adminmessage)
892            if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
893                if user.LimitBy and (user.LimitBy.lower() == "balance") : 
894                    message = self.config.getPoorWarn()
895                else :     
896                    message = self.config.getSoftWarn(printer.Name)
897                if mailto != "EXTERNAL" :   
898                    self.sendMessageToUser(admin, adminmail, user, _("Print Quota Low"), message)
899                else :   
900                    self.externalMailTo(arguments, action, user, printer, message)
901            if mailto in [ "BOTH", "ADMIN" ] :
902                self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
903        return action       
904       
905class PyKotaFilterOrBackend(PyKotaTool) :   
906    """Class for the PyKota filter or backend."""
907    def __init__(self) :
908        """Initialize local datas from current environment."""
909        # We begin with ignoring signals, we may de-ignore them later on.
910        self.gotSigTerm = 0
911        signal.signal(signal.SIGTERM, signal.SIG_IGN)
912        # signal.signal(signal.SIGCHLD, signal.SIG_IGN)
913        signal.signal(signal.SIGPIPE, signal.SIG_IGN)
914       
915        PyKotaTool.__init__(self)
916        (self.printingsystem, \
917         self.printerhostname, \
918         self.printername, \
919         self.username, \
920         self.jobid, \
921         self.inputfile, \
922         self.copies, \
923         self.title, \
924         self.options, \
925         self.originalbackend) = self.extractInfoFromCupsOrLprng()
926         
927        self.logdebug(_("Printing system %s, args=%s") % (str(self.printingsystem), " ".join(sys.argv)))
928       
929        self.username = self.username or 'root' # when printing test page from CUPS web interface, username is empty
930        if self.config.getUserNameToLower() :
931            self.username = self.username.lower()
932        self.preserveinputfile = self.inputfile 
933        try :
934            self.accounter = accounter.openAccounter(self)
935        except (config.PyKotaConfigError, accounter.PyKotaAccounterError), msg :   
936            self.crashed(msg)
937            raise
938        self.exportJobInfo()
939        self.jobdatastream = self.openJobDataStream()
940        os.environ["PYKOTAJOBSIZEBYTES"] = str(self.jobSizeBytes)
941        self.logdebug("Job size is %s bytes" % self.jobSizeBytes)
942        self.logdebug("Capturing SIGTERM events.")
943        signal.signal(signal.SIGTERM, self.sigterm_handler)
944       
945    def sendBackChannelData(self, message, level="info") :   
946        """Sends an informational message to CUPS via back channel stream (stderr)."""
947        sys.stderr.write("%s: PyKota (PID %s) : %s\n" % (level.upper(), os.getpid(), message.strip()))
948        sys.stderr.flush()
949       
950    def openJobDataStream(self) :   
951        """Opens the file which contains the job's datas."""
952        if self.preserveinputfile is None :
953            # Job comes from sys.stdin, but this is not
954            # seekable and complexifies our task, so create
955            # a temporary file and use it instead
956            self.logdebug("Duplicating data stream from stdin to temporary file")
957            dummy = 0
958            MEGABYTE = 1024*1024
959            self.jobSizeBytes = 0
960            infile = tempfile.TemporaryFile()
961            while 1 :
962                data = sys.stdin.read(MEGABYTE) 
963                if not data :
964                    break
965                self.jobSizeBytes += len(data)   
966                if not (dummy % 10) :
967                    self.logdebug("%s bytes read..." % self.jobSizeBytes)
968                dummy += 1   
969                infile.write(data)
970            self.logdebug("%s bytes read total." % self.jobSizeBytes)
971            infile.flush()   
972            infile.seek(0)
973            return infile
974        else :   
975            # real file, just open it
976            self.logdebug("Opening data stream %s" % self.preserveinputfile)
977            self.jobSizeBytes = os.stat(self.preserveinputfile)[6]
978            return open(self.preserveinputfile, "rb")
979       
980    def closeJobDataStream(self) :   
981        """Closes the file which contains the job's datas."""
982        self.logdebug("Closing data stream.")
983        try :
984            self.jobdatastream.close()
985        except :   
986            pass
987       
988    def precomputeJobSize(self) :   
989        """Computes the job size with a software method."""
990        self.logdebug("Precomputing job's size with generic PDL analyzer...")
991        self.jobdatastream.seek(0)
992        try :
993            parser = pdlanalyzer.PDLAnalyzer(self.jobdatastream)
994            jobsize = parser.getJobSize()
995        except pdlanalyzer.PDLAnalyzerError, msg :   
996            # Here we just log the failure, but
997            # we finally ignore it and return 0 since this
998            # computation is just an indication of what the
999            # job's size MAY be.
1000            self.printInfo(_("Unable to precompute the job's size with the generic PDL analyzer : %s") % msg, "warn")
1001            return 0
1002        else :   
1003            if ((self.printingsystem == "CUPS") \
1004                and (self.preserveinputfile is not None)) \
1005                or (self.printingsystem != "CUPS") :
1006                return jobsize * self.copies
1007            else :       
1008                return jobsize
1009           
1010    def sigterm_handler(self, signum, frame) :
1011        """Sets an attribute whenever SIGTERM is received."""
1012        self.gotSigTerm = 1
1013        os.environ["PYKOTASTATUS"] = "CANCELLED"
1014        self.printInfo(_("SIGTERM received, job %s cancelled.") % self.jobid)
1015       
1016    def exportJobInfo(self) :   
1017        """Exports job information to the environment."""
1018        os.environ["PYKOTAUSERNAME"] = str(self.username)
1019        os.environ["PYKOTAPRINTERNAME"] = str(self.printername)
1020        os.environ["PYKOTAJOBID"] = str(self.jobid)
1021        os.environ["PYKOTATITLE"] = self.title or ""
1022        os.environ["PYKOTAFILENAME"] = self.preserveinputfile or ""
1023        os.environ["PYKOTACOPIES"] = str(self.copies)
1024        os.environ["PYKOTAOPTIONS"] = self.options or ""
1025   
1026    def exportUserInfo(self, userpquota) :
1027        """Exports user information to the environment."""
1028        os.environ["PYKOTALIMITBY"] = str(userpquota.User.LimitBy)
1029        os.environ["PYKOTABALANCE"] = str(userpquota.User.AccountBalance or 0.0)
1030        os.environ["PYKOTALIFETIMEPAID"] = str(userpquota.User.LifeTimePaid or 0.0)
1031        os.environ["PYKOTAPAGECOUNTER"] = str(userpquota.PageCounter or 0)
1032        os.environ["PYKOTALIFEPAGECOUNTER"] = str(userpquota.LifePageCounter or 0)
1033        os.environ["PYKOTASOFTLIMIT"] = str(userpquota.SoftLimit)
1034        os.environ["PYKOTAHARDLIMIT"] = str(userpquota.HardLimit)
1035        os.environ["PYKOTADATELIMIT"] = str(userpquota.DateLimit)
1036       
1037        # not really an user information, but anyway
1038        # exports the list of printers groups the current
1039        # printer is a member of
1040        os.environ["PYKOTAPGROUPS"] = ",".join([p.Name for p in self.storage.getParentPrinters(userpquota.Printer)])
1041       
1042    def prehook(self, userpquota) :
1043        """Allows plugging of an external hook before the job gets printed."""
1044        prehook = self.config.getPreHook(userpquota.Printer.Name)
1045        if prehook :
1046            self.logdebug("Executing pre-hook [%s]" % prehook)
1047            os.system(prehook)
1048       
1049    def posthook(self, userpquota) :
1050        """Allows plugging of an external hook after the job gets printed and/or denied."""
1051        posthook = self.config.getPostHook(userpquota.Printer.Name)
1052        if posthook :
1053            self.logdebug("Executing post-hook [%s]" % posthook)
1054            os.system(posthook)
1055       
1056    def printInfo(self, message, level="info") :       
1057        """Sends a message to standard error."""
1058        self.logger.log_message("%s" % message, level)
1059       
1060    def extractInfoFromCupsOrLprng(self) :   
1061        """Returns a tuple (printingsystem, printerhostname, printername, username, jobid, filename, title, options, backend).
1062       
1063           Returns (None, None, None, None, None, None, None, None, None, None) if no printing system is recognized.
1064        """
1065        # Try to detect CUPS
1066        if os.environ.has_key("CUPS_SERVERROOT") and os.path.isdir(os.environ.get("CUPS_SERVERROOT", "")) :
1067            if len(sys.argv) == 7 :
1068                inputfile = sys.argv[6]
1069            else :   
1070                inputfile = None
1071               
1072            # check that the DEVICE_URI environment variable's value is
1073            # prefixed with "cupspykota:" otherwise don't touch it.
1074            # If this is the case, we have to remove the prefix from
1075            # the environment before launching the real backend in cupspykota
1076            device_uri = os.environ.get("DEVICE_URI", "")
1077            if device_uri.startswith("cupspykota:") :
1078                fulldevice_uri = device_uri[:]
1079                device_uri = fulldevice_uri[len("cupspykota:"):]
1080                if device_uri.startswith("//") :    # lpd (at least)
1081                    device_uri = device_uri[2:]
1082                os.environ["DEVICE_URI"] = device_uri   # TODO : side effect !
1083            # TODO : check this for more complex urls than ipp://myprinter.dot.com:631/printers/lp
1084            try :
1085                (backend, destination) = device_uri.split(":", 1) 
1086            except ValueError :   
1087                raise PyKotaToolError, "Invalid DEVICE_URI : %s\n" % device_uri
1088            while destination.startswith("/") :
1089                destination = destination[1:]
1090            checkauth = destination.split("@", 1)   
1091            if len(checkauth) == 2 :
1092                destination = checkauth[1]
1093            printerhostname = destination.split("/")[0].split(":")[0]
1094            return ("CUPS", \
1095                    printerhostname, \
1096                    os.environ.get("PRINTER"), \
1097                    sys.argv[2].strip(), \
1098                    sys.argv[1].strip(), \
1099                    inputfile, \
1100                    int(sys.argv[4].strip()), \
1101                    sys.argv[3], \
1102                    sys.argv[5], \
1103                    backend)
1104        else :   
1105            # Try to detect LPRng
1106            # TODO : try to extract filename, options if available
1107            jseen = Jseen = Pseen = nseen = rseen = Kseen = None
1108            for arg in sys.argv :
1109                if arg.startswith("-j") :
1110                    jseen = arg[2:].strip()
1111                elif arg.startswith("-n") :     
1112                    nseen = arg[2:].strip()
1113                elif arg.startswith("-P") :   
1114                    Pseen = arg[2:].strip()
1115                elif arg.startswith("-r") :   
1116                    rseen = arg[2:].strip()
1117                elif arg.startswith("-J") :   
1118                    Jseen = arg[2:].strip()
1119                elif arg.startswith("-K") or arg.startswith("-#") :   
1120                    Kseen = int(arg[2:].strip())
1121            if Kseen is None :       
1122                Kseen = 1       # we assume the user wants at least one copy...
1123            if (rseen is None) and jseen and Pseen and nseen :   
1124                self.printInfo(_("Printer hostname undefined, set to 'localhost'"), "warn")
1125                rseen = "localhost"
1126            try :   
1127                df_name = [line[8:] for line in os.environ.get("HF", "").split() if line.startswith("df_name=")][0]
1128            except IndexError :
1129                inputfile = None       
1130            else :   
1131                inputfile = os.path.join(os.environ.get("SPOOL_DIR", "."), df_name)
1132            if jseen and Pseen and nseen and rseen :       
1133                options = os.environ.get("HF", "")
1134                return ("LPRNG", rseen, Pseen, nseen, jseen, inputfile, Kseen, Jseen, options, None)
1135        self.printInfo(_("Printing system unknown, args=%s") % " ".join(sys.argv), "warn")
1136        return (None, None, None, None, None, None, None, None, None, None)   # Unknown printing system
1137       
1138    def getPrinterUserAndUserPQuota(self) :       
1139        """Returns a tuple (policy, printer, user, and user print quota) on this printer.
1140       
1141           "OK" is returned in the policy if both printer, user and user print quota
1142           exist in the Quota Storage.
1143           Otherwise, the policy as defined for this printer in pykota.conf is returned.
1144           
1145           If policy was set to "EXTERNAL" and one of printer, user, or user print quota
1146           doesn't exist in the Quota Storage, then an external command is launched, as
1147           defined in the external policy for this printer in pykota.conf
1148           This external command can do anything, like automatically adding printers
1149           or users, for example, and finally extracting printer, user and user print
1150           quota from the Quota Storage is tried a second time.
1151           
1152           "EXTERNALERROR" is returned in case policy was "EXTERNAL" and an error status
1153           was returned by the external command.
1154        """
1155        for passnumber in range(1, 3) :
1156            printer = self.storage.getPrinter(self.printername)
1157            user = self.storage.getUser(self.username)
1158            userpquota = self.storage.getUserPQuota(user, printer)
1159            if printer.Exists and user.Exists and userpquota.Exists :
1160                policy = "OK"
1161                break
1162            (policy, args) = self.config.getPrinterPolicy(self.printername)
1163            if policy == "EXTERNAL" :   
1164                commandline = self.formatCommandLine(args, user, printer)
1165                if not printer.Exists :
1166                    self.printInfo(_("Printer %s not registered in the PyKota system, applying external policy (%s) for printer %s") % (self.printername, commandline, self.printername))
1167                if not user.Exists :
1168                    self.printInfo(_("User %s not registered in the PyKota system, applying external policy (%s) for printer %s") % (self.username, commandline, self.printername))
1169                if not userpquota.Exists :
1170                    self.printInfo(_("User %s doesn't have quota on printer %s in the PyKota system, applying external policy (%s) for printer %s") % (self.username, self.printername, commandline, self.printername))
1171                if os.system(commandline) :
1172                    self.printInfo(_("External policy %s for printer %s produced an error. Job rejected. Please check PyKota's configuration files.") % (commandline, self.printername), "error")
1173                    policy = "EXTERNALERROR"
1174                    break
1175            else :       
1176                if not printer.Exists :
1177                    self.printInfo(_("Printer %s not registered in the PyKota system, applying default policy (%s)") % (self.printername, policy))
1178                if not user.Exists :
1179                    self.printInfo(_("User %s not registered in the PyKota system, applying default policy (%s) for printer %s") % (self.username, policy, self.printername))
1180                if not userpquota.Exists :
1181                    self.printInfo(_("User %s doesn't have quota on printer %s in the PyKota system, applying default policy (%s)") % (self.username, self.printername, policy))
1182                break
1183        if policy == "EXTERNAL" :   
1184            if not printer.Exists :
1185                self.printInfo(_("Printer %s still not registered in the PyKota system, job will be rejected") % self.printername)
1186            if not user.Exists :
1187                self.printInfo(_("User %s still not registered in the PyKota system, job will be rejected on printer %s") % (self.username, self.printername))
1188            if not userpquota.Exists :
1189                self.printInfo(_("User %s still doesn't have quota on printer %s in the PyKota system, job will be rejected") % (self.username, self.printername))
1190        return (policy, printer, user, userpquota)
1191       
1192    def mainWork(self) :   
1193        """Main work is done here."""
1194        (policy, printer, user, userpquota) = self.getPrinterUserAndUserPQuota()
1195        # TODO : check for last user's quota in case pykota filter is used with querying
1196        if policy == "EXTERNALERROR" :
1197            # Policy was 'EXTERNAL' and the external command returned an error code
1198            return self.removeJob()
1199        elif policy == "EXTERNAL" :
1200            # Policy was 'EXTERNAL' and the external command wasn't able
1201            # to add either the printer, user or user print quota
1202            return self.removeJob()
1203        elif policy == "DENY" :   
1204            # Either printer, user or user print quota doesn't exist,
1205            # and the job should be rejected.
1206            return self.removeJob()
1207        else :
1208            if policy not in ("OK", "ALLOW") :
1209                self.printInfo(_("Invalid policy %s for printer %s") % (policy, self.printername))
1210                return self.removeJob()
1211            else :
1212                return self.doWork(policy, printer, user, userpquota)
Note: See TracBrowser for help on using the browser.