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

Revision 1687, 21.7 kB (checked in by jalet, 20 years ago)

Introduction of the new 'onaccountererror' configuration directive.
Small fix for software accounter's return code which can't be None anymore.
Make software and hardware accounting code look similar : will be factorized
later.

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