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

Revision 844, 12.4 kB (checked in by jalet, 21 years ago)

Comment

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