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

Revision 3055, 34.9 kB (checked in by jerome, 17 years ago)

Simplified i18n and l10n stuff by removing code used with Python<2.3 => not supported anymore.
Fixed it for working in CGI scripts as expected (better with Python 2.4 and higher).

  • 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, 2005, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20#
21# $Id$
22#
23#
24
25import sys
26import os
27import pwd
28import fnmatch
29import getopt
30import smtplib
31import gettext
32import locale
33import socket
34import time
35from email.MIMEText import MIMEText
36from email.Header import Header
37import email.Utils
38
39from mx import DateTime
40
41try :
42    import chardet
43except ImportError :   
44    def detectCharset(text) :
45        """Fakes a charset detection if the chardet module is not installed."""
46        return "ISO-8859-15"
47else :   
48    def detectCharset(text) :
49        """Uses the chardet module to workaround CUPS lying to us."""
50        return chardet.detect(text)["encoding"]
51
52from pykota import config, storage, logger
53from pykota.version import __version__, __author__, __years__, __gplblurb__
54
55def N_(message) :
56    """Fake translation marker for translatable strings extraction."""
57    return message
58
59class PyKotaToolError(Exception):
60    """An exception for PyKota related stuff."""
61    def __init__(self, message = ""):
62        self.message = message
63        Exception.__init__(self, message)
64    def __repr__(self):
65        return self.message
66    __str__ = __repr__
67   
68class PyKotaCommandLineError(PyKotaToolError) :   
69    """An exception for Pykota command line tools."""
70    pass
71   
72def crashed(message="Bug in PyKota") :   
73    """Minimal crash method."""
74    import traceback
75    lines = []
76    for line in traceback.format_exception(*sys.exc_info()) :
77        lines.extend([l for l in line.split("\n") if l])
78    msg = "ERROR: ".join(["%s\n" % l for l in (["ERROR: PyKota v%s" % __version__, message] + lines)])
79    sys.stderr.write(msg)
80    sys.stderr.flush()
81    return msg
82
83class Percent :
84    """A class to display progress."""
85    def __init__(self, app, size=None) :
86        """Initializes the engine."""
87        self.app = app
88        self.size = None
89        if size :
90            self.setSize(size)
91        self.previous = None
92        self.before = time.time()
93       
94    def setSize(self, size) :     
95        """Sets the total size."""
96        self.number = 0
97        self.size = size
98        if size :
99            self.factor = 100.0 / float(size)
100       
101    def display(self, msg) :   
102        """Displays the value."""
103        self.app.display(msg)
104       
105    def oneMore(self) :   
106        """Increments internal counter."""
107        if self.size :
108            self.number += 1
109            percent = "%.02f" % (float(self.number) * self.factor)
110            if percent != self.previous : # optimize for large number of items
111                self.display("\r%s%%" % percent)
112                self.previous = percent
113           
114    def done(self) :         
115        """Displays the 'done' message."""
116        after = time.time()
117        if self.size :
118            speed = self.size / (after - self.before)
119            self.display("\r100.00%%\r        \r%s. %s : %.2f %s.\n" \
120                     % (_("Done"), _("Average speed"), speed, _("entries per second")))
121        else :             
122            self.display("\r100.00%%\r        \r%s.\n" % _("Done"))
123       
124class Tool :
125    """Base class for tools with no database access."""
126    def __init__(self, lang="", charset=None, doc="PyKota v%(__version__)s (c) %(__years__)s %(__author__)s") :
127        """Initializes the command line tool."""
128        # did we drop priviledges ?
129        self.privdropped = 0
130       
131        # locale stuff
132        try :
133            locale.setlocale(locale.LC_ALL, (lang, charset))
134        except (locale.Error, IOError) :
135            locale.setlocale(locale.LC_ALL, None)
136        (self.language, self.charset) = locale.getlocale()
137        self.language = self.language or "C"
138        self.charset = self.charset or locale.getpreferredencoding()
139       
140        # translation stuff
141        try :
142            try :
143                trans = gettext.translation("pykota", languages=["%s.%s" % (self.language, self.charset)], codeset=self.charset)
144            except TypeError : # Python <2.4
145                trans = gettext.translation("pykota", languages=["%s.%s" % (self.language, self.charset)])
146            trans.install()
147        except :
148            gettext.NullTranslations().install()
149   
150        # pykota specific stuff
151        self.documentation = doc
152       
153    def deferredInit(self) :       
154        """Deferred initialization."""
155        confdir = os.environ.get("PYKOTA_HOME")
156        environHome = True
157        missingUser = False
158        if confdir is None :
159            environHome = False
160            # check for config files in the 'pykota' user's home directory.
161            try :
162                self.pykotauser = pwd.getpwnam("pykota")
163                confdir = self.pykotauser[5]
164            except KeyError :   
165                self.pykotauser = None
166                confdir = "/etc/pykota"
167                missingUser = True
168           
169        self.config = config.PyKotaConfig(confdir)
170        self.debug = self.config.getDebug()
171        self.smtpserver = self.config.getSMTPServer()
172        self.maildomain = self.config.getMailDomain()
173        self.logger = logger.openLogger(self.config.getLoggingBackend())
174           
175        # now drop priviledge if possible
176        self.dropPriv()   
177       
178        # We NEED this here, even when not in an accounting filter/backend   
179        self.softwareJobSize = 0
180        self.softwareJobPrice = 0.0
181       
182        if environHome :
183            self.printInfo("PYKOTA_HOME environment variable is set. Configuration files were searched in %s" % confdir, "info")
184        else :
185            if missingUser :     
186                self.printInfo("The 'pykota' system account is missing. Configuration files were searched in %s instead." % confdir, "warn")
187       
188        self.logdebug("Language in use : %s" % self.language)
189        self.logdebug("Charset in use : %s" % self.charset)
190       
191        arguments = " ".join(['"%s"' % arg for arg in sys.argv])
192        self.logdebug("Command line arguments : %s" % arguments)
193       
194    def dropPriv(self) :   
195        """Drops priviledges."""
196        uid = os.geteuid()
197        try :
198            self.originalUserName = pwd.getpwuid(uid)[0]
199        except (KeyError, IndexError), msg :   
200            self.printInfo(_("Strange problem with uid(%s) : %s") % (uid, msg), "warn")
201            self.originalUserName = None
202        else :
203            if uid :
204                self.logdebug(_("Running as user '%s'.") % self.originalUserName)
205            else :
206                if self.pykotauser is None :
207                    self.logdebug(_("No user named 'pykota'. Not dropping priviledges."))
208                else :   
209                    try :
210                        os.setegid(self.pykotauser[3])
211                        os.seteuid(self.pykotauser[2])
212                    except OSError, msg :   
213                        self.printInfo(_("Impossible to drop priviledges : %s") % msg, "warn")
214                    else :   
215                        self.logdebug(_("Priviledges dropped. Now running as user 'pykota'."))
216                        self.privdropped = 1
217           
218    def regainPriv(self) :   
219        """Drops priviledges."""
220        if self.privdropped :
221            try :
222                os.seteuid(0)
223                os.setegid(0)
224            except OSError, msg :   
225                self.printInfo(_("Impossible to regain priviledges : %s") % msg, "warn")
226            else :   
227                self.logdebug(_("Regained priviledges. Now running as root."))
228                self.privdropped = 0
229       
230    def UTF8ToUserCharset(self, text) :
231        """Converts from UTF-8 to user's charset."""
232        if text is None :
233            return None
234        try :
235            return text.decode("UTF-8").encode(self.charset, "replace") 
236        except (UnicodeError, AttributeError) :   
237            try :
238                # Maybe already in Unicode ?
239                return text.encode(self.charset, "replace") 
240            except (UnicodeError, AttributeError) :
241                # Try to autodetect the charset
242                return text.decode(detectCharset(text), "replace").encode(self.charset, "replace")
243       
244    def userCharsetToUTF8(self, text) :
245        """Converts from user's charset to UTF-8."""
246        if text is None :
247            return None
248        try :
249            # We don't necessarily trust the default charset, because
250            # xprint sends us titles in UTF-8 but CUPS gives us an ISO-8859-1 charset !
251            # So we first try to see if the text is already in UTF-8 or not, and
252            # if it is, we delete characters which can't be converted to the user's charset,
253            # then convert back to UTF-8. PostgreSQL 7.3.x used to reject some unicode characters,
254            # this is fixed by the ugly line below :
255            return text.decode("UTF-8").encode(self.charset, "replace").decode(self.charset).encode("UTF-8", "replace")
256        except (UnicodeError, AttributeError) :
257            try :
258                return text.decode(self.charset).encode("UTF-8", "replace") 
259            except (UnicodeError, AttributeError) :   
260                try :
261                    # Maybe already in Unicode ?
262                    return text.encode("UTF-8", "replace") 
263                except (UnicodeError, AttributeError) :
264                    # Try to autodetect the charset
265                    return text.decode(detectCharset(text), "replace").encode("UTF-8", "replace")
266        return newtext
267       
268    def display(self, message) :
269        """Display a message but only if stdout is a tty."""
270        if sys.stdout.isatty() :
271            sys.stdout.write(message)
272            sys.stdout.flush()
273           
274    def logdebug(self, message) :   
275        """Logs something to debug output if debug is enabled."""
276        if self.debug :
277            self.logger.log_message(message, "debug")
278           
279    def printInfo(self, message, level="info") :       
280        """Sends a message to standard error."""
281        sys.stderr.write("%s: %s\n" % (level.upper(), message))
282        sys.stderr.flush()
283       
284    def matchString(self, s, patterns) :
285        """Returns True if the string s matches one of the patterns, else False."""
286        if not patterns :
287            return True # No pattern, always matches.
288        else :   
289            for pattern in patterns :
290                if fnmatch.fnmatchcase(s, pattern) :
291                    return True
292            return False
293       
294    def sanitizeNames(self, options, names) :
295        """Ensures that an user can only see the datas he is allowed to see, by modifying the list of names."""
296        if not self.config.isAdmin :
297            username = pwd.getpwuid(os.geteuid())[0]
298            if not options["list"] :
299                raise PyKotaCommandLineError, "%s : %s" % (username, _("You're not allowed to use this command."))
300            else :
301                if options["groups"] :
302                    user = self.storage.getUser(username)
303                    if user.Exists :
304                        return [ g.Name for g in self.storage.getUserGroups(user) ]
305                return [ username ]
306        elif not names :       
307            return ["*"]
308        else :   
309            return names
310       
311    def display_version_and_quit(self) :
312        """Displays version number, then exists successfully."""
313        try :
314            self.clean()
315        except AttributeError :   
316            pass
317        print __version__
318        sys.exit(0)
319   
320    def display_usage_and_quit(self) :
321        """Displays command line usage, then exists successfully."""
322        try :
323            self.clean()
324        except AttributeError :   
325            pass
326        print _(self.documentation) % globals()
327        print __gplblurb__
328        print
329        print _("Please report bugs to :"), __author__
330        sys.exit(0)
331       
332    def crashed(self, message="Bug in PyKota") :   
333        """Outputs a crash message, and optionally sends it to software author."""
334        msg = crashed(message)
335        fullmessage = "========== Traceback :\n\n%s\n\n========== sys.argv :\n\n%s\n\n========== Environment :\n\n%s\n" % \
336                        (msg, \
337                         "\n".join(["    %s" % repr(a) for a in sys.argv]), \
338                         "\n".join(["    %s=%s" % (k, v) for (k, v) in os.environ.items()]))
339        try :
340            crashrecipient = self.config.getCrashRecipient()
341            if crashrecipient :
342                admin = self.config.getAdminMail("global") # Nice trick, isn't it ?
343                server = smtplib.SMTP(self.smtpserver)
344                msg = MIMEText(fullmessage, _charset=self.charset)
345                msg["Subject"] = str(Header("PyKota v%s crash traceback !" \
346                                        % __version__, charset=self.charset))
347                msg["From"] = admin
348                msg["To"] = crashrecipient
349                msg["Cc"] = admin
350                msg["Date"] = email.Utils.formatdate(localtime=True)
351                server.sendmail(admin, [admin, crashrecipient], msg.as_string())
352                server.quit()
353        except :
354            pass
355        return fullmessage   
356       
357    def parseCommandline(self, argv, short, long, allownothing=0) :
358        """Parses the command line, controlling options."""
359        # split options in two lists: those which need an argument, those which don't need any
360        short = "%sA:" % short
361        long.append("arguments=")
362        withoutarg = []
363        witharg = []
364        lgs = len(short)
365        i = 0
366        while i < lgs :
367            ii = i + 1
368            if (ii < lgs) and (short[ii] == ':') :
369                # needs an argument
370                witharg.append(short[i])
371                ii = ii + 1 # skip the ':'
372            else :
373                # doesn't need an argument
374                withoutarg.append(short[i])
375            i = ii
376               
377        for option in long :
378            if option[-1] == '=' :
379                # needs an argument
380                witharg.append(option[:-1])
381            else :
382                # doesn't need an argument
383                withoutarg.append(option)
384       
385        # then we parse the command line
386        done = 0
387        while not done :
388            # we begin with all possible options unset
389            parsed = {}
390            for option in withoutarg + witharg :
391                parsed[option] = None
392            args = []       # to not break if something unexpected happened
393            try :
394                options, args = getopt.getopt(argv, short, long)
395                if options :
396                    for (o, v) in options :
397                        # we skip the '-' chars
398                        lgo = len(o)
399                        i = 0
400                        while (i < lgo) and (o[i] == '-') :
401                            i = i + 1
402                        o = o[i:]
403                        if o in witharg :
404                            # needs an argument : set it
405                            parsed[o] = v
406                        elif o in withoutarg :
407                            # doesn't need an argument : boolean
408                            parsed[o] = 1
409                        else :
410                            # should never occur
411                            raise PyKotaCommandLineError, "Unexpected problem when parsing command line"
412                elif (not args) and (not allownothing) and sys.stdin.isatty() : # no option and no argument, we display help if we are a tty
413                    self.display_usage_and_quit()
414            except getopt.error, msg :
415                raise PyKotaCommandLineError, str(msg)
416            else :   
417                if parsed["arguments"] or parsed["A"] :
418                    # arguments are in a file, we ignore all other arguments
419                    # and reset the list of arguments to the lines read from
420                    # the file.
421                    argsfile = open(parsed["arguments"] or parsed["A"], "r")
422                    argv = [ l.strip() for l in argsfile.readlines() ]
423                    argsfile.close()
424                    for i in range(len(argv)) :
425                        argi = argv[i]
426                        if argi.startswith('"') and argi.endswith('"') :
427                            argv[i] = argi[1:-1]
428                else :   
429                    done = 1
430        return (parsed, args)
431   
432class PyKotaTool(Tool) :   
433    """Base class for all PyKota command line tools."""
434    def __init__(self, lang="", charset=None, doc="PyKota v%(__version__)s (c) %(__years__)s %(__author__)s") :
435        """Initializes the command line tool and opens the database."""
436        Tool.__init__(self, lang, charset, doc)
437       
438    def deferredInit(self) :   
439        """Deferred initialization."""
440        Tool.deferredInit(self)
441        self.storage = storage.openConnection(self)
442        if self.config.isAdmin : # TODO : We don't know this before, fix this !
443            self.logdebug("Beware : running as a PyKota administrator !")
444        else :   
445            self.logdebug("Don't Panic : running as a mere mortal !")
446       
447    def clean(self) :   
448        """Ensures that the database is closed."""
449        try :
450            self.storage.close()
451        except (TypeError, NameError, AttributeError) :   
452            pass
453           
454    def isValidName(self, name) :
455        """Checks if a user or printer name is valid."""
456        invalidchars = "/@?*,;&|"
457        for c in list(invalidchars) :
458            if c in name :
459                return 0
460        return 1       
461       
462    def sendMessage(self, adminmail, touser, fullmessage) :
463        """Sends an email message containing headers to some user."""
464        try :   
465            server = smtplib.SMTP(self.smtpserver)
466        except socket.error, msg :   
467            self.printInfo(_("Impossible to connect to SMTP server : %s") % msg, "error")
468        else :
469            try :
470                server.sendmail(adminmail, [touser], fullmessage)
471            except smtplib.SMTPException, answer :   
472                for (k, v) in answer.recipients.items() :
473                    self.printInfo(_("Impossible to send mail to %s, error %s : %s") % (k, v[0], v[1]), "error")
474            server.quit()
475       
476    def sendMessageToUser(self, admin, adminmail, user, subject, message) :
477        """Sends an email message to a user."""
478        message += _("\n\nPlease contact your system administrator :\n\n\t%s - <%s>\n") % (admin, adminmail)
479        usermail = user.Email or user.Name
480        if "@" not in usermail :
481            usermail = "%s@%s" % (usermail, self.maildomain or self.smtpserver or "localhost")
482        msg = MIMEText(message, _charset=self.charset)
483        msg["Subject"] = str(Header(subject, charset=self.charset))
484        msg["From"] = adminmail
485        msg["To"] = usermail
486        msg["Date"] = email.Utils.formatdate(localtime=True)
487        self.sendMessage(adminmail, usermail, msg.as_string())
488       
489    def sendMessageToAdmin(self, adminmail, subject, message) :
490        """Sends an email message to the Print Quota administrator."""
491        if "@" not in adminmail :
492            adminmail = "%s@%s" % (adminmail, self.maildomain or self.smtpserver or "localhost")
493        msg = MIMEText(message, _charset=self.charset)
494        msg["Subject"] = str(Header(subject, charset=self.charset))
495        msg["From"] = adminmail
496        msg["To"] = adminmail
497        self.sendMessage(adminmail, adminmail, msg.as_string())
498       
499    def _checkUserPQuota(self, userpquota) :           
500        """Checks the user quota on a printer and deny or accept the job."""
501        # then we check the user's own quota
502        # if we get there we are sure that policy is not EXTERNAL
503        user = userpquota.User
504        printer = userpquota.Printer
505        enforcement = self.config.getPrinterEnforcement(printer.Name)
506        self.logdebug("Checking user %s's quota on printer %s" % (user.Name, printer.Name))
507        (policy, dummy) = self.config.getPrinterPolicy(userpquota.Printer.Name)
508        if not userpquota.Exists :
509            # Unknown userquota
510            if policy == "ALLOW" :
511                action = "POLICY_ALLOW"
512            else :   
513                action = "POLICY_DENY"
514            self.printInfo(_("Unable to match user %s on printer %s, applying default policy (%s)") % (user.Name, printer.Name, action))
515        else :   
516            pagecounter = int(userpquota.PageCounter or 0)
517            if enforcement == "STRICT" :
518                pagecounter += self.softwareJobSize
519            if userpquota.SoftLimit is not None :
520                softlimit = int(userpquota.SoftLimit)
521                if pagecounter < softlimit :
522                    action = "ALLOW"
523                else :   
524                    if userpquota.HardLimit is None :
525                        # only a soft limit, this is equivalent to having only a hard limit
526                        action = "DENY"
527                    else :   
528                        hardlimit = int(userpquota.HardLimit)
529                        if softlimit <= pagecounter < hardlimit :   
530                            now = DateTime.now()
531                            if userpquota.DateLimit is not None :
532                                datelimit = DateTime.ISO.ParseDateTime(str(userpquota.DateLimit)[:19])
533                            else :
534                                datelimit = now + self.config.getGraceDelay(printer.Name)
535                                userpquota.setDateLimit(datelimit)
536                            if now < datelimit :
537                                action = "WARN"
538                            else :   
539                                action = "DENY"
540                        else :         
541                            action = "DENY"
542            else :       
543                if userpquota.HardLimit is not None :
544                    # no soft limit, only a hard one.
545                    hardlimit = int(userpquota.HardLimit)
546                    if pagecounter < hardlimit :
547                        action = "ALLOW"
548                    else :     
549                        action = "DENY"
550                else :
551                    # Both are unset, no quota, i.e. accounting only
552                    action = "ALLOW"
553        return action
554   
555    def checkGroupPQuota(self, grouppquota) :   
556        """Checks the group quota on a printer and deny or accept the job."""
557        group = grouppquota.Group
558        printer = grouppquota.Printer
559        enforcement = self.config.getPrinterEnforcement(printer.Name)
560        self.logdebug("Checking group %s's quota on printer %s" % (group.Name, printer.Name))
561        if group.LimitBy and (group.LimitBy.lower() == "balance") : 
562            val = group.AccountBalance or 0.0
563            if enforcement == "STRICT" : 
564                val -= self.softwareJobPrice # use precomputed size.
565            balancezero = self.config.getBalanceZero()
566            if val <= balancezero :
567                action = "DENY"
568            elif val <= self.config.getPoorMan() :   
569                action = "WARN"
570            else :   
571                action = "ALLOW"
572            if (enforcement == "STRICT") and (val == balancezero) :
573                action = "WARN" # we can still print until account is 0
574        else :
575            val = grouppquota.PageCounter or 0
576            if enforcement == "STRICT" :
577                val += int(self.softwareJobSize) # TODO : this is not a fix, problem is elsewhere in grouppquota.PageCounter
578            if grouppquota.SoftLimit is not None :
579                softlimit = int(grouppquota.SoftLimit)
580                if val < softlimit :
581                    action = "ALLOW"
582                else :   
583                    if grouppquota.HardLimit is None :
584                        # only a soft limit, this is equivalent to having only a hard limit
585                        action = "DENY"
586                    else :   
587                        hardlimit = int(grouppquota.HardLimit)
588                        if softlimit <= val < hardlimit :   
589                            now = DateTime.now()
590                            if grouppquota.DateLimit is not None :
591                                datelimit = DateTime.ISO.ParseDateTime(str(grouppquota.DateLimit)[:19])
592                            else :
593                                datelimit = now + self.config.getGraceDelay(printer.Name)
594                                grouppquota.setDateLimit(datelimit)
595                            if now < datelimit :
596                                action = "WARN"
597                            else :   
598                                action = "DENY"
599                        else :         
600                            action = "DENY"
601            else :       
602                if grouppquota.HardLimit is not None :
603                    # no soft limit, only a hard one.
604                    hardlimit = int(grouppquota.HardLimit)
605                    if val < hardlimit :
606                        action = "ALLOW"
607                    else :     
608                        action = "DENY"
609                else :
610                    # Both are unset, no quota, i.e. accounting only
611                    action = "ALLOW"
612        return action
613   
614    def checkUserPQuota(self, userpquota) :
615        """Checks the user quota on a printer and all its parents and deny or accept the job."""
616        user = userpquota.User
617        printer = userpquota.Printer
618       
619        # indicates that a warning needs to be sent
620        warned = 0               
621       
622        # first we check any group the user is a member of
623        for group in self.storage.getUserGroups(user) :
624            # No need to check anything if the group is in noquota mode
625            if group.LimitBy != "noquota" :
626                grouppquota = self.storage.getGroupPQuota(group, printer)
627                # for the printer and all its parents
628                for gpquota in [ grouppquota ] + grouppquota.ParentPrintersGroupPQuota :
629                    if gpquota.Exists :
630                        action = self.checkGroupPQuota(gpquota)
631                        if action == "DENY" :
632                            return action
633                        elif action == "WARN" :   
634                            warned = 1
635                       
636        # Then we check the user's account balance
637        # if we get there we are sure that policy is not EXTERNAL
638        (policy, dummy) = self.config.getPrinterPolicy(printer.Name)
639        if user.LimitBy and (user.LimitBy.lower() == "balance") : 
640            self.logdebug("Checking account balance for user %s" % user.Name)
641            if user.AccountBalance is None :
642                if policy == "ALLOW" :
643                    action = "POLICY_ALLOW"
644                else :   
645                    action = "POLICY_DENY"
646                self.printInfo(_("Unable to find user %s's account balance, applying default policy (%s) for printer %s") % (user.Name, action, printer.Name))
647                return action       
648            else :   
649                if user.OverCharge == 0.0 :
650                    self.printInfo(_("User %s will not be charged for printing.") % user.Name)
651                    action = "ALLOW"
652                else :
653                    val = float(user.AccountBalance or 0.0)
654                    enforcement = self.config.getPrinterEnforcement(printer.Name)
655                    if enforcement == "STRICT" : 
656                        val -= self.softwareJobPrice # use precomputed size.
657                    balancezero = self.config.getBalanceZero()   
658                    if val <= balancezero :
659                        action = "DENY"
660                    elif val <= self.config.getPoorMan() :   
661                        action = "WARN"
662                    else :
663                        action = "ALLOW"
664                    if (enforcement == "STRICT") and (val == balancezero) :
665                        action = "WARN" # we can still print until account is 0
666                return action   
667        else :
668            # Then check the user quota on current printer and all its parents.               
669            policyallowed = 0
670            for upquota in [ userpquota ] + userpquota.ParentPrintersUserPQuota :               
671                action = self._checkUserPQuota(upquota)
672                if action in ("DENY", "POLICY_DENY") :
673                    return action
674                elif action == "WARN" :   
675                    warned = 1
676                elif action == "POLICY_ALLOW" :   
677                    policyallowed = 1
678            if warned :       
679                return "WARN"
680            elif policyallowed :   
681                return "POLICY_ALLOW" 
682            else :   
683                return "ALLOW"
684               
685    def externalMailTo(self, cmd, action, user, printer, message) :
686        """Warns the user with an external command."""
687        username = user.Name
688        printername = printer.Name
689        email = user.Email or user.Name
690        if "@" not in email :
691            email = "%s@%s" % (email, self.maildomain or self.smtpserver)
692        os.system(cmd % locals())
693   
694    def formatCommandLine(self, cmd, user, printer) :
695        """Executes an external command."""
696        username = user.Name
697        printername = printer.Name
698        return cmd % locals()
699       
700    def warnGroupPQuota(self, grouppquota) :
701        """Checks a group quota and send messages if quota is exceeded on current printer."""
702        group = grouppquota.Group
703        printer = grouppquota.Printer
704        admin = self.config.getAdmin(printer.Name)
705        adminmail = self.config.getAdminMail(printer.Name)
706        (mailto, arguments) = self.config.getMailTo(printer.Name)
707        if group.LimitBy in ("noquota", "nochange") :
708            action = "ALLOW"
709        else :   
710            action = self.checkGroupPQuota(grouppquota)
711            if action.startswith("POLICY_") :
712                action = action[7:]
713            if action == "DENY" :
714                adminmessage = _("Print Quota exceeded for group %s on printer %s") % (group.Name, printer.Name)
715                self.printInfo(adminmessage)
716                if mailto in [ "BOTH", "ADMIN" ] :
717                    self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
718                if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
719                    for user in self.storage.getGroupMembers(group) :
720                        if mailto != "EXTERNAL" :
721                            self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), self.config.getHardWarn(printer.Name))
722                        else :   
723                            self.externalMailTo(arguments, action, user, printer, self.config.getHardWarn(printer.Name))
724            elif action == "WARN" :   
725                adminmessage = _("Print Quota low for group %s on printer %s") % (group.Name, printer.Name)
726                self.printInfo(adminmessage)
727                if mailto in [ "BOTH", "ADMIN" ] :
728                    self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
729                if group.LimitBy and (group.LimitBy.lower() == "balance") : 
730                    message = self.config.getPoorWarn()
731                else :     
732                    message = self.config.getSoftWarn(printer.Name)
733                if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
734                    for user in self.storage.getGroupMembers(group) :
735                        if mailto != "EXTERNAL" :
736                            self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), message)
737                        else :   
738                            self.externalMailTo(arguments, action, user, printer, message)
739        return action       
740       
741    def warnUserPQuota(self, userpquota) :
742        """Checks a user quota and send him a message if quota is exceeded on current printer."""
743        user = userpquota.User
744        printer = userpquota.Printer
745        admin = self.config.getAdmin(printer.Name)
746        adminmail = self.config.getAdminMail(printer.Name)
747        (mailto, arguments) = self.config.getMailTo(printer.Name)
748       
749        if user.LimitBy in ("noquota", "nochange") :
750            action = "ALLOW"
751        elif user.LimitBy == "noprint" :
752            action = "DENY"
753            message = _("User %s is not allowed to print at this time.") % user.Name
754            self.printInfo(message)
755            if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
756                if mailto != "EXTERNAL" :
757                    self.sendMessageToUser(admin, adminmail, user, _("Printing denied."), message)
758                else :   
759                    self.externalMailTo(arguments, action, user, printer, message)
760            if mailto in [ "BOTH", "ADMIN" ] :
761                self.sendMessageToAdmin(adminmail, _("Print Quota"), message)
762        else :
763            action = self.checkUserPQuota(userpquota)
764            if action.startswith("POLICY_") :
765                action = action[7:]
766               
767            if action == "DENY" :
768                adminmessage = _("Print Quota exceeded for user %s on printer %s") % (user.Name, printer.Name)
769                self.printInfo(adminmessage)
770                if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
771                    message = self.config.getHardWarn(printer.Name)
772                    if mailto != "EXTERNAL" :
773                        self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), message)
774                    else :   
775                        self.externalMailTo(arguments, action, user, printer, message)
776                if mailto in [ "BOTH", "ADMIN" ] :
777                    self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
778            elif action == "WARN" :   
779                adminmessage = _("Print Quota low for user %s on printer %s") % (user.Name, printer.Name)
780                self.printInfo(adminmessage)
781                if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
782                    if user.LimitBy and (user.LimitBy.lower() == "balance") : 
783                        message = self.config.getPoorWarn()
784                    else :     
785                        message = self.config.getSoftWarn(printer.Name)
786                    if mailto != "EXTERNAL" :   
787                        self.sendMessageToUser(admin, adminmail, user, _("Print Quota Low"), message)
788                    else :   
789                        self.externalMailTo(arguments, action, user, printer, message)
790                if mailto in [ "BOTH", "ADMIN" ] :
791                    self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
792        return action       
793       
Note: See TracBrowser for help on using the browser.