root / pykota / trunk / bin / pkturnkey @ 2467

Revision 2467, 18.5 kB (checked in by jerome, 19 years ago)

Introduces the -c | --doconf command line option for pkturnkey,
which replaces what pkhint did before.

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1#! /usr/bin/env python
2# -*- coding: ISO-8859-15 -*-
3
4# PyKota Turn Key tool
5#
6# PyKota - Print Quotas for CUPS and LPRng
7#
8# (c) 2003, 2004, 2005 Jerome Alet <alet@librelogiciel.com>
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation; either version 2 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program; if not, write to the Free Software
21# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22#
23# $Id$
24#
25#
26
27import sys
28import os
29import pwd
30import grp
31import socket
32
33from pykota.tool import Tool, PyKotaToolError, crashed, N_
34
35__doc__ = N_("""pkturnkey v%(__version__)s (c) %(__years__)s %(__author__)s
36
37A turn key tool for PyKota. When launched, this command will initialize
38PyKota's database with all existing print queues and some or all users.
39For now, no prices or limits are set, so printing is fully accounted
40for, but not limited.
41
42command line usage :
43
44  pkturnkey [options] [printqueues names]
45
46options :
47
48  -v | --version       Prints pkturnkey version number then exits.
49  -h | --help          Prints this message then exits.
50 
51  -c | --doconf        Give hints about what to put into pykota.conf
52 
53  -d | --dousers       Manages users accounts as well.
54 
55  -D | --dogroups      Manages users groups as well.
56                       Implies -d | --dousers.
57 
58  -e | --emptygroups   Includes empty groups.
59 
60  -f | --force         Modifies the database instead of printing what
61                       it would do.
62                       
63  -u | --uidmin uid    Only adds users whose uid is greater than or equal to
64                       uid. You can pass an username there as well, and its
65                       uid will be used automatically.
66                       If not set, 0 will be used automatically.
67                       Implies -d | --dousers.
68                       
69  -U | --uidmax uid    Only adds users whose uid is lesser than or equal to
70                       uid. You can pass an username there as well, and its
71                       uid will be used automatically.
72                       If not set, a large value will be used automatically.
73                       Implies -d | --dousers.
74
75  -g | --gidmin gid    Only adds groups whose gid is greater than or equal to
76                       gid. You can pass a groupname there as well, and its
77                       gid will be used automatically.
78                       If not set, 0 will be used automatically.
79                       Implies -D | --dogroups.
80                       
81  -G | --gidmax gid    Only adds groups whose gid is lesser than or equal to
82                       gid. You can pass a groupname there as well, and its
83                       gid will be used automatically.
84                       If not set, a large value will be used automatically.
85                       Implies -D | --dogroups.
86
87examples :                             
88
89  $ pkturnkey --dousers --uidmin jerome
90
91  Will simulate the initialization of PyKota's database will all existing
92  printers and print accounts for all users whose uid is greater than
93  or equal to jerome's one. Won't manage any users group.
94 
95  To REALLY initialize the database instead of simulating it, please
96  use the -f | --force command line switch.
97 
98  You can limit the initialization to only a subset of the existing
99  printers, by passing their names at the end of the command line.
100""")
101       
102class PKTurnKey(Tool) :
103    """A class for an initialization tool."""
104    def listPrinters(self, namestomatch) :
105        """Returns a list of tuples (queuename, deviceuri) for all existing print queues."""
106        self.printInfo("Extracting all print queues.")
107        result = os.popen("lpstat -v", "r")
108        lines = result.readlines()
109        result.close()
110        printers = []
111        for line in lines :
112            (begin, end) = line.split(':', 1)
113            deviceuri = end.strip()
114            queuename = begin.split()[-1]
115            if self.matchString(queuename, namestomatch) :
116                printers.append((queuename, deviceuri))
117            else :   
118                self.printInfo("Print queue %s skipped." % queuename)
119        return printers   
120       
121    def listUsers(self, uidmin, uidmax) :   
122        """Returns a list of users whose uids are between uidmin and uidmax."""
123        self.printInfo("Extracting all users whose uid is between %s and %s." % (uidmin, uidmax))
124        return [(entry[0], entry[3]) for entry in pwd.getpwall() if uidmin <= entry[2] <= uidmax]
125       
126    def listGroups(self, gidmin, gidmax, users) :
127        """Returns a list of groups whose gids are between gidmin and gidmax."""
128        self.printInfo("Extracting all groups whose gid is between %s and %s." % (gidmin, gidmax))
129        groups = [(entry[0], entry[2], entry[3]) for entry in grp.getgrall() if gidmin <= entry[2] <= gidmax]
130        gidusers = {}
131        usersgid = {}
132        for u in users :
133            gidusers.setdefault(u[1], []).append(u[0])
134            usersgid.setdefault(u[0], []).append(u[1]) 
135           
136        membership = {}   
137        for g in range(len(groups)) :
138            (gname, gid, members) = groups[g]
139            newmembers = {}
140            for m in members :
141                newmembers[m] = m
142            try :
143                usernames = gidusers[gid]
144            except KeyError :   
145                pass
146            else :   
147                for username in usernames :
148                    if not newmembers.has_key(username) :
149                        newmembers[username] = username
150            for member in newmembers.keys() :           
151                if not usersgid.has_key(member) :
152                    del newmembers[member]
153            membership[gname] = newmembers.keys()
154        return membership
155       
156    def runCommand(self, command, dryrun) :   
157        """Launches an external command."""
158        self.printInfo("%s" % command)
159        if not dryrun :   
160            os.system(command)
161           
162    def createPrinters(self, printers, dryrun=0) :   
163        """Creates all printers in PyKota's database."""
164        if printers :
165            needswarning = [p[0] for p in printers if p[1].find("cupspykota") == -1]
166            command = "pkprinters --add %s" % " ".join(['"%s"' % p[0] for p in printers])
167            self.runCommand(command, dryrun)
168            for p in needswarning :
169                self.printInfo(_("Printer %s is not managed by PyKota yet. Please modify printers.conf and restart CUPS.") % p, "warn")
170       
171    def createUsers(self, users, printers, dryrun=0) :
172        """Creates all users in PyKota's database."""
173        if users :
174            printersnames = [p[0] for p in printers]
175            command = "edpykota --add --noquota --printer %s %s" \
176                          % (",".join(['"%s"' % p for p in printersnames]), \
177                             " ".join(['"%s"' % u for u in users]))
178            self.runCommand(command, dryrun)
179           
180    def createGroups(self, groups, printers, dryrun=0) :
181        """Creates all groups in PyKota's database."""
182        if groups :
183            printersnames = [p[0] for p in printers]
184            commands = ["edpykota --add --groups --noquota --printer %s %s" \
185                            % (",".join(['"%s"' % p for p in printersnames]), \
186                               " ".join(['"%s"' % g for g in groups.keys()]))]
187            revmembership = {}
188            for (groupname, usernames) in groups.items() :
189                for username in usernames :
190                    revmembership.setdefault(username, []).append(groupname)
191            for (username, groupnames) in revmembership.items() :       
192                commands.append('edpykota --ingroups %s "%s"' \
193                    % (",".join(['"%s"' % g for g in groupnames]), username))
194            for command in commands :
195                self.runCommand(command, dryrun)
196       
197    def hintConfig(self, printers) :   
198        """Gives some hints about what to put into pykota.conf"""
199        if not printers :
200            return
201        sys.stderr.flush() # ensure outputs don't mix   
202        print     
203        print "--- CUT ---"
204        print "# Here are some lines that we suggest you add at the end"
205        print "# of the pykota.conf file. These lines gives possible"
206        print "# values for the way print jobs' size will be computed."
207        print "# NB : it is possible that a manual configuration gives"
208        print "# better results for you. As always, your mileage may vary."
209        print "#"
210        for (name, uri) in printers :
211            print "[%s]" % name
212            accounter = "software()"
213            try :
214                uri = uri.split("cupspykota:", 2)[-1]
215            except (ValueError, IndexError) :   
216                pass
217            else :   
218                while uri and uri.startswith("/") :
219                    uri = uri[1:]
220                try :
221                    (backend, destination) = uri.split(":", 1) 
222                    if backend not in ("ipp", "http", "https", "lpd", "socket") :
223                        raise ValueError
224                except ValueError :   
225                    pass
226                else :       
227                    while destination.startswith("/") :
228                        destination = destination[1:]
229                    checkauth = destination.split("@", 1)   
230                    if len(checkauth) == 2 :
231                        destination = checkauth[1]
232                    parts = destination.split("/")[0].split(":")
233                    if len(parts) == 2 :
234                        (hostname, port) = parts
235                        try :
236                            port = int(port)
237                        except ValueError :
238                            port = 9100
239                    else :   
240                        (hostname, port) = parts[0], 9100
241                       
242                    # check udp/161 (SNMP)
243                    # TODO : this doesn't work as expected if the host
244                    # TODO : is down, I must find another solution.
245                    trysnmp = 0
246                    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
247                    try :
248                        s.connect((hostname, socket.getservbyname("snmp", "udp")))
249                        for i in range(2) :
250                            s.send("")
251                    except :
252                        pass
253                    else :   
254                        trysnmp = 1   
255                    s.close()
256                   
257                    # check tcp/9100
258                    trypjl = 0
259                    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
260                    try :
261                        s.connect((hostname, port))
262                    except :   
263                        pass
264                    else :   
265                        trypjl = 1
266                    s.close()
267                       
268                    if trysnmp :
269                        accounter = "hardware(snmp)"
270                    elif trypjl :   
271                        if port != 9100 :
272                            accounter = "hardware(pjl:%s)" % port
273                        else :   
274                            accounter = "hardware(pjl)" % port
275                   
276            print "accounter : %s" % accounter
277            print
278        print "--- CUT ---"
279       
280    def main(self, names, options) :
281        """Intializes PyKota's database."""
282        if not self.config.isAdmin :
283            raise PyKotaToolError, "%s : %s" % (pwd.getpwuid(os.geteuid())[0],\
284                                   _("You're not allowed to use this command."))
285           
286        if not names :
287            names = ["*"]
288           
289        self.printInfo(_("Please be patient..."))
290        dryrun = not options["force"]
291        if dryrun :
292            self.printInfo(_("Don't worry, the database WILL NOT BE MODIFIED."))
293        else :   
294            self.printInfo(_("Please WORRY NOW, the database WILL BE MODIFIED."))
295           
296        if options["dousers"] :   
297            if not options["uidmin"] :   
298                self.printInfo(_("System users will have a print account as well !"), "warn")
299                uidmin = 0
300            else :   
301                try :
302                    uidmin = int(options["uidmin"])
303                except :   
304                    try :
305                        uidmin = pwd.getpwnam(options["uidmin"])[2]
306                    except KeyError, msg :   
307                        raise PyKotaToolError, _("Unknown username %s : %s") \
308                                                   % (options["uidmin"], msg)
309                       
310            if not options["uidmax"] :   
311                uidmax = sys.maxint
312            else :   
313                try :
314                    uidmax = int(options["uidmax"])
315                except :   
316                    try :
317                        uidmax = pwd.getpwnam(options["uidmax"])[2]
318                    except KeyError, msg :   
319                        raise PyKotaToolError, _("Unknown username %s : %s") \
320                                                   % (options["uidmax"], msg)
321           
322            if uidmin > uidmax :           
323                (uidmin, uidmax) = (uidmax, uidmin)
324            users = self.listUsers(uidmin, uidmax)
325        else :   
326            users = []
327           
328        if options["dogroups"] :   
329            if not options["gidmin"] :   
330                self.printInfo(_("System groups will have a print account as well !"), "warn")
331                gidmin = 0
332            else :   
333                try :
334                    gidmin = int(options["gidmin"])
335                except :   
336                    try :
337                        gidmin = grp.getgrnam(options["gidmin"])[2]
338                    except KeyError, msg :   
339                        raise PyKotaToolError, _("Unknown groupname %s : %s") \
340                                                   % (options["gidmin"], msg)
341                       
342            if not options["gidmax"] :   
343                gidmax = sys.maxint
344            else :   
345                try :
346                    gidmax = int(options["gidmax"])
347                except :   
348                    try :
349                        gidmax = grp.getgrnam(options["gidmax"])[2]
350                    except KeyError, msg :   
351                        raise PyKotaToolError, _("Unknown groupname %s : %s") \
352                                                   % (options["gidmax"], msg)
353           
354            if gidmin > gidmax :           
355                (gidmin, gidmax) = (gidmax, gidmin)
356            groups = self.listGroups(gidmin, gidmax, users)
357            if not options["emptygroups"] :
358                for (groupname, members) in groups.items() :
359                    if not members :
360                        del groups[groupname]
361        else :   
362            groups = []
363           
364        printers = self.listPrinters(names)
365        if printers :
366            self.createPrinters(printers, dryrun)
367            self.createUsers([entry[0] for entry in users], printers, dryrun)
368            self.createGroups(groups, printers, dryrun)
369       
370        if dryrun :
371            self.printInfo(_("Simulation terminated."))
372        else :   
373            self.printInfo(_("Database initialized !"))
374       
375        if options["doconf"] :   
376            self.hintConfig(printers)
377                   
378                     
379if __name__ == "__main__" : 
380    retcode = 0
381    try :
382        short_options = "hvdDefu:U:g:G:c"
383        long_options = ["help", "version", "dousers", "dogroups", \
384                        "emptygroups", "force", "uidmin=", "uidmax=", \
385                        "gidmin=", "gidmax=", "doconf"]
386       
387        # Initializes the command line tool
388        manager = PKTurnKey(doc=__doc__)
389        manager.deferredInit()
390       
391        # parse and checks the command line
392        (options, args) = manager.parseCommandline(sys.argv[1:], \
393                                                   short_options, \
394                                                   long_options, \
395                                                   allownothing=1)
396       
397        # sets long options
398        options["help"] = options["h"] or options["help"]
399        options["version"] = options["v"] or options["version"]
400        options["dousers"] = options["d"] or options["dousers"]
401        options["dogroups"] = options["D"] or options["dogroups"]
402        options["emptygroups"] = options["e"] or options["emptygroups"]
403        options["force"] = options["f"] or options["force"]
404        options["uidmin"] = options["u"] or options["uidmin"]
405        options["uidmax"] = options["U"] or options["uidmax"]
406        options["gidmin"] = options["g"] or options["gidmin"]
407        options["gidmax"] = options["G"] or options["gidmax"]
408        options["doconf"] = options["c"] or options["doconf"]
409       
410        if options["uidmin"] or options["uidmax"] :
411            if not options["dousers"] :
412                manager.printInfo(_("The --uidmin or --uidmax command line option implies --dousers as well."), "warn")
413            options["dousers"] = 1   
414           
415        if options["gidmin"] or options["gidmax"] :
416            if not options["dogroups"] :
417                manager.printInfo(_("The --gidmin or --gidmax command line option implies --dogroups as well."), "warn")
418            options["dogroups"] = 1
419       
420        if options["dogroups"] :
421            if not options["dousers"] :
422                manager.printInfo(_("The --dogroups command line option implies --dousers as well."), "warn")
423            options["dousers"] = 1   
424           
425        if options["help"] :
426            manager.display_usage_and_quit()
427        elif options["version"] :
428            manager.display_version_and_quit()
429        else :
430            retcode = manager.main(args, options)
431    except KeyboardInterrupt :       
432        sys.stderr.write("\nInterrupted with Ctrl+C !\n")
433    except SystemExit :       
434        pass
435    except :
436        try :
437            manager.crashed("pkturnkey failed")
438        except :   
439            crashed("pkturnkey failed")
440        retcode = -1
441
442    try :
443        manager.storage.close()
444    except (TypeError, NameError, AttributeError) :   
445        pass
446       
447    sys.exit(retcode)   
Note: See TracBrowser for help on using the browser.