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

Revision 1665, 51.7 kB (checked in by jalet, 20 years ago)

Fixed french translation problem.
Fixed problem with group quotas and strict enforcement.

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