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

Revision 2830, 34.3 kB (checked in by jerome, 18 years ago)

Improved the code's quality a bit with pylint.

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