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

Revision 2992, 34.7 kB (checked in by jerome, 16 years ago)

Added a TODO remark about the value of grouppquota.PageCounter?

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1# PyKota
2# -*- coding: ISO-8859-15 -*-
3
4# PyKota - Print Quotas for CUPS and LPRng
5#
6# (c) 2003, 2004, 2005, 2006 Jerome Alet <alet@librelogiciel.com>
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20#
21# $Id$
22#
23#
24
25import sys
26import os
27import pwd
28import fnmatch
29import getopt
30import smtplib
31import gettext
32import locale
33import socket
34import time
35from email.MIMEText import MIMEText
36from email.Header import Header
37
38from mx import DateTime
39
40from pykota import config, storage, logger
41from pykota.version import __version__, __author__, __years__, __gplblurb__
42
43def N_(message) :
44    """Fake translation marker for translatable strings extraction."""
45    return message
46
47class PyKotaToolError(Exception):
48    """An exception for PyKota related stuff."""
49    def __init__(self, message = ""):
50        self.message = message
51        Exception.__init__(self, message)
52    def __repr__(self):
53        return self.message
54    __str__ = __repr__
55   
56class PyKotaCommandLineError(PyKotaToolError) :   
57    """An exception for Pykota command line tools."""
58    pass
59   
60def crashed(message="Bug in PyKota") :   
61    """Minimal crash method."""
62    import traceback
63    lines = []
64    for line in traceback.format_exception(*sys.exc_info()) :
65        lines.extend([l for l in line.split("\n") if l])
66    msg = "ERROR: ".join(["%s\n" % l for l in (["ERROR: PyKota v%s" % __version__, message] + lines)])
67    sys.stderr.write(msg)
68    sys.stderr.flush()
69    return msg
70
71class Percent :
72    """A class to display progress."""
73    def __init__(self, app, size=None) :
74        """Initializes the engine."""
75        self.app = app
76        self.size = None
77        if size :
78            self.setSize(size)
79        self.previous = None
80        self.before = time.time()
81       
82    def setSize(self, size) :     
83        """Sets the total size."""
84        self.number = 0
85        self.size = size
86        if size :
87            self.factor = 100.0 / float(size)
88       
89    def display(self, msg) :   
90        """Displays the value."""
91        self.app.display(msg)
92       
93    def oneMore(self) :   
94        """Increments internal counter."""
95        if self.size :
96            self.number += 1
97            percent = "%.02f" % (float(self.number) * self.factor)
98            if percent != self.previous : # optimize for large number of items
99                self.display("\r%s%%" % percent)
100                self.previous = percent
101           
102    def done(self) :         
103        """Displays the 'done' message."""
104        after = time.time()
105        if self.size :
106            speed = self.size / (after - self.before)
107            self.display("\r100.00%%\r        \r%s. %s : %.2f %s.\n" \
108                     % (_("Done"), _("Average speed"), speed, _("entries per second")))
109        else :             
110            self.display("\r100.00%%\r        \r%s.\n" % _("Done"))
111       
112class Tool :
113    """Base class for tools with no database access."""
114    def __init__(self, lang="", charset=None, doc="PyKota v%(__version__)s (c) %(__years__)s %(__author__)s") :
115        """Initializes the command line tool."""
116        # did we drop priviledges ?
117        self.privdropped = 0
118       
119        # locale stuff
120        self.defaultToCLocale = 0
121        try :
122            locale.setlocale(locale.LC_ALL, lang)
123        except (locale.Error, IOError) :
124            # locale.setlocale(locale.LC_ALL, "C")
125            self.defaultToCLocale = 1
126        try :
127            gettext.install("pykota")
128        except :
129            gettext.NullTranslations().install()
130           
131        # We can force the charset.   
132        # The CHARSET environment variable is set by CUPS when printing.
133        # Else we use the current locale's one.
134        # If nothing is set, we use ISO-8859-15 widely used in western Europe.
135        localecharset = None
136        try :
137            try :
138                localecharset = locale.nl_langinfo(locale.CODESET)
139            except AttributeError :   
140                try :
141                    localecharset = locale.getpreferredencoding()
142                except AttributeError :   
143                    try :
144                        localecharset = locale.getlocale()[1]
145                        localecharset = localecharset or locale.getdefaultlocale()[1]
146                    except ValueError :   
147                        pass        # Unknown locale, strange...
148        except locale.Error :           
149            pass
150        self.charset = charset or os.environ.get("CHARSET") or localecharset or "ISO-8859-15"
151   
152        # pykota specific stuff
153        self.documentation = doc
154       
155    def deferredInit(self) :       
156        """Deferred initialization."""
157        # try to find the configuration files in user's 'pykota' home directory.
158        try :
159            self.pykotauser = pwd.getpwnam("pykota")
160        except KeyError :   
161            self.pykotauser = None
162            confdir = "/etc/pykota"
163            missingUser = 1
164        else :   
165            confdir = self.pykotauser[5]
166            missingUser = 0
167           
168        self.config = config.PyKotaConfig(confdir)
169        self.debug = self.config.getDebug()
170        self.smtpserver = self.config.getSMTPServer()
171        self.maildomain = self.config.getMailDomain()
172        self.logger = logger.openLogger(self.config.getLoggingBackend())
173           
174        # now drop priviledge if possible
175        self.dropPriv()   
176       
177        # We NEED this here, even when not in an accounting filter/backend   
178        self.softwareJobSize = 0
179        self.softwareJobPrice = 0.0
180       
181        if self.defaultToCLocale :
182            self.printInfo("Incorrect locale settings. PyKota falls back to the default locale.", "warn")
183        if missingUser :     
184            self.printInfo("The 'pykota' system account is missing. Configuration files were searched in /etc/pykota instead.", "warn")
185       
186        self.logdebug("Charset in use : %s" % self.charset)
187        arguments = " ".join(['"%s"' % arg for arg in sys.argv])
188        self.logdebug("Command line arguments : %s" % arguments)
189       
190    def dropPriv(self) :   
191        """Drops priviledges."""
192        uid = os.geteuid()
193        try :
194            self.originalUserName = pwd.getpwuid(uid)[0]
195        except (KeyError, IndexError), msg :   
196            self.printInfo(_("Strange problem with uid(%s) : %s") % (uid, msg), "warn")
197            self.originalUserName = None
198        else :
199            if uid :
200                self.logdebug(_("Running as user '%s'.") % self.originalUserName)
201            else :
202                if self.pykotauser is None :
203                    self.logdebug(_("No user named 'pykota'. Not dropping priviledges."))
204                else :   
205                    try :
206                        os.setegid(self.pykotauser[3])
207                        os.seteuid(self.pykotauser[2])
208                    except OSError, msg :   
209                        self.printInfo(_("Impossible to drop priviledges : %s") % msg, "warn")
210                    else :   
211                        self.logdebug(_("Priviledges dropped. Now running as user 'pykota'."))
212                        self.privdropped = 1
213           
214    def regainPriv(self) :   
215        """Drops priviledges."""
216        if self.privdropped :
217            try :
218                os.seteuid(0)
219                os.setegid(0)
220            except OSError, msg :   
221                self.printInfo(_("Impossible to regain priviledges : %s") % msg, "warn")
222            else :   
223                self.logdebug(_("Regained priviledges. Now running as root."))
224                self.privdropped = 0
225       
226    def getCharset(self) :   
227        """Returns the charset in use."""
228        return self.charset
229       
230    def UTF8ToUserCharset(self, text) :
231        """Converts from UTF-8 to user's charset."""
232        if text is not None :
233            try :
234                return text.decode("UTF-8").encode(self.charset, "replace") 
235            except (UnicodeError, AttributeError) :   
236                try :
237                    # Maybe already in Unicode
238                    return text.encode(self.charset, "replace") 
239                except (UnicodeError, AttributeError) :
240                    pass # Don't know what to do
241        return text
242       
243    def userCharsetToUTF8(self, text) :
244        """Converts from user's charset to UTF-8."""
245        if text is not None :
246            try :
247                # We don't necessarily trust the default charset, because
248                # xprint sends us titles in UTF-8 but CUPS gives us an ISO-8859-1 charset !
249                # So we first try to see if the text is already in UTF-8 or not, and
250                # if it is, we delete characters which can't be converted to the user's charset,
251                # then convert back to UTF-8. PostgreSQL 7.3.x used to reject some unicode characters,
252                # this is fixed by the ugly line below :
253                return text.decode("UTF-8").encode(self.charset, "replace").decode(self.charset).encode("UTF-8", "replace")
254            except (UnicodeError, AttributeError) :
255                try :
256                    return text.decode(self.charset).encode("UTF-8", "replace") 
257                except (UnicodeError, AttributeError) :   
258                    try :
259                        # Maybe already in Unicode
260                        return text.encode("UTF-8", "replace") 
261                    except (UnicodeError, AttributeError) :
262                        pass # Don't know what to do
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(str(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 += int(self.softwareJobSize) # TODO : this is not a fix, problem is elsewhere in grouppquota.PageCounter
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(str(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.