root / pykota / trunk / bin / pknotify @ 2785

Revision 2785, 11.4 kB (checked in by jerome, 18 years ago)

Authentication can now be checked through PAM.

  • 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# A notifier for PyKota
5#
6# PyKota - Print Quotas for CUPS and LPRng
7#
8# (c) 2003, 2004, 2005, 2006 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 popen2
30import socket
31import xmlrpclib
32
33try :
34    import PAM
35except ImportError :   
36    hasPAM = 0
37else :   
38    hasPAM = 1
39
40from pykota.tool import Tool, PyKotaToolError, PyKotaCommandLineError, crashed, N_
41
42__doc__ = N_("""pknotify v%(__version__)s (c) %(__years__)s %(__author__)s
43
44Notifies or ask questions to end users who launched the PyKotIcon application.
45
46command line usage :
47
48  pknotify  [options]  [arguments]
49
50options :
51
52  -v | --version             Prints pkbanner's version number then exits.
53  -h | --help                Prints this message then exits.
54 
55  -d | --destination h[:p]   Sets the destination hostname and optional
56                             port onto which contact the remote PyKotIcon
57                             application. This option is mandatory.
58                             When not specified, the port defaults to 7654.
59                             
60  -a | --ask                 Tells pknotify to ask something to the end
61                             user. Then pknotify will output the result.
62                       
63  -C | --checkauth           When --ask is used and an 'username' and a
64                             'password' are asked to the end user, then
65                             pknotify will try to authenticate the user
66                             through PAM. If authentified, this program
67                             will print "AUTH=YES", else "AUTH=NO".
68                             If a field is missing, "AUTH=IMPOSSIBLE" will
69                             be printed.
70                             
71  -c | --confirm             Tells pknotify to ask for either a confirmation                       
72                             or abortion.
73                             
74  -n | --notify              Tells pknotify to send an informational message
75                             message to the end user.
76                             
77  -q | --quit                Tells pknotify to send a message asking the
78                             PyKotIcon application to exit. This option can
79                             be combined with the other ones to make PyKotIcon
80                             exit after having sent the answer from the dialog.
81                             
82  You MUST specify either --ask, --confirm, --notify or --quit.
83
84  arguments :             
85 
86    -a | --ask : Several arguments are accepted, or the form
87                 "label:varname:defaultvalue". The result will
88                 be printed to stdout in the following format :
89                 VAR1NAME=VAR1VALUE
90                 VAR2NAME=VAR2VALUE
91                 ...
92                 If the dialog was cancelled, nothing will be
93                 printed. If one of the varname is 'password'
94                 then this field is asked as a password (you won't
95                 see what you type in), and is NOT printed. Although
96                 it is not printed, it will be used to check if
97                 authentication is valid if you specify --checkauth.
98                 
99    -c | --confirm : A single argument is expected, representing the
100                     message to display. If the dialog is confirmed
101                     then pknotify will print OK, else CANCEL.
102                     
103    -n | --notify : A single argument is expected, representing the                 
104                    message to display. In this case pknotify will
105                    always print OK.
106                   
107examples :                   
108
109  pknotify -d client:7654 --confirm "This job costs :\n10 credits !"
110 
111  Would display the cost of a print job and asks for confirmation.
112 
113  pknotify --destination $PYKOTAJOBORIGINATINGHOSTNAME:7654 \\
114           --checkauth --ask "Your name:username:" "Your password:password:"
115           
116  Asks an username and password, and checks if they are valid.         
117  NB : The PYKOTAJOBORIGINATINGHOSTNAME environment variable is
118  only set if you launch pknotify from cupspykota through a directive
119  in ~pykota/pykota.conf
120""")
121       
122class PyKotaNotify(Tool) :       
123    """A class for pknotify."""
124    def sanitizeMessage(self, msg) :
125        """Replaces \\n and returns a messagee in xmlrpclib Binary format."""
126        return xmlrpclib.Binary(msg.replace("\\n", "\n"))
127       
128    def convPAM(self, auth, queries, userdata) :
129        """Prepares PAM datas."""
130        response = []
131        for (query, qtype) in queries :
132            if qtype == PAM.PAM_PROMPT_ECHO_OFF :
133                response.append((self.password, 0))
134            elif qtype in (PAM.PAM_PROMPT_ECHO_ON, PAM.PAM_ERROR_MSG, PAM.PAM_TEXT_INFO) :
135                self.printInfo("Unexpected PAM query : %s (%s)" % (query, qtype), "warn")
136                response.append(('', 0))
137            else:
138                return None
139        return response
140
141    def checkAuth(self, username, password) :   
142        """Checks if we could authenticate an username with a password."""
143        if not hasPAM :   
144            raise PyKotaCommandLineError, _("You MUST install PyPAM for this functionnality to work !")
145        else :   
146            retcode = False
147            self.password = password
148            self.regainPriv()
149            auth = PAM.pam()
150            auth.start("passwd")
151            auth.set_item(PAM.PAM_USER, username)
152            auth.set_item(PAM.PAM_CONV, self.convPAM)
153            try :
154                auth.authenticate()
155                auth.acct_mgmt()
156            except PAM.error, resp :
157                self.printInfo(_("Authentication error for user %s : %s") % (username, resp), "warn")
158            except :
159                self.printInfo(_("Internal error : can't authenticate user %s") % username, "error")
160            else :
161                self.logdebug(_("Password correct for user %s") % username)
162                retcode = True
163            self.dropPriv()   
164            return retcode
165           
166    def main(self, arguments, options) :
167        """Notifies or asks questions to end users through PyKotIcon."""
168        try :
169            (destination, port) = options["destination"].split(":")
170        except ValueError :
171            destination = options["destination"]
172            port = 7654
173        try :   
174            server = xmlrpclib.ServerProxy("http://%s:%s" % (destination, port))
175            if options["ask"] :
176                labels = []
177                varnames = []
178                varvalues = {}
179                for arg in arguments :
180                    try :
181                        (label, varname, varvalue) = arg.split(":", 2)
182                    except ValueError :   
183                        raise PyKotaCommandLineError, "argument '%s' is invalid !" % arg
184                    labels.append(self.sanitizeMessage(label))
185                    varname = varname.lower()
186                    varnames.append(varname)
187                    varvalues[varname] = self.sanitizeMessage(varvalue)
188                result = server.askDatas(labels, varnames, varvalues)   
189                if result["isValid"] :
190                    for varname in varnames :
191                        result[varname] = result[varname].data
192                        if (varname != "password") :
193                            print "%s=%s" % (varname.upper(), result[varname])
194                    if options["checkauth"] :
195                        if ("username" in varnames) and ("password" in varnames) :
196                            if self.checkAuth(result["username"], result["password"]) :
197                                print "AUTH=YES"
198                            else :   
199                                print "AUTH=NO"
200                        else :       
201                            print "AUTH=IMPOSSIBLE"
202            elif options["confirm"] :
203                print server.showDialog(self.sanitizeMessage(arguments[0]), True)
204            elif options["notify"] :
205                print server.showDialog(self.sanitizeMessage(arguments[0]), False)
206               
207            if options["quit"] :   
208                server.quitApplication()
209        except (socket.error, socket.gaierror), msg :
210            raise PyKotaCommandLineError, "%s : %s" % (_("Connection error"), str(msg))
211       
212if __name__ == "__main__" :
213    retcode = 0
214    try :
215        defaults = { \
216                   }
217        short_options = "vhd:acnqC"
218        long_options = ["help", "version", "destination=", \
219                        "ask", "checkauth", "confirm", "notify", "quit" ]
220       
221        # Initializes the command line tool
222        notifier = PyKotaNotify(doc=__doc__)
223        notifier.deferredInit()
224       
225        # parse and checks the command line
226        (options, args) = notifier.parseCommandline(sys.argv[1:], short_options, long_options)
227       
228        # sets long options
229        options["help"] = options["h"] or options["help"]
230        options["version"] = options["v"] or options["version"]
231        options["destination"] = options["d"] or options["destination"]
232        options["ask"] = options["a"] or options["ask"]
233        options["confirm"] = options["c"] or options["confirm"]
234        options["notify"] = options["n"] or options["notify"]
235        options["quit"] = options["q"] or options["quit"]
236        options["checkauth"] = options["C"] or options["checkauth"]
237       
238        if options["help"] :
239            notifier.display_usage_and_quit()
240        elif options["version"] :
241            notifier.display_version_and_quit()
242        elif (options["ask"] and (options["confirm"] or options["notify"])) \
243             or (options["confirm"] and (options["ask"] or options["notify"])) \
244             or (options["checkauth"] and not options["ask"]) \
245             or (options["notify"] and (options["ask"] or options["confirm"])) :
246            raise PyKotaCommandLineError, _("incompatible options, see help.")
247        elif (not options["destination"]) \
248             or not (options["quit"] or options["ask"] or options["confirm"] or options["notify"]) :
249            raise PyKotaCommandLineError, _("some options are mandatory, see help.")
250        elif (not args) and (not options["quit"]) :
251            raise PyKotaCommandLineError, _("some options require arguments, see help.")
252        else :
253            retcode = notifier.main(args, options)
254    except KeyboardInterrupt :       
255        sys.stderr.write("\nInterrupted with Ctrl+C !\n")
256        retcode = -3
257    except PyKotaCommandLineError, msg :   
258        sys.stderr.write("%s : %s\n" % (sys.argv[0], msg))
259        retcode = -2
260    except SystemExit :       
261        pass
262    except :
263        try :
264            notifier.crashed("%s failed" % sys.argv[0])
265        except :   
266            crashed("%s failed" % sys.argv[0])
267        retcode = -1
268       
269    sys.exit(retcode)   
Note: See TracBrowser for help on using the browser.