root / pykota / trunk / bin / pkprinters @ 3276

Revision 3276, 18.2 kB (checked in by jerome, 16 years ago)

Doesn't drop and regain priviledges anymore : no added security since we could regain them (we needed to regain them for PAM and some end user scripts). This is also more consistent.
Removed SGTERM handling stuff in cupspykota : now only SIGINT can be used.
Now outputs an error message when printing (but doesn't fail) if CUPS is
not v1.3.4 or higher : we need 1.3.4 or higher because it fixes some
problematic charset handling bugs (by only accepting ascii and utf-8,
but this is a different story...)
Now ensures only the supported exit codes are returned by cupspykota :
we used to exit -1 in some cases (see man backend for details).

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1#! /usr/bin/env python
2# -*- coding: UTF-8 -*-
3#
4# PyKota : Print Quotas for CUPS
5#
6# (c) 2003, 2004, 2005, 2006, 2007, 2008 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 3 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, see <http://www.gnu.org/licenses/>.
19#
20# $Id$
21#
22#
23
24import os
25import sys
26import pwd
27
28from pykota.tool import Percent, PyKotaTool, PyKotaCommandLineError, crashed, N_
29from pykota.storage import StoragePrinter
30
31from pkipplib import pkipplib
32
33__doc__ = N_("""pkprinters v%(__version__)s (c) %(__years__)s %(__author__)s
34
35A Printers Manager for PyKota.
36
37command line usage :
38
39  pkprinters [options] printer1 printer2 printer3 ... printerN
40
41options :
42
43  -v | --version       Prints pkprinters's version number then exits.
44  -h | --help          Prints this message then exits.
45 
46  -a | --add           Adds printers if they don't exist on the Quota
47                       Storage Server. If they exist, they are modified
48                       unless -s|--skipexisting is also used.
49                       
50  -d | --delete        Deletes printers from the quota storage.
51 
52  -D | --description d Adds a textual description to printers.
53
54  -C | --cups          Also modifies the DeviceURI in CUPS' printers.conf
55
56  -c | --charge p[,j]  Sets the price per page and per job to charge.
57                       Job price is optional.
58                       If both are to be set, separate them with a comma.
59                       Floating point and negative values are allowed.
60 
61  -g | --groups pg1[,pg2...] Adds or Remove the printer(s) to the printer
62                       groups pg1, pg2, etc... which must already exist.
63                       A printer group is just like a normal printer,
64                       only that it is usually unknown from the printing
65                       system. Create printer groups exactly the same
66                       way that you create printers, then add other
67                       printers to them with this option.
68                       Accounting is done on a printer and on all
69                       the printer groups it belongs to, quota checking
70                       is done on a printer and on all the printer groups
71                       it belongs to.
72                       If the --remove option below is not used, the
73                       default action is to add printers to the specified
74                       printer groups.
75                       
76  -l | --list          List informations about the printer(s) and the
77                       printers groups it is a member of.
78                       
79  -r | --remove        In combination with the --groups option above,                       
80                       remove printers from the specified printers groups.
81                       
82  -s | --skipexisting  In combination with the --add option above, tells
83                       pkprinters to not modify existing printers.
84                       
85  -m | --maxjobsize s  Sets the maximum job size allowed on the printer
86                       to s pages.
87                       
88  -p | --passthrough   Activate passthrough mode for the printer. In this
89                       mode, users are allowed to print without any impact
90                       on their quota or account balance.
91                       
92  -n | --nopassthrough Deactivate passthrough mode for the printer.
93                       Without -p or -n, printers are created in
94                       normal mode, i.e. no passthrough.
95 
96  printer1 through printerN can contain wildcards if the --add option
97  is not set.
98 
99examples :                             
100
101  $ pkprinters --add -D "HP Printer" --charge 0.05,0.1 hp2100 hp2200 hp8000
102 
103  Will create three printers named hp2100, hp2200 and hp8000.
104  Their price per page will be set at 0.05 unit, and their price
105  per job will be set at 0.1 unit. Units are in your own currency,
106  or whatever you want them to mean.
107  All of their descriptions will be set to the string "HP Printer".
108  If any of these printers already exists, it will also be modified
109  unless the -s|--skipexisting command line option is also used.
110           
111  $ pkprinters --delete "*"
112 
113  This will completely delete all printers and associated quota information,
114  as well as their job history. USE WITH CARE !
115 
116  $ pkprinters --groups Laser,HP "hp*"
117 
118  This will put all printers which name matches "hp*" into printers groups
119  Laser and HP, which MUST already exist.
120 
121  $ pkprinters --groups LexMark --remove hp2200
122 
123  This will remove the hp2200 printer from the LexMark printer group.
124""")
125       
126class PKPrinters(PyKotaTool) :       
127    """A class for a printers manager."""
128    def modifyPrinter(self, printer, charges, perpage, perjob, description, passthrough, nopassthrough, maxjobsize) :
129        if charges :
130            printer.setPrices(perpage, perjob)   
131        if description is not None :        # NB : "" is allowed !
132            printer.setDescription(description)
133        if nopassthrough :   
134            printer.setPassThrough(False)
135        if passthrough :   
136            printer.setPassThrough(True)
137        if maxjobsize is not None :
138            printer.setMaxJobSize(maxjobsize)
139           
140    def managePrintersGroups(self, pgroups, printer, remove) :       
141        """Manage printer group membership."""
142        for pgroup in pgroups :
143            if remove :
144                pgroup.delPrinterFromGroup(printer)
145            else :
146                pgroup.addPrinterToGroup(printer)   
147               
148    def getPrinterDeviceURI(self, printername) :           
149        """Returns the Device URI attribute for a particular printer."""
150        if not printername :
151            return ""
152        cups = pkipplib.CUPS()
153        req = cups.newRequest(pkipplib.IPP_GET_PRINTER_ATTRIBUTES)
154        req.operation["printer-uri"] = ("uri", cups.identifierToURI("printers", printername))
155        try :
156            return cups.doRequest(req).printer["device-uri"][0][1]
157        except :   
158            self.printInfo(_("Impossible to retrieve %(printername)s's DeviceURI") % locals(), "warn")
159            return ""
160       
161    def isPrinterCaptured(self, printername=None, deviceuri=None) :
162        """Returns True if the printer is already redirected through PyKota's backend, else False."""
163        if (deviceuri or self.getPrinterDeviceURI(printername)).find("cupspykota:") != -1 :
164            return True
165        else :   
166            return False
167       
168    def reroutePrinterThroughPyKota(self, printer) :   
169        """Reroutes a CUPS printer through PyKota."""
170        uri = self.getPrinterDeviceURI(printer.Name)
171        if uri and (not self.isPrinterCaptured(deviceuri=uri)) :
172             newuri = "cupspykota://%s" % uri
173             os.system('lpadmin -p "%s" -v "%s"' % (printer.Name, newuri))
174             self.logdebug("Printer %s rerouted to %s" % (printer.Name, newuri))
175             
176    def deroutePrinterFromPyKota(self, printer) :   
177        """Deroutes a PyKota printer through CUPS only."""
178        uri = self.getPrinterDeviceURI(printer.Name)
179        if uri and self.isPrinterCaptured(deviceuri=uri) :
180             newuri = uri.replace("cupspykota:", "")
181             if newuri.startswith("//") :
182                 newuri = newuri[2:]
183             os.system('lpadmin -p "%s" -v "%s"' % (printer.Name, newuri))
184             self.logdebug("Printer %s rerouted to %s" % (printer.Name, newuri))
185                                     
186    def main(self, names, options) :
187        """Manage printers."""
188        if (not self.config.isAdmin) and (not options["list"]) :
189            raise PyKotaCommandLineError, "%s : %s" % (pwd.getpwuid(os.geteuid())[0], _("You're not allowed to use this command."))
190           
191        docups = options["cups"]
192       
193        if not options["list"] :   
194            percent = Percent(self)
195           
196        if not options["add"] :
197            if not options["list"] :
198                percent.display("%s..." % _("Extracting datas"))
199            if not names :      # NB : can't happen for --delete because it's catched earlier
200                names = ["*"]
201            printers = self.storage.getMatchingPrinters(",".join(names))
202            if not printers :
203                if not options["list"] :
204                    percent.display("\n")
205                raise PyKotaCommandLineError, _("There's no printer matching %s") % " ".join(names)
206            if not options["list"] :   
207                percent.setSize(len(printers))
208               
209        if options["list"] :
210            for printer in printers :
211                parents = ", ".join([p.Name for p in self.storage.getParentPrinters(printer)])
212                print "%s [%s] (%s + #*%s)" % \
213                      (printer.Name, printer.Description, printer.PricePerJob, \
214                       printer.PricePerPage)
215                print "    %s" % (_("Passthrough mode : %s") % ((printer.PassThrough and _("ON")) or _("OFF")))
216                print "    %s" % (_("Maximum job size : %s") % ((printer.MaxJobSize and (_("%s pages") % printer.MaxJobSize)) or _("Unlimited")))
217                print "    %s" % (_("Routed through PyKota : %s") % ((self.isPrinterCaptured(printer.Name) and _("YES")) or _("NO")))
218                if parents : 
219                    print "    %s %s" % (_("in"), parents)
220                print   
221        elif options["delete"] :   
222            percent.display("\n%s..." % _("Deletion"))
223            self.storage.deleteManyPrinters(printers)
224            percent.display("\n")
225            if docups :
226                percent.display("%s...\n" % _("Rerouting printers to CUPS"))
227                for printer in printers :
228                    self.deroutePrinterFromPyKota(printer)
229                    percent.oneMore()
230        else :
231            if options["groups"] :       
232                printersgroups = self.storage.getMatchingPrinters(options["groups"])
233                if not printersgroups :
234                    raise PyKotaCommandLineError, _("There's no printer matching %s") % " ".join(options["groups"].split(','))
235            else :         
236                printersgroups = []
237                   
238            if options["charge"] :
239                try :
240                    charges = [float(part) for part in options["charge"].split(',', 1)]
241                except ValueError :   
242                    raise PyKotaCommandLineError, _("Invalid charge amount value %s") % options["charge"]
243                else :   
244                    if len(charges) > 2 :
245                        charges = charges[:2]
246                    if len(charges) != 2 :
247                        charges = [charges[0], None]
248                    (perpage, perjob) = charges
249            else :       
250                charges = perpage = perjob = None
251                   
252            if options["maxjobsize"] :       
253                try :
254                    maxjobsize = int(options["maxjobsize"])
255                    if maxjobsize < 0 :
256                        raise ValueError
257                except ValueError :   
258                    raise PyKotaCommandLineError, _("Invalid maximum job size value %s") % options["maxjobsize"]
259            else :       
260                maxjobsize = None
261                   
262            description = options["description"]
263            if description :
264                description = description.strip()
265               
266            nopassthrough = options["nopassthrough"]   
267            passthrough = options["passthrough"]
268            remove = options["remove"]
269            skipexisting = options["skipexisting"]
270            self.storage.beginTransaction()
271            try :
272                if options["add"] :   
273                    percent.display("%s...\n" % _("Creation"))
274                    percent.setSize(len(names))
275                    for pname in names :
276                        if self.isValidName(pname) :
277                            printer = StoragePrinter(self.storage, pname)
278                            self.modifyPrinter(printer, charges, perpage, perjob, \
279                                           description, passthrough, \
280                                           nopassthrough, maxjobsize)
281                            oldprinter = self.storage.addPrinter(printer)               
282                           
283                            if docups :
284                                 self.reroutePrinterThroughPyKota(printer)
285                                     
286                            if oldprinter is not None :
287                                if skipexisting :
288                                    self.logdebug(_("Printer %s already exists, skipping.") % pname)
289                                else :   
290                                    self.logdebug(_("Printer %s already exists, will be modified.") % pname)
291                                    self.modifyPrinter(oldprinter, charges, \
292                                               perpage, perjob, description, \
293                                               passthrough, nopassthrough, \
294                                               maxjobsize)
295                                    oldprinter.save()           
296                                    self.managePrintersGroups(printersgroups, oldprinter, remove)
297                            elif printersgroups :       
298                                self.managePrintersGroups(printersgroups, \
299                                                          self.storage.getPrinter(pname), \
300                                                          remove)
301                        else :   
302                            raise PyKotaCommandLineError, _("Invalid printer name %s") % pname
303                        percent.oneMore()
304                else :       
305                    percent.display("\n%s...\n" % _("Modification"))
306                    for printer in printers :       
307                        self.modifyPrinter(printer, charges, perpage, perjob, \
308                                           description, passthrough, \
309                                           nopassthrough, maxjobsize)
310                        printer.save()   
311                        self.managePrintersGroups(printersgroups, printer, remove)
312                        if docups :
313                            self.reroutePrinterThroughPyKota(printer)
314                        percent.oneMore()
315            except :                   
316                self.storage.rollbackTransaction()
317                raise
318            else :   
319                self.storage.commitTransaction()
320               
321        if not options["list"] :
322            percent.done()
323                     
324if __name__ == "__main__" : 
325    retcode = 0
326    try :
327        short_options = "hvaCc:D:dg:lrsnpm:"
328        long_options = ["help", "version", "add", "cups", "charge=", "description=", \
329                        "delete", "groups=", "list", "remove", \
330                        "skipexisting", "passthrough", "nopassthrough", \
331                        "maxjobsize="]
332       
333        # Initializes the command line tool
334        manager = PKPrinters(doc=__doc__)
335        manager.deferredInit()
336       
337        # parse and checks the command line
338        (options, args) = manager.parseCommandline(sys.argv[1:], short_options, long_options)
339       
340        # sets long options
341        options["help"] = options["h"] or options["help"]
342        options["version"] = options["v"] or options["version"]
343        options["add"] = options["a"] or options["add"]
344        options["cups"] = options["C"] or options["cups"]
345        options["charge"] = options["c"] or options["charge"]
346        options["description"] = options["D"] or options["description"]
347        options["delete"] = options["d"] or options["delete"] 
348        options["groups"] = options["g"] or options["groups"]
349        options["list"] = options["l"] or options["list"]
350        options["remove"] = options["r"] or options["remove"]
351        options["skipexisting"] = options["s"] or options["skipexisting"]
352        options["maxjobsize"] = options["m"] or options["maxjobsize"]
353        options["passthrough"] = options["p"] or options["passthrough"]
354        options["nopassthrough"] = options["n"] or options["nopassthrough"]
355       
356        if options["help"] :
357            manager.display_usage_and_quit()
358        elif options["version"] :
359            manager.display_version_and_quit()
360        elif (options["delete"] and (options["add"] or options["groups"] or options["charge"] or options["remove"] or options["description"])) \
361           or (options["skipexisting"] and not options["add"]) \
362           or (options["list"] and (options["add"] or options["delete"] or options["groups"] or options["charge"] or options["remove"] or options["description"])) \
363           or (options["passthrough"] and options["nopassthrough"]) \
364           or (options["remove"] and options["add"]) :
365            raise PyKotaCommandLineError, _("incompatible options, see help.")
366        elif options["remove"] and not options["groups"] :
367            raise PyKotaCommandLineError, _("You have to pass printer groups names on the command line")
368        elif (not args) and (options["add"] or options["delete"]) :
369            raise PyKotaCommandLineError, _("You have to pass printer names on the command line")
370        else :
371            retcode = manager.main(args, options)
372    except KeyboardInterrupt :       
373        sys.stderr.write("\nInterrupted with Ctrl+C !\n")
374        retcode = -3
375    except PyKotaCommandLineError, msg :   
376        sys.stderr.write("%s : %s\n" % (sys.argv[0], msg))
377        retcode = -2
378    except SystemExit :       
379        pass
380    except :
381        try :
382            manager.crashed("pkprinters failed")
383        except :   
384            crashed("pkprinters failed")
385        retcode = -1
386
387    try :
388        manager.storage.close()
389    except (TypeError, NameError, AttributeError) :   
390        pass
391       
392    sys.exit(retcode)   
Note: See TracBrowser for help on using the browser.