root / pykota / trunk / pykota / config.py @ 1494

Revision 1483, 19.6 kB (checked in by jalet, 20 years ago)

Big code changes to completely remove the need for "requester" directives,
jsut use "hardware(... your previous requester directive's content ...)"

  • 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 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
20#
21# $Id$
22#
23# $Log$
24# Revision 1.47  2004/05/18 14:49:20  jalet
25# Big code changes to completely remove the need for "requester" directives,
26# jsut use "hardware(... your previous requester directive's content ...)"
27#
28# Revision 1.46  2004/05/13 13:59:28  jalet
29# Code simplifications
30#
31# Revision 1.45  2004/03/01 10:22:30  jalet
32# Can now extract per printer pre and post hooks from the configuration file
33#
34# Revision 1.44  2004/02/20 14:42:21  jalet
35# Experimental ldapcache directive added
36#
37# Revision 1.43  2004/02/19 14:20:21  jalet
38# maildomain pykota.conf directive added.
39# Small improvements on mail headers quality.
40#
41# Revision 1.42  2004/01/08 14:10:32  jalet
42# Copyright year changed.
43#
44# Revision 1.41  2003/11/29 20:06:20  jalet
45# Added 'utolower' configuration option to convert all usernames to
46# lowercase when printing. All database accesses are still and will
47# remain case sensitive though.
48#
49# Revision 1.40  2003/11/18 23:43:12  jalet
50# Mailto can be any external command now, as usual.
51#
52# Revision 1.39  2003/10/08 21:41:38  jalet
53# External policies for printers works !
54# We can now auto-add users on first print, and do other useful things if needed.
55#
56# Revision 1.38  2003/10/07 22:06:05  jalet
57# Preliminary code to disable job history
58#
59# Revision 1.37  2003/10/07 09:07:28  jalet
60# Character encoding added to please latest version of Python
61#
62# Revision 1.36  2003/10/02 20:23:18  jalet
63# Storage caching mechanism added.
64#
65# Revision 1.35  2003/07/29 09:54:03  jalet
66# Added configurable LDAP mail attribute support
67#
68# Revision 1.34  2003/07/28 09:11:12  jalet
69# PyKota now tries to add its attributes intelligently in existing LDAP
70# directories.
71#
72# Revision 1.33  2003/07/16 21:53:07  jalet
73# Really big modifications wrt new configuration file's location and content.
74#
75# Revision 1.32  2003/07/08 19:43:51  jalet
76# Configurable warning messages.
77# Poor man's treshold value added.
78#
79# Revision 1.31  2003/07/07 11:49:24  jalet
80# Lots of small fixes with the help of PyChecker
81#
82# Revision 1.30  2003/06/25 14:10:01  jalet
83# Hey, it may work (edpykota --reset excepted) !
84#
85# Revision 1.29  2003/06/14 22:44:21  jalet
86# More work on LDAP storage backend.
87#
88# Revision 1.28  2003/06/10 16:37:54  jalet
89# Deletion of the second user which is not needed anymore.
90# Added a debug configuration field in /etc/pykota.conf
91# All queries can now be sent to the logger in debug mode, this will
92# greatly help improve performance when time for this will come.
93#
94# Revision 1.27  2003/05/27 23:00:21  jalet
95# Big rewrite of external accounting methods.
96# Should work well now.
97#
98# Revision 1.26  2003/04/30 19:53:58  jalet
99# 1.05
100#
101# Revision 1.25  2003/04/30 13:36:40  jalet
102# Stupid accounting method was added.
103#
104# Revision 1.24  2003/04/29 18:37:54  jalet
105# Pluggable accounting methods (actually doesn't support external scripts)
106#
107# Revision 1.23  2003/04/24 11:53:48  jalet
108# Default policy for unknown users/groups is to DENY printing instead
109# of the previous default to ALLOW printing. This is to solve an accuracy
110# problem. If you set the policy to ALLOW, jobs printed by in nexistant user
111# (from PyKota's POV) will be charged to the next user who prints on the
112# same printer.
113#
114# Revision 1.22  2003/04/23 22:13:57  jalet
115# Preliminary support for LPRng added BUT STILL UNTESTED.
116#
117# Revision 1.21  2003/03/29 13:45:27  jalet
118# GPL paragraphs were incorrectly (from memory) copied into the sources.
119# Two README files were added.
120# Upgrade script for PostgreSQL pre 1.01 schema was added.
121#
122# Revision 1.20  2003/03/29 13:08:28  jalet
123# Configuration is now expected to be found in /etc/pykota.conf instead of
124# in /etc/cups/pykota.conf
125# Installation script can move old config files to the new location if needed.
126# Better error handling if configuration file is absent.
127#
128# Revision 1.19  2003/03/16 09:56:52  jalet
129# Mailto option now accepts some additional values which all mean that
130# nobody will receive any email message.
131# Mailto option now works. Version 1.01 is now officially out.
132#
133# Revision 1.18  2003/03/16 08:00:50  jalet
134# Default hard coded options are now used if they are not set in the
135# configuration file.
136#
137# Revision 1.17  2003/03/15 23:01:28  jalet
138# New mailto option in configuration file added.
139# No time to test this tonight (although it should work).
140#
141# Revision 1.16  2003/02/17 23:01:56  jalet
142# Typos
143#
144# Revision 1.15  2003/02/17 22:55:01  jalet
145# More options can now be set per printer or globally :
146#
147#       admin
148#       adminmail
149#       gracedelay
150#       requester
151#
152# the printer option has priority when both are defined.
153#
154# Revision 1.14  2003/02/17 22:05:50  jalet
155# Storage backend now supports admin and user passwords (untested)
156#
157# Revision 1.13  2003/02/10 11:47:39  jalet
158# Moved some code down into the requesters
159#
160# Revision 1.12  2003/02/10 10:36:33  jalet
161# Small problem wrt external requester
162#
163# Revision 1.11  2003/02/10 08:50:45  jalet
164# External requester seems to be finally ok now
165#
166# Revision 1.10  2003/02/10 08:19:57  jalet
167# tell ConfigParser to return raw data, this allows our own strings
168# interpolations in the requester
169#
170# Revision 1.9  2003/02/10 00:44:38  jalet
171# Typos
172#
173# Revision 1.8  2003/02/10 00:42:17  jalet
174# External requester should be ok (untested)
175# New syntax for configuration file wrt requesters
176#
177# Revision 1.7  2003/02/09 13:05:43  jalet
178# Internationalization continues...
179#
180# Revision 1.6  2003/02/07 22:00:09  jalet
181# Bad cut&paste
182#
183# Revision 1.5  2003/02/06 23:58:05  jalet
184# repykota should be ok
185#
186# Revision 1.4  2003/02/06 09:19:02  jalet
187# More robust behavior (hopefully) when the user or printer is not managed
188# correctly by the Quota System : e.g. cupsFilter added in ppd file, but
189# printer and/or user not 'yet?' in storage.
190#
191# Revision 1.3  2003/02/05 23:26:22  jalet
192# Incorrect handling of grace delay
193#
194# Revision 1.2  2003/02/05 23:09:20  jalet
195# Name conflict
196#
197# Revision 1.1  2003/02/05 21:28:17  jalet
198# Initial import into CVS
199#
200#
201#
202
203import os
204import ConfigParser
205
206class PyKotaConfigError(Exception):
207    """An exception for PyKota config related stuff."""
208    def __init__(self, message = ""):
209        self.message = message
210        Exception.__init__(self, message)
211    def __repr__(self):
212        return self.message
213    __str__ = __repr__
214   
215class PyKotaConfig :
216    """A class to deal with PyKota's configuration."""
217    def __init__(self, directory) :
218        """Reads and checks the configuration file."""
219        self.filename = os.path.join(directory, "pykota.conf")
220        if not os.path.isfile(self.filename) :
221            raise PyKotaConfigError, _("Configuration file %s not found.") % self.filename
222        self.config = ConfigParser.ConfigParser()
223        self.config.read([self.filename])
224           
225    def isTrue(self, option) :       
226        """Returns 1 if option is set to true, else 0."""
227        if (option is not None) and (option.upper().strip() in ['Y', 'YES', '1', 'ON', 'T', 'TRUE']) :
228            return 1
229        else :   
230            return 0
231                       
232    def getPrinterNames(self) :   
233        """Returns the list of configured printers, i.e. all sections names minus 'global'."""
234        return [pname for pname in self.config.sections() if pname != "global"]
235       
236    def getGlobalOption(self, option, ignore=0) :   
237        """Returns an option from the global section, or raises a PyKotaConfigError if ignore is not set, else returns None."""
238        try :
239            return self.config.get("global", option, raw=1)
240        except (ConfigParser.NoSectionError, ConfigParser.NoOptionError) :   
241            if ignore :
242                return
243            else :
244                raise PyKotaConfigError, _("Option %s not found in section global of %s") % (option, self.filename)
245               
246    def getPrinterOption(self, printername, option) :   
247        """Returns an option from the printer section, or the global section, or raises a PyKotaConfigError."""
248        globaloption = self.getGlobalOption(option, ignore=1)
249        try :
250            return self.config.get(printername, option, raw=1)
251        except (ConfigParser.NoSectionError, ConfigParser.NoOptionError) :   
252            if globaloption is not None :
253                return globaloption
254            else :
255                raise PyKotaConfigError, _("Option %s not found in section %s of %s") % (option, printername, self.filename)
256       
257    def getStorageBackend(self) :   
258        """Returns the storage backend information as a Python mapping."""       
259        backendinfo = {}
260        for option in [ "storagebackend", "storageserver", \
261                        "storagename", "storageuser", \
262                      ] :
263            backendinfo[option] = self.getGlobalOption(option)
264        backendinfo["storageuserpw"] = self.getGlobalOption("storageuserpw", ignore=1)  # password is optional
265        backendinfo["storageadmin"] = None
266        backendinfo["storageadminpw"] = None
267        adminconf = ConfigParser.ConfigParser()
268        adminconf.read(["/etc/pykota/pykotadmin.conf"])
269        if adminconf.sections() : # were we able to read the file ?
270            try :
271                backendinfo["storageadmin"] = adminconf.get("global", "storageadmin", raw=1)
272            except (ConfigParser.NoSectionError, ConfigParser.NoOptionError) :   
273                raise PyKotaConfigError, _("Option %s not found in section global of %s") % ("storageadmin", "/etc/pykota/pykotadmin.conf")
274            try :
275                backendinfo["storageadminpw"] = adminconf.get("global", "storageadminpw", raw=1)
276            except (ConfigParser.NoSectionError, ConfigParser.NoOptionError) :   
277                pass # Password is optional
278        return backendinfo
279       
280    def getLDAPInfo(self) :   
281        """Returns some hints for the LDAP backend."""       
282        ldapinfo = {}
283        for option in [ "userbase", "userrdn", \
284                        "balancebase", "balancerdn", \
285                        "groupbase", "grouprdn", "groupmembers", \
286                        "printerbase", "printerrdn", \
287                        "userquotabase", "groupquotabase", \
288                        "jobbase", "lastjobbase", \
289                        "newuser", "newgroup", \
290                        "usermail", \
291                      ] :
292            ldapinfo[option] = self.getGlobalOption(option).strip()
293        for field in ["newuser", "newgroup"] :
294            if ldapinfo[field].lower().startswith('attach(') :
295                ldapinfo[field] = ldapinfo[field][7:-1]
296        return ldapinfo
297       
298    def getLoggingBackend(self) :   
299        """Returns the logging backend information."""
300        validloggers = [ "stderr", "system" ] 
301        try :
302            logger = self.getGlobalOption("logger").lower()
303        except PyKotaConfigError :   
304            logger = "system"
305        if logger not in validloggers :             
306            raise PyKotaConfigError, _("Option logger only supports values in %s") % str(validloggers)
307        return logger   
308       
309    def getAccounterBackend(self, printername) :   
310        """Returns the accounter backend to use for a given printer.
311       
312           if it is not set, it defaults to 'hardware' which means ask printer
313           for its internal lifetime page counter.
314        """   
315        validaccounters = [ "hardware", "software" ]     
316        fullaccounter = self.getPrinterOption(printername, "accounter").strip()
317        flower = fullaccounter.lower()
318        if flower.startswith("software") or flower.startswith("hardware") :   
319            try :
320                (accounter, args) = [x.strip() for x in fullaccounter.split('(', 1)]
321            except ValueError :   
322                raise PyKotaConfigError, _("Invalid accounter %s for printer %s") % (fullaccounter, printername)
323            if args.endswith(')') :
324                args = args[:-1]
325            if not args :
326                raise PyKotaConfigError, _("Invalid accounter %s for printer %s") % (fullaccounter, printername)
327            return (accounter.lower(), args)   
328        else :
329            raise PyKotaConfigError, _("Option accounter in section %s only supports values in %s") % (printername, str(validaccounters))
330       
331    def getPreHook(self, printername) :   
332        """Returns the prehook command line to launch, or None if unset."""
333        try :
334            return self.getPrinterOption(printername, "prehook").strip()
335        except PyKotaConfigError :   
336            return      # No command to launch in the pre-hook
337           
338    def getPostHook(self, printername) :   
339        """Returns the posthook command line to launch, or None if unset."""
340        try :
341            return self.getPrinterOption(printername, "posthook").strip()
342        except PyKotaConfigError :   
343            return      # No command to launch in the post-hook
344           
345    def getPrinterPolicy(self, printername) :   
346        """Returns the default policy for the current printer."""
347        validpolicies = [ "ALLOW", "DENY", "EXTERNAL" ]     
348        try :
349            fullpolicy = self.getPrinterOption(printername, "policy")
350        except PyKotaConfigError :   
351            return ("DENY", None)
352        else :   
353            try :
354                policy = [x.strip() for x in fullpolicy.split('(', 1)]
355            except ValueError :   
356                raise PyKotaConfigError, _("Invalid policy %s for printer %s") % (fullpolicy, printername)
357            if len(policy) == 1 :   
358                policy.append("")
359            (policy, args) = policy   
360            if args.endswith(')') :
361                args = args[:-1]
362            policy = policy.upper()   
363            if (policy == "EXTERNAL") and not args :
364                raise PyKotaConfigError, _("Invalid policy %s for printer %s") % (fullpolicy, printername)
365            if policy not in validpolicies :
366                raise PyKotaConfigError, _("Option policy in section %s only supports values in %s") % (printername, str(validpolicies))
367            return (policy, args)
368       
369    def getSMTPServer(self) :   
370        """Returns the SMTP server to use to send messages to users."""
371        try :
372            return self.getGlobalOption("smtpserver")
373        except PyKotaConfigError :   
374            return "localhost"
375       
376    def getMailDomain(self) :   
377        """Returns the mail domain to use to send messages to users."""
378        try :
379            return self.getGlobalOption("maildomain")
380        except PyKotaConfigError :   
381            return 
382       
383    def getAdminMail(self, printername) :   
384        """Returns the Email address of the Print Quota Administrator."""
385        try :
386            return self.getPrinterOption(printername, "adminmail")
387        except PyKotaConfigError :   
388            return "root@localhost"
389       
390    def getAdmin(self, printername) :   
391        """Returns the full name of the Print Quota Administrator."""
392        try :
393            return self.getPrinterOption(printername, "admin")
394        except PyKotaConfigError :   
395            return "root"
396       
397    def getMailTo(self, printername) :   
398        """Returns the recipient of email messages."""
399        validmailtos = [ "EXTERNAL", "NOBODY", "NONE", "NOONE", "BITBUCKET", "DEVNULL", "BOTH", "USER", "ADMIN" ]
400        try :
401            fullmailto = self.getPrinterOption(printername, "mailto")
402        except PyKotaConfigError :   
403            return ("BOTH", None)
404        else :   
405            try :
406                mailto = [x.strip() for x in fullmailto.split('(', 1)]
407            except ValueError :   
408                raise PyKotaConfigError, _("Invalid option mailto %s for printer %s") % (fullmailto, printername)
409            if len(mailto) == 1 :   
410                mailto.append("")
411            (mailto, args) = mailto   
412            if args.endswith(')') :
413                args = args[:-1]
414            mailto = mailto.upper()   
415            if (mailto == "EXTERNAL") and not args :
416                raise PyKotaConfigError, _("Invalid option mailto %s for printer %s") % (fullmailto, printername)
417            if mailto not in validmailtos :
418                raise PyKotaConfigError, _("Option mailto in section %s only supports values in %s") % (printername, str(validmailtos))
419            return (mailto, args)
420       
421    def getGraceDelay(self, printername) :   
422        """Returns the grace delay in days."""
423        try :
424            gd = self.getPrinterOption(printername, "gracedelay")
425        except PyKotaConfigError :   
426            gd = 7
427        try :
428            return int(gd)
429        except (TypeError, ValueError) :   
430            raise PyKotaConfigError, _("Invalid grace delay %s") % gd
431           
432    def getPoorMan(self) :   
433        """Returns the poor man's treshold."""
434        try :
435            pm = self.getGlobalOption("poorman")
436        except PyKotaConfigError :   
437            pm = 1.0
438        try :
439            return float(pm)
440        except (TypeError, ValueError) :   
441            raise PyKotaConfigError, _("Invalid poor man's treshold %s") % pm
442           
443    def getPoorWarn(self) :   
444        """Returns the poor man's warning message."""
445        try :
446            return self.getGlobalOption("poorwarn")
447        except PyKotaConfigError :   
448            return _("Your Print Quota account balance is Low.\nSoon you'll not be allowed to print anymore.\nPlease contact the Print Quota Administrator to solve the problem.")
449           
450    def getHardWarn(self, printername) :   
451        """Returns the hard limit error message."""
452        try :
453            return self.getPrinterOption(printername, "hardwarn")
454        except PyKotaConfigError :   
455            return _("You are not allowed to print anymore because\nyour Print Quota is exceeded on printer %s.") % printername
456           
457    def getSoftWarn(self, printername) :   
458        """Returns the soft limit error message."""
459        try :
460            return self.getPrinterOption(printername, "softwarn")
461        except PyKotaConfigError :   
462            return _("You will soon be forbidden to print anymore because\nyour Print Quota is almost reached on printer %s.") % printername
463           
464    def getDebug(self) :         
465        """Returns 1 if debugging is activated, else 0."""
466        return self.isTrue(self.getGlobalOption("debug", ignore=1))
467           
468    def getCaching(self) :         
469        """Returns 1 if database caching is enabled, else 0."""
470        return self.isTrue(self.getGlobalOption("storagecaching", ignore=1))
471           
472    def getLDAPCache(self) :         
473        """Returns 1 if low-level LDAP caching is enabled, else 0."""
474        return self.isTrue(self.getGlobalOption("ldapcache", ignore=1))
475           
476    def getDisableHistory(self) :         
477        """Returns 1 if we want to disable history, else 0."""
478        return self.isTrue(self.getGlobalOption("disablehistory", ignore=1))
479           
480    def getUserNameToLower(self) :         
481        """Returns 1 if we want to convert usernames to lowercase when printing, else 0."""
482        return self.isTrue(self.getGlobalOption("utolower", ignore=1))
Note: See TracBrowser for help on using the browser.