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

Revision 1611, 51.5 kB (checked in by jalet, 20 years ago)

Software accounting seems to be OK with LPRng support now

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