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

Revision 2867, 34.5 kB (checked in by jerome, 18 years ago)

Improved character encoding routines to handle incorrect input datas like
UTF-8 encoded strings received with an ISO-8859-1 charset, thanks to
CUPS and Xprint !

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