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

Revision 2804, 34.0 kB (checked in by jerome, 18 years ago)

Ensures that all texts sent by pknotify to a remote pykoticon server
are UTF-8 encoded.

  • 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 :   
245                try :
246                    # Incorrect locale settings ?
247                    return unicode(text, "UTF-8").encode("ISO-8859-15") 
248                except UnicodeError :   
249                    pass
250        return text
251       
252    def userCharsetToUTF8(self, text) :
253        """Converts from user's charset to UTF-8."""
254        if text is not None :
255            try :
256                return unicode(text, self.charset).encode("UTF-8") 
257            except UnicodeError :   
258                try :
259                    # Incorrect locale settings ?
260                    return unicode(text, "ISO-8859-15").encode("UTF-8") 
261                except UnicodeError :   
262                    pass
263        return text
264       
265    def display(self, message) :
266        """Display a message but only if stdout is a tty."""
267        if sys.stdout.isatty() :
268            sys.stdout.write(message)
269            sys.stdout.flush()
270           
271    def logdebug(self, message) :   
272        """Logs something to debug output if debug is enabled."""
273        if self.debug :
274            self.logger.log_message(message, "debug")
275           
276    def printInfo(self, message, level="info") :       
277        """Sends a message to standard error."""
278        sys.stderr.write("%s: %s\n" % (level.upper(), message))
279        sys.stderr.flush()
280       
281    def matchString(self, s, patterns) :
282        """Returns True if the string s matches one of the patterns, else False."""
283        if not patterns :
284            return True # No pattern, always matches.
285        else :   
286            for pattern in patterns :
287                if fnmatch.fnmatchcase(s, pattern) :
288                    return True
289            return False
290       
291    def sanitizeNames(self, options, names) :
292        """Ensures that an user can only see the datas he is allowed to see, by modifying the list of names."""
293        if not self.config.isAdmin :
294            username = pwd.getpwuid(os.geteuid())[0]
295            if not options["list"] :
296                raise PyKotaCommandLineError, "%s : %s" % (username, _("You're not allowed to use this command."))
297            else :
298                if options["groups"] :
299                    user = self.storage.getUser(username)
300                    if user.Exists :
301                        return [ g.Name for g in self.storage.getUserGroups(user) ]
302                return [ username ]
303        elif not names :       
304            return ["*"]
305        else :   
306            return names
307       
308    def display_version_and_quit(self) :
309        """Displays version number, then exists successfully."""
310        try :
311            self.clean()
312        except AttributeError :   
313            pass
314        print __version__
315        sys.exit(0)
316   
317    def display_usage_and_quit(self) :
318        """Displays command line usage, then exists successfully."""
319        try :
320            self.clean()
321        except AttributeError :   
322            pass
323        print _(self.documentation) % globals()
324        print __gplblurb__
325        print
326        print _("Please report bugs to :"), __author__
327        sys.exit(0)
328       
329    def crashed(self, message="Bug in PyKota") :   
330        """Outputs a crash message, and optionally sends it to software author."""
331        msg = crashed(message)
332        fullmessage = "========== Traceback :\n\n%s\n\n========== sys.argv :\n\n%s\n\n========== Environment :\n\n%s\n" % \
333                        (msg, \
334                         "\n".join(["    %s" % repr(a) for a in sys.argv]), \
335                         "\n".join(["    %s=%s" % (k, v) for (k, v) in os.environ.items()]))
336        try :
337            crashrecipient = self.config.getCrashRecipient()
338            if crashrecipient :
339                admin = self.config.getAdminMail("global") # Nice trick, isn't it ?
340                server = smtplib.SMTP(self.smtpserver)
341                msg = MIMEText(fullmessage, _charset=self.charset)
342                msg["Subject"] = str(Header("PyKota v%s crash traceback !" \
343                                        % __version__, charset=self.charset))
344                msg["From"] = admin
345                msg["To"] = crashrecipient
346                msg["Cc"] = admin
347                server.sendmail(admin, [admin, crashrecipient], msg.as_string())
348                server.quit()
349        except :
350            pass
351        return fullmessage   
352       
353    def parseCommandline(self, argv, short, long, allownothing=0) :
354        """Parses the command line, controlling options."""
355        # split options in two lists: those which need an argument, those which don't need any
356        short = "%sA:" % short
357        long.append("arguments=")
358        withoutarg = []
359        witharg = []
360        lgs = len(short)
361        i = 0
362        while i < lgs :
363            ii = i + 1
364            if (ii < lgs) and (short[ii] == ':') :
365                # needs an argument
366                witharg.append(short[i])
367                ii = ii + 1 # skip the ':'
368            else :
369                # doesn't need an argument
370                withoutarg.append(short[i])
371            i = ii
372               
373        for option in long :
374            if option[-1] == '=' :
375                # needs an argument
376                witharg.append(option[:-1])
377            else :
378                # doesn't need an argument
379                withoutarg.append(option)
380       
381        # then we parse the command line
382        done = 0
383        while not done :
384            # we begin with all possible options unset
385            parsed = {}
386            for option in withoutarg + witharg :
387                parsed[option] = None
388            args = []       # to not break if something unexpected happened
389            try :
390                options, args = getopt.getopt(argv, short, long)
391                if options :
392                    for (o, v) in options :
393                        # we skip the '-' chars
394                        lgo = len(o)
395                        i = 0
396                        while (i < lgo) and (o[i] == '-') :
397                            i = i + 1
398                        o = o[i:]
399                        if o in witharg :
400                            # needs an argument : set it
401                            parsed[o] = v
402                        elif o in withoutarg :
403                            # doesn't need an argument : boolean
404                            parsed[o] = 1
405                        else :
406                            # should never occur
407                            raise PyKotaCommandLineError, "Unexpected problem when parsing command line"
408                elif (not args) and (not allownothing) and sys.stdin.isatty() : # no option and no argument, we display help if we are a tty
409                    self.display_usage_and_quit()
410            except getopt.error, msg :
411                raise PyKotaCommandLineError, str(msg)
412            else :   
413                if parsed["arguments"] or parsed["A"] :
414                    # arguments are in a file, we ignore all other arguments
415                    # and reset the list of arguments to the lines read from
416                    # the file.
417                    argsfile = open(parsed["arguments"] or parsed["A"], "r")
418                    argv = [ l.strip() for l in argsfile.readlines() ]
419                    argsfile.close()
420                    for i in range(len(argv)) :
421                        argi = argv[i]
422                        if argi.startswith('"') and argi.endswith('"') :
423                            argv[i] = argi[1:-1]
424                else :   
425                    done = 1
426        return (parsed, args)
427   
428class PyKotaTool(Tool) :   
429    """Base class for all PyKota command line tools."""
430    def __init__(self, lang="", charset=None, doc="PyKota v%(__version__)s (c) %(__years__)s %(__author__)s") :
431        """Initializes the command line tool and opens the database."""
432        Tool.__init__(self, lang, charset, doc)
433       
434    def deferredInit(self) :   
435        """Deferred initialization."""
436        Tool.deferredInit(self)
437        self.storage = storage.openConnection(self)
438        if self.config.isAdmin : # TODO : We don't know this before, fix this !
439            self.logdebug("Beware : running as a PyKota administrator !")
440        else :   
441            self.logdebug("Don't Panic : running as a mere mortal !")
442       
443    def clean(self) :   
444        """Ensures that the database is closed."""
445        try :
446            self.storage.close()
447        except (TypeError, NameError, AttributeError) :   
448            pass
449           
450    def isValidName(self, name) :
451        """Checks if a user or printer name is valid."""
452        invalidchars = "/@?*,;&|"
453        for c in list(invalidchars) :
454            if c in name :
455                return 0
456        return 1       
457       
458    def sendMessage(self, adminmail, touser, fullmessage) :
459        """Sends an email message containing headers to some user."""
460        try :   
461            server = smtplib.SMTP(self.smtpserver)
462        except socket.error, msg :   
463            self.printInfo(_("Impossible to connect to SMTP server : %s") % msg, "error")
464        else :
465            try :
466                server.sendmail(adminmail, [touser], fullmessage)
467            except smtplib.SMTPException, answer :   
468                for (k, v) in answer.recipients.items() :
469                    self.printInfo(_("Impossible to send mail to %s, error %s : %s") % (k, v[0], v[1]), "error")
470            server.quit()
471       
472    def sendMessageToUser(self, admin, adminmail, user, subject, message) :
473        """Sends an email message to a user."""
474        message += _("\n\nPlease contact your system administrator :\n\n\t%s - <%s>\n") % (admin, adminmail)
475        usermail = user.Email or user.Name
476        if "@" not in usermail :
477            usermail = "%s@%s" % (usermail, self.maildomain or self.smtpserver or "localhost")
478        msg = MIMEText(message, _charset=self.charset)
479        msg["Subject"] = str(Header(subject, charset=self.charset))
480        msg["From"] = adminmail
481        msg["To"] = usermail
482        self.sendMessage(adminmail, usermail, msg.as_string())
483       
484    def sendMessageToAdmin(self, adminmail, subject, message) :
485        """Sends an email message to the Print Quota administrator."""
486        if "@" not in adminmail :
487            adminmail = "%s@%s" % (adminmail, self.maildomain or self.smtpserver or "localhost")
488        msg = MIMEText(message, _charset=self.charset)
489        msg["Subject"] = str(Header(subject, charset=self.charset))
490        msg["From"] = adminmail
491        msg["To"] = adminmail
492        self.sendMessage(adminmail, adminmail, msg.as_string())
493       
494    def _checkUserPQuota(self, userpquota) :           
495        """Checks the user quota on a printer and deny or accept the job."""
496        # then we check the user's own quota
497        # if we get there we are sure that policy is not EXTERNAL
498        user = userpquota.User
499        printer = userpquota.Printer
500        enforcement = self.config.getPrinterEnforcement(printer.Name)
501        self.logdebug("Checking user %s's quota on printer %s" % (user.Name, printer.Name))
502        (policy, dummy) = self.config.getPrinterPolicy(userpquota.Printer.Name)
503        if not userpquota.Exists :
504            # Unknown userquota
505            if policy == "ALLOW" :
506                action = "POLICY_ALLOW"
507            else :   
508                action = "POLICY_DENY"
509            self.printInfo(_("Unable to match user %s on printer %s, applying default policy (%s)") % (user.Name, printer.Name, action))
510        else :   
511            pagecounter = int(userpquota.PageCounter or 0)
512            if enforcement == "STRICT" :
513                pagecounter += self.softwareJobSize
514            if userpquota.SoftLimit is not None :
515                softlimit = int(userpquota.SoftLimit)
516                if pagecounter < softlimit :
517                    action = "ALLOW"
518                else :   
519                    if userpquota.HardLimit is None :
520                        # only a soft limit, this is equivalent to having only a hard limit
521                        action = "DENY"
522                    else :   
523                        hardlimit = int(userpquota.HardLimit)
524                        if softlimit <= pagecounter < hardlimit :   
525                            now = DateTime.now()
526                            if userpquota.DateLimit is not None :
527                                datelimit = DateTime.ISO.ParseDateTime(userpquota.DateLimit)
528                            else :
529                                datelimit = now + self.config.getGraceDelay(printer.Name)
530                                userpquota.setDateLimit(datelimit)
531                            if now < datelimit :
532                                action = "WARN"
533                            else :   
534                                action = "DENY"
535                        else :         
536                            action = "DENY"
537            else :       
538                if userpquota.HardLimit is not None :
539                    # no soft limit, only a hard one.
540                    hardlimit = int(userpquota.HardLimit)
541                    if pagecounter < hardlimit :
542                        action = "ALLOW"
543                    else :     
544                        action = "DENY"
545                else :
546                    # Both are unset, no quota, i.e. accounting only
547                    action = "ALLOW"
548        return action
549   
550    def checkGroupPQuota(self, grouppquota) :   
551        """Checks the group quota on a printer and deny or accept the job."""
552        group = grouppquota.Group
553        printer = grouppquota.Printer
554        enforcement = self.config.getPrinterEnforcement(printer.Name)
555        self.logdebug("Checking group %s's quota on printer %s" % (group.Name, printer.Name))
556        if group.LimitBy and (group.LimitBy.lower() == "balance") : 
557            val = group.AccountBalance or 0.0
558            if enforcement == "STRICT" : 
559                val -= self.softwareJobPrice # use precomputed size.
560            balancezero = self.config.getBalanceZero()
561            if val <= balancezero :
562                action = "DENY"
563            elif val <= self.config.getPoorMan() :   
564                action = "WARN"
565            else :   
566                action = "ALLOW"
567            if (enforcement == "STRICT") and (val == balancezero) :
568                action = "WARN" # we can still print until account is 0
569        else :
570            val = grouppquota.PageCounter or 0
571            if enforcement == "STRICT" :
572                val += self.softwareJobSize
573            if grouppquota.SoftLimit is not None :
574                softlimit = int(grouppquota.SoftLimit)
575                if val < softlimit :
576                    action = "ALLOW"
577                else :   
578                    if grouppquota.HardLimit is None :
579                        # only a soft limit, this is equivalent to having only a hard limit
580                        action = "DENY"
581                    else :   
582                        hardlimit = int(grouppquota.HardLimit)
583                        if softlimit <= val < hardlimit :   
584                            now = DateTime.now()
585                            if grouppquota.DateLimit is not None :
586                                datelimit = DateTime.ISO.ParseDateTime(grouppquota.DateLimit)
587                            else :
588                                datelimit = now + self.config.getGraceDelay(printer.Name)
589                                grouppquota.setDateLimit(datelimit)
590                            if now < datelimit :
591                                action = "WARN"
592                            else :   
593                                action = "DENY"
594                        else :         
595                            action = "DENY"
596            else :       
597                if grouppquota.HardLimit is not None :
598                    # no soft limit, only a hard one.
599                    hardlimit = int(grouppquota.HardLimit)
600                    if val < hardlimit :
601                        action = "ALLOW"
602                    else :     
603                        action = "DENY"
604                else :
605                    # Both are unset, no quota, i.e. accounting only
606                    action = "ALLOW"
607        return action
608   
609    def checkUserPQuota(self, userpquota) :
610        """Checks the user quota on a printer and all its parents and deny or accept the job."""
611        user = userpquota.User
612        printer = userpquota.Printer
613       
614        # indicates that a warning needs to be sent
615        warned = 0               
616       
617        # first we check any group the user is a member of
618        for group in self.storage.getUserGroups(user) :
619            # No need to check anything if the group is in noquota mode
620            if group.LimitBy != "noquota" :
621                grouppquota = self.storage.getGroupPQuota(group, printer)
622                # for the printer and all its parents
623                for gpquota in [ grouppquota ] + grouppquota.ParentPrintersGroupPQuota :
624                    if gpquota.Exists :
625                        action = self.checkGroupPQuota(gpquota)
626                        if action == "DENY" :
627                            return action
628                        elif action == "WARN" :   
629                            warned = 1
630                       
631        # Then we check the user's account balance
632        # if we get there we are sure that policy is not EXTERNAL
633        (policy, dummy) = self.config.getPrinterPolicy(printer.Name)
634        if user.LimitBy and (user.LimitBy.lower() == "balance") : 
635            self.logdebug("Checking account balance for user %s" % user.Name)
636            if user.AccountBalance is None :
637                if policy == "ALLOW" :
638                    action = "POLICY_ALLOW"
639                else :   
640                    action = "POLICY_DENY"
641                self.printInfo(_("Unable to find user %s's account balance, applying default policy (%s) for printer %s") % (user.Name, action, printer.Name))
642                return action       
643            else :   
644                if user.OverCharge == 0.0 :
645                    self.printInfo(_("User %s will not be charged for printing.") % user.Name)
646                    action = "ALLOW"
647                else :
648                    val = float(user.AccountBalance or 0.0)
649                    enforcement = self.config.getPrinterEnforcement(printer.Name)
650                    if enforcement == "STRICT" : 
651                        val -= self.softwareJobPrice # use precomputed size.
652                    balancezero = self.config.getBalanceZero()   
653                    if val <= balancezero :
654                        action = "DENY"
655                    elif val <= self.config.getPoorMan() :   
656                        action = "WARN"
657                    else :
658                        action = "ALLOW"
659                    if (enforcement == "STRICT") and (val == balancezero) :
660                        action = "WARN" # we can still print until account is 0
661                return action   
662        else :
663            # Then check the user quota on current printer and all its parents.               
664            policyallowed = 0
665            for upquota in [ userpquota ] + userpquota.ParentPrintersUserPQuota :               
666                action = self._checkUserPQuota(upquota)
667                if action in ("DENY", "POLICY_DENY") :
668                    return action
669                elif action == "WARN" :   
670                    warned = 1
671                elif action == "POLICY_ALLOW" :   
672                    policyallowed = 1
673            if warned :       
674                return "WARN"
675            elif policyallowed :   
676                return "POLICY_ALLOW" 
677            else :   
678                return "ALLOW"
679               
680    def externalMailTo(self, cmd, action, user, printer, message) :
681        """Warns the user with an external command."""
682        username = user.Name
683        printername = printer.Name
684        email = user.Email or user.Name
685        if "@" not in email :
686            email = "%s@%s" % (email, self.maildomain or self.smtpserver)
687        os.system(cmd % locals())
688   
689    def formatCommandLine(self, cmd, user, printer) :
690        """Executes an external command."""
691        username = user.Name
692        printername = printer.Name
693        return cmd % locals()
694       
695    def warnGroupPQuota(self, grouppquota) :
696        """Checks a group quota and send messages if quota is exceeded on current printer."""
697        group = grouppquota.Group
698        printer = grouppquota.Printer
699        admin = self.config.getAdmin(printer.Name)
700        adminmail = self.config.getAdminMail(printer.Name)
701        (mailto, arguments) = self.config.getMailTo(printer.Name)
702        if group.LimitBy in ("noquota", "nochange") :
703            action = "ALLOW"
704        else :   
705            action = self.checkGroupPQuota(grouppquota)
706            if action.startswith("POLICY_") :
707                action = action[7:]
708            if action == "DENY" :
709                adminmessage = _("Print Quota exceeded for group %s on printer %s") % (group.Name, printer.Name)
710                self.printInfo(adminmessage)
711                if mailto in [ "BOTH", "ADMIN" ] :
712                    self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
713                if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
714                    for user in self.storage.getGroupMembers(group) :
715                        if mailto != "EXTERNAL" :
716                            self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), self.config.getHardWarn(printer.Name))
717                        else :   
718                            self.externalMailTo(arguments, action, user, printer, self.config.getHardWarn(printer.Name))
719            elif action == "WARN" :   
720                adminmessage = _("Print Quota low for group %s on printer %s") % (group.Name, printer.Name)
721                self.printInfo(adminmessage)
722                if mailto in [ "BOTH", "ADMIN" ] :
723                    self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
724                if group.LimitBy and (group.LimitBy.lower() == "balance") : 
725                    message = self.config.getPoorWarn()
726                else :     
727                    message = self.config.getSoftWarn(printer.Name)
728                if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
729                    for user in self.storage.getGroupMembers(group) :
730                        if mailto != "EXTERNAL" :
731                            self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), message)
732                        else :   
733                            self.externalMailTo(arguments, action, user, printer, message)
734        return action       
735       
736    def warnUserPQuota(self, userpquota) :
737        """Checks a user quota and send him a message if quota is exceeded on current printer."""
738        user = userpquota.User
739        printer = userpquota.Printer
740        admin = self.config.getAdmin(printer.Name)
741        adminmail = self.config.getAdminMail(printer.Name)
742        (mailto, arguments) = self.config.getMailTo(printer.Name)
743       
744        if user.LimitBy in ("noquota", "nochange") :
745            action = "ALLOW"
746        elif user.LimitBy == "noprint" :
747            action = "DENY"
748            message = _("User %s is not allowed to print at this time.") % user.Name
749            self.printInfo(message)
750            if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
751                if mailto != "EXTERNAL" :
752                    self.sendMessageToUser(admin, adminmail, user, _("Printing denied."), message)
753                else :   
754                    self.externalMailTo(arguments, action, user, printer, message)
755            if mailto in [ "BOTH", "ADMIN" ] :
756                self.sendMessageToAdmin(adminmail, _("Print Quota"), message)
757        else :
758            action = self.checkUserPQuota(userpquota)
759            if action.startswith("POLICY_") :
760                action = action[7:]
761               
762            if action == "DENY" :
763                adminmessage = _("Print Quota exceeded for user %s on printer %s") % (user.Name, printer.Name)
764                self.printInfo(adminmessage)
765                if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
766                    message = self.config.getHardWarn(printer.Name)
767                    if mailto != "EXTERNAL" :
768                        self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), message)
769                    else :   
770                        self.externalMailTo(arguments, action, user, printer, message)
771                if mailto in [ "BOTH", "ADMIN" ] :
772                    self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
773            elif action == "WARN" :   
774                adminmessage = _("Print Quota low for user %s on printer %s") % (user.Name, printer.Name)
775                self.printInfo(adminmessage)
776                if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
777                    if user.LimitBy and (user.LimitBy.lower() == "balance") : 
778                        message = self.config.getPoorWarn()
779                    else :     
780                        message = self.config.getSoftWarn(printer.Name)
781                    if mailto != "EXTERNAL" :   
782                        self.sendMessageToUser(admin, adminmail, user, _("Print Quota Low"), message)
783                    else :   
784                        self.externalMailTo(arguments, action, user, printer, message)
785                if mailto in [ "BOTH", "ADMIN" ] :
786                    self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
787        return action       
788       
Note: See TracBrowser for help on using the browser.