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

Revision 2807, 34.4 kB (checked in by jerome, 18 years ago)

Backported unicode fix from pykoticon

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