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

Revision 3020, 35.1 kB (checked in by jerome, 18 years ago)

Removed unneeded code to retrieve the correct character set.

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