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

Revision 825, 11.9 kB (checked in by jalet, 21 years ago)

Correctly maps PyKota's log levels to syslog log levels

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1#! /usr/bin/env python
2
3# PyKota - Print Quotas for CUPS
4#
5# (c) 2003 Jerome Alet <alet@librelogiciel.com>
6# You're welcome to redistribute this software under the
7# terms of the GNU General Public Licence version 2.0
8# or, at your option, any higher version.
9#
10# You can read the complete GNU GPL in the file COPYING
11# which should come along with this software, or visit
12# the Free Software Foundation's WEB site http://www.fsf.org
13#
14# $Id$
15#
16# $Log$
17# Revision 1.24  2003/02/27 23:48:41  jalet
18# Correctly maps PyKota's log levels to syslog log levels
19#
20# Revision 1.23  2003/02/27 22:55:20  jalet
21# WARN log priority doesn't exist.
22#
23# Revision 1.22  2003/02/27 09:09:20  jalet
24# Added a method to match strings against wildcard patterns
25#
26# Revision 1.21  2003/02/17 23:01:56  jalet
27# Typos
28#
29# Revision 1.20  2003/02/17 22:55:01  jalet
30# More options can now be set per printer or globally :
31#
32#       admin
33#       adminmail
34#       gracedelay
35#       requester
36#
37# the printer option has priority when both are defined.
38#
39# Revision 1.19  2003/02/10 11:28:45  jalet
40# Localization
41#
42# Revision 1.18  2003/02/10 01:02:17  jalet
43# External requester is about to work, but I must sleep
44#
45# Revision 1.17  2003/02/09 13:05:43  jalet
46# Internationalization continues...
47#
48# Revision 1.16  2003/02/09 12:56:53  jalet
49# Internationalization begins...
50#
51# Revision 1.15  2003/02/08 22:09:52  jalet
52# Name check method moved here
53#
54# Revision 1.14  2003/02/07 10:42:45  jalet
55# Indentation problem
56#
57# Revision 1.13  2003/02/07 08:34:16  jalet
58# Test wrt date limit was wrong
59#
60# Revision 1.12  2003/02/06 23:20:02  jalet
61# warnpykota doesn't need any user/group name argument, mimicing the
62# warnquota disk quota tool.
63#
64# Revision 1.11  2003/02/06 22:54:33  jalet
65# warnpykota should be ok
66#
67# Revision 1.10  2003/02/06 15:03:11  jalet
68# added a method to set the limit date
69#
70# Revision 1.9  2003/02/06 10:39:23  jalet
71# Preliminary edpykota work.
72#
73# Revision 1.8  2003/02/06 09:19:02  jalet
74# More robust behavior (hopefully) when the user or printer is not managed
75# correctly by the Quota System : e.g. cupsFilter added in ppd file, but
76# printer and/or user not 'yet?' in storage.
77#
78# Revision 1.7  2003/02/06 00:00:45  jalet
79# Now includes the printer name in email messages
80#
81# Revision 1.6  2003/02/05 23:55:02  jalet
82# Cleaner email messages
83#
84# Revision 1.5  2003/02/05 23:45:09  jalet
85# Better DateTime manipulation wrt grace delay
86#
87# Revision 1.4  2003/02/05 23:26:22  jalet
88# Incorrect handling of grace delay
89#
90# Revision 1.3  2003/02/05 22:16:20  jalet
91# DEVICE_URI is undefined outside of CUPS, i.e. for normal command line tools
92#
93# Revision 1.2  2003/02/05 22:10:29  jalet
94# Typos
95#
96# Revision 1.1  2003/02/05 21:28:17  jalet
97# Initial import into CVS
98#
99#
100#
101
102import sys
103import os
104import fnmatch
105import getopt
106import smtplib
107import gettext
108import locale
109
110from mx import DateTime
111
112from pykota import version, config, storage, logger
113
114class PyKotaToolError(Exception):
115    """An exception for PyKota config related stuff."""
116    def __init__(self, message = ""):
117        self.message = message
118        Exception.__init__(self, message)
119    def __repr__(self):
120        return self.message
121    __str__ = __repr__
122   
123class PyKotaTool :   
124    """Base class for all PyKota command line tools."""
125    def __init__(self, isfilter=0, doc="PyKota %s (c) 2003 %s" % (version.__version__, version.__author__)) :
126        """Initializes the command line tool."""
127        # locale stuff
128        try :
129            locale.setlocale(locale.LC_ALL, "")
130            gettext.install("pykota")
131        except (locale.Error, IOError) :
132            gettext.NullTranslations().install()
133   
134        # pykota specific stuff
135        self.documentation = doc
136        self.config = config.PyKotaConfig(os.environ.get("CUPS_SERVERROOT", "/etc/cups"))
137        self.logger = logger.openLogger(self.config)
138        self.storage = storage.openConnection(self.config, asadmin=(not isfilter))
139        self.printername = os.environ.get("PRINTER", None)
140        self.smtpserver = self.config.getSMTPServer()
141       
142    def display_version_and_quit(self) :
143        """Displays version number, then exists successfully."""
144        print version.__version__
145        sys.exit(0)
146   
147    def display_usage_and_quit(self) :
148        """Displays command line usage, then exists successfully."""
149        print self.documentation
150        sys.exit(0)
151       
152    def parseCommandline(self, argv, short, long, allownothing=0) :
153        """Parses the command line, controlling options."""
154        # split options in two lists: those which need an argument, those which don't need any
155        withoutarg = []
156        witharg = []
157        lgs = len(short)
158        i = 0
159        while i < lgs :
160            ii = i + 1
161            if (ii < lgs) and (short[ii] == ':') :
162                # needs an argument
163                witharg.append(short[i])
164                ii = ii + 1 # skip the ':'
165            else :
166                # doesn't need an argument
167                withoutarg.append(short[i])
168            i = ii
169               
170        for option in long :
171            if option[-1] == '=' :
172                # needs an argument
173                witharg.append(option[:-1])
174            else :
175                # doesn't need an argument
176                withoutarg.append(option)
177       
178        # we begin with all possible options unset
179        parsed = {}
180        for option in withoutarg + witharg :
181            parsed[option] = None
182       
183        # then we parse the command line
184        args = []       # to not break if something unexpected happened
185        try :
186            options, args = getopt.getopt(argv, short, long)
187            if options :
188                for (o, v) in options :
189                    # we skip the '-' chars
190                    lgo = len(o)
191                    i = 0
192                    while (i < lgo) and (o[i] == '-') :
193                        i = i + 1
194                    o = o[i:]
195                    if o in witharg :
196                        # needs an argument : set it
197                        parsed[o] = v
198                    elif o in withoutarg :
199                        # doesn't need an argument : boolean
200                        parsed[o] = 1
201                    else :
202                        # should never occur
203                        raise PyKotaToolError, "Unexpected problem when parsing command line"
204            elif (not args) and (not allownothing) and sys.stdin.isatty() : # no option and no argument, we display help if we are a tty
205                self.display_usage_and_quit()
206        except getopt.error, msg :
207            sys.stderr.write("%s\n" % msg)
208            sys.stderr.flush()
209            self.display_usage_and_quit()
210        return (parsed, args)
211   
212    def isValidName(self, name) :
213        """Checks if a user or printer name is valid."""
214        # unfortunately Python 2.1 string modules doesn't define ascii_letters...
215        asciiletters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
216        digits = '0123456789'
217        if name[0] in asciiletters :
218            validchars = asciiletters + digits + "-_"
219            for c in name[1:] :
220                if c not in validchars :
221                    return 0
222            return 1       
223        return 0
224       
225    def matchString(self, s, patterns) :
226        """Returns 1 if the string s matches one of the patterns, else 0."""
227        for pattern in patterns :
228            if fnmatch.fnmatchcase(s, pattern) :
229                return 1
230        return 0
231       
232    def sendMessage(self, adminmail, touser, fullmessage) :
233        """Sends an email message containing headers to some user."""
234        if "@" not in touser :
235            touser = "%s@%s" % (touser, self.smtpserver)
236        server = smtplib.SMTP(self.smtpserver)
237        server.sendmail(adminmail, [touser], fullmessage)
238        server.quit()
239       
240    def sendMessageToUser(self, admin, adminmail, username, subject, message) :
241        """Sends an email message to a user."""
242        message += _("\n\nPlease contact your system administrator :\n\n\t%s - <%s>\n") % (admin, adminmail)
243        self.sendMessage(adminmail, username, "Subject: %s\n\n%s" % (subject, message))
244       
245    def sendMessageToAdmin(self, adminmail, subject, message) :
246        """Sends an email message to the Print Quota administrator."""
247        self.sendMessage(adminmail, adminmail, "Subject: %s\n\n%s" % (subject, message))
248       
249    def checkUserPQuota(self, username, printername) :
250        """Checks the user quota on a printer and deny or accept the job."""
251        quota = self.storage.getUserPQuota(username, printername)
252        if quota is None :
253            # Unknown user or printer or combination
254            policy = self.config.getPrinterPolicy(printername)
255            if policy in [None, "ALLOW"] :
256                action = "ALLOW"
257            else :   
258                action = "DENY"
259            self.logger.log_message(_("Unable to match user %s on printer %s, applying default policy (%s)") % (username, printername, action))
260            return (action, None, None)
261        else :   
262            pagecounter = quota["pagecounter"]
263            softlimit = quota["softlimit"]
264            hardlimit = quota["hardlimit"]
265            datelimit = quota["datelimit"]
266            if datelimit is not None :
267                datelimit = DateTime.ISO.ParseDateTime(datelimit)
268            if softlimit is not None :
269                if pagecounter < softlimit :
270                    action = "ALLOW"
271                elif hardlimit is not None :
272                    if softlimit <= pagecounter < hardlimit :   
273                        now = DateTime.now()
274                        if datelimit is None :
275                            datelimit = now + self.config.getGraceDelay(printername)
276                            self.storage.setDateLimit(username, printername, datelimit)
277                        if now < datelimit :
278                            action = "WARN"
279                        else :   
280                            action = "DENY"
281                    else :         
282                        action = "DENY"
283                else :       
284                    action = "DENY"
285            else :       
286                action = "ALLOW"
287            return (action, (hardlimit - pagecounter), datelimit)
288   
289    def warnGroupPQuota(self, username, printername=None) :
290        """Checks a user quota and send him a message if quota is exceeded on current printer."""
291        pname = printername or self.printername
292        raise PyKotaToolError, _("Group quotas are currently not implemented.")
293       
294    def warnUserPQuota(self, username, printername=None) :
295        """Checks a user quota and send him a message if quota is exceeded on current printer."""
296        pname = printername or self.printername
297        admin = self.config.getAdmin(pname)
298        adminmail = self.config.getAdminMail(pname)
299        (action, grace, gracedate) = self.checkUserPQuota(username, pname)
300        if action == "DENY" :
301            if (grace is not None) and (gracedate is not None) :
302                # only when both user and printer are known
303                adminmessage = _("Print Quota exceeded for user %s on printer %s") % (username, pname)
304                self.logger.log_message(adminmessage)
305                self.sendMessageToUser(admin, adminmail, username, _("Print Quota Exceeded"), _("You are not allowed to print anymore because\nyour Print Quota is exceeded on printer %s.") % pname)
306                self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
307        elif action == "WARN" :   
308            adminmessage = _("Print Quota soft limit exceeded for user %s on printer %s") % (username, pname)
309            self.logger.log_message(adminmessage)
310            self.sendMessageToUser(admin, adminmail, username, _("Print Quota Exceeded"), _("You will soon be forbidden to print anymore because\nyour Print Quota is almost reached on printer %s.") % pname)
311            self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
312        return action       
313   
Note: See TracBrowser for help on using the browser.