root / pykota / trunk / bin / pkturnkey @ 2507

Revision 2507, 19.0 kB (checked in by jerome, 19 years ago)

Removed a superfluous string interpolation parameter.
Improved PJL detection stuff to make it take less time, and better detect PJL support.

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