#! /usr/bin/env python # -*- coding: ISO-8859-15 -*- # A notifier for PyKota # # PyKota - Print Quotas for CUPS and LPRng # # (c) 2003, 2004, 2005, 2006 Jerome Alet # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # $Id$ # # import sys import os import popen2 import socket import xmlrpclib try : import PAM except ImportError : hasPAM = 0 else : hasPAM = 1 from pykota.tool import Tool, PyKotaToolError, PyKotaCommandLineError, crashed, N_ __doc__ = N_("""pknotify v%(__version__)s (c) %(__years__)s %(__author__)s Notifies or ask questions to end users who launched the PyKotIcon application. command line usage : pknotify [options] [arguments] options : -v | --version Prints pkbanner's version number then exits. -h | --help Prints this message then exits. -d | --destination h[:p] Sets the destination hostname and optional port onto which contact the remote PyKotIcon application. This option is mandatory. When not specified, the port defaults to 7654. -a | --ask Tells pknotify to ask something to the end user. Then pknotify will output the result. -C | --checkauth When --ask is used and both an 'username' and a 'password' are asked to the end user, then pknotify will try to authenticate the user through PAM. If authentified, this program will print "AUTH=YES", else "AUTH=NO". If a field is missing, "AUTH=IMPOSSIBLE" will be printed. If the user is authenticated, then "USERNAME=xxxx" will be printed as well. -c | --confirm Tells pknotify to ask for either a confirmation or abortion. -n | --notify Tells pknotify to send an informational message message to the end user. -q | --quit Tells pknotify to send a message asking the PyKotIcon application to exit. This option can be combined with the other ones to make PyKotIcon exit after having sent the answer from the dialog. You MUST specify either --ask, --confirm, --notify or --quit. arguments : -a | --ask : Several arguments are accepted, or the form "label:varname:defaultvalue". The result will be printed to stdout in the following format : VAR1NAME=VAR1VALUE VAR2NAME=VAR2VALUE ... If the dialog was cancelled, nothing will be printed. If one of the varname is 'password' then this field is asked as a password (you won't see what you type in), and is NOT printed. Although it is not printed, it will be used to check if authentication is valid if you specify --checkauth. -c | --confirm : A single argument is expected, representing the message to display. If the dialog is confirmed then pknotify will print OK, else CANCEL. -n | --notify : A single argument is expected, representing the message to display. In this case pknotify will always print OK. examples : pknotify -d client:7654 --confirm "This job costs :\n10 credits !" Would display the cost of a print job and asks for confirmation. pknotify --destination $PYKOTAJOBORIGINATINGHOSTNAME:7654 \\ --checkauth --ask "Your name:username:" "Your password:password:" Asks an username and password, and checks if they are valid. NB : The PYKOTAJOBORIGINATINGHOSTNAME environment variable is only set if you launch pknotify from cupspykota through a directive in ~pykota/pykota.conf """) class PyKotaNotify(Tool) : """A class for pknotify.""" def sanitizeMessage(self, msg) : """Replaces \\n and returns a messagee in xmlrpclib Binary format.""" return xmlrpclib.Binary(msg.replace("\\n", "\n")) def convPAM(self, auth, queries, userdata) : """Prepares PAM datas.""" response = [] for (query, qtype) in queries : if qtype == PAM.PAM_PROMPT_ECHO_OFF : response.append((self.password, 0)) elif qtype in (PAM.PAM_PROMPT_ECHO_ON, PAM.PAM_ERROR_MSG, PAM.PAM_TEXT_INFO) : self.printInfo("Unexpected PAM query : %s (%s)" % (query, qtype), "warn") response.append(('', 0)) else: return None return response def checkAuth(self, username, password) : """Checks if we could authenticate an username with a password.""" if not hasPAM : raise PyKotaCommandLineError, _("You MUST install PyPAM for this functionnality to work !") else : retcode = False self.password = password self.regainPriv() auth = PAM.pam() auth.start("passwd") auth.set_item(PAM.PAM_USER, username) auth.set_item(PAM.PAM_CONV, self.convPAM) try : auth.authenticate() auth.acct_mgmt() except PAM.error, resp : self.printInfo(_("Authentication error for user %s : %s") % (username, resp), "warn") except : self.printInfo(_("Internal error : can't authenticate user %s") % username, "error") else : self.logdebug(_("Password correct for user %s") % username) retcode = True self.dropPriv() return retcode def main(self, arguments, options) : """Notifies or asks questions to end users through PyKotIcon.""" try : (destination, port) = options["destination"].split(":") except ValueError : destination = options["destination"] port = 7654 try : server = xmlrpclib.ServerProxy("http://%s:%s" % (destination, port)) if options["ask"] : labels = [] varnames = [] varvalues = {} for arg in arguments : try : (label, varname, varvalue) = arg.split(":", 2) except ValueError : raise PyKotaCommandLineError, "argument '%s' is invalid !" % arg labels.append(self.sanitizeMessage(label)) varname = varname.lower() varnames.append(varname) varvalues[varname] = self.sanitizeMessage(varvalue) result = server.askDatas(labels, varnames, varvalues) if result["isValid"] : authok = None if options["checkauth"] : if ("username" in varnames) and ("password" in varnames) : if self.checkAuth(result["username"].data, result["password"].data) : authok = "AUTH=YES" else : authok = "AUTH=NO" else : authok = "AUTH=IMPOSSIBLE" for varname in varnames : if (varname != "password") \ and ((varname != "username") or (authok == "AUTH=YES")) : print "%s=%s" % (varname.upper(), result[varname].data) if authok is not None : print authok elif options["confirm"] : print server.showDialog(self.sanitizeMessage(arguments[0]), True) elif options["notify"] : print server.showDialog(self.sanitizeMessage(arguments[0]), False) if options["quit"] : server.quitApplication() except (socket.error, socket.gaierror), msg : raise PyKotaCommandLineError, "%s : %s" % (_("Connection error"), str(msg)) if __name__ == "__main__" : retcode = 0 try : defaults = { \ } short_options = "vhd:acnqC" long_options = ["help", "version", "destination=", \ "ask", "checkauth", "confirm", "notify", "quit" ] # Initializes the command line tool notifier = PyKotaNotify(doc=__doc__) notifier.deferredInit() # parse and checks the command line (options, args) = notifier.parseCommandline(sys.argv[1:], short_options, long_options) # sets long options options["help"] = options["h"] or options["help"] options["version"] = options["v"] or options["version"] options["destination"] = options["d"] or options["destination"] options["ask"] = options["a"] or options["ask"] options["confirm"] = options["c"] or options["confirm"] options["notify"] = options["n"] or options["notify"] options["quit"] = options["q"] or options["quit"] options["checkauth"] = options["C"] or options["checkauth"] if options["help"] : notifier.display_usage_and_quit() elif options["version"] : notifier.display_version_and_quit() elif (options["ask"] and (options["confirm"] or options["notify"])) \ or (options["confirm"] and (options["ask"] or options["notify"])) \ or (options["checkauth"] and not options["ask"]) \ or (options["notify"] and (options["ask"] or options["confirm"])) : raise PyKotaCommandLineError, _("incompatible options, see help.") elif (not options["destination"]) \ or not (options["quit"] or options["ask"] or options["confirm"] or options["notify"]) : raise PyKotaCommandLineError, _("some options are mandatory, see help.") elif (not args) and (not options["quit"]) : raise PyKotaCommandLineError, _("some options require arguments, see help.") else : retcode = notifier.main(args, options) except KeyboardInterrupt : sys.stderr.write("\nInterrupted with Ctrl+C !\n") retcode = -3 except PyKotaCommandLineError, msg : sys.stderr.write("%s : %s\n" % (sys.argv[0], msg)) retcode = -2 except SystemExit : pass except : try : notifier.crashed("%s failed" % sys.argv[0]) except : crashed("%s failed" % sys.argv[0]) retcode = -1 sys.exit(retcode)