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

Revision 1785, 22.4 kB (checked in by jalet, 20 years ago)

Minor changes to allow any PyKota administrator to launch enhanced versions
of the commands, and not only the root user.

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