From 4c77be6bc2ebffe16e40f74b02ad997bee8f857e Mon Sep 17 00:00:00 2001 From: Nicolas Hennion Date: Tue, 22 Jan 2013 21:03:44 +0100 Subject: Add password for the client/server mode --- NEWS | 1 + README | 7 +-- glances/glances.py | 126 +++++++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 117 insertions(+), 17 deletions(-) diff --git a/NEWS b/NEWS index b185063e..9efa33e8 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,7 @@ Version 1.6 IO rate only available on Linux from a root account * If CPU iowait alert then sort by processes by IO rate * Per CPU display IOwait (if data is available) + * Add password for the client/server mode (-P password) * Process column style auto (underline) or manual (bold) * Display a sort indicator (is space is available) * Change the table key in the help screen diff --git a/README b/README index 6c2551ce..948fdcc7 100644 --- a/README +++ b/README @@ -136,10 +136,11 @@ In server mode, you can set the bind address (-B ADDRESS) and listenning TCP por In client mode, you can set the TCP port of the server (-p port). -Default binding address is 0.0.0.0 (Glances will listen on all the networks interfaces). - TCP port is 61209. +Default binding address is 0.0.0.0 (Glances will listen on all the networks interfaces) and TCP port is 61209. -In client/server mode, limits are set by the server. +In client/server mode, limits are set by the server side. + +The version 1.6 introduces a optionnal password to access to the server (-P password). ## User guide diff --git a/glances/glances.py b/glances/glances.py index 8e2c38bf..e6e0dc83 100755 --- a/glances/glances.py +++ b/glances/glances.py @@ -57,6 +57,10 @@ except ImportError: from ConfigParser import RawConfigParser from ConfigParser import NoOptionError +# For client/server authentication +from base64 import b64decode +from hashlib import md5 + try: # For Python v2.x from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler @@ -68,10 +72,10 @@ except ImportError: try: # For Python v2.x - from xmlrpclib import ServerProxy + from xmlrpclib import ServerProxy, ProtocolError except ImportError: # For Python v3.x - from xmlrpc.client import ServerProxy + from xmlrpc.client import ServerProxy, ProtocolError if not is_Windows: # Only import curses for non Windows OS @@ -2605,8 +2609,15 @@ class glancesScreen: self.process_y + 3 + processes, process_x + 47, format(dtime, '>8'), 8) # IO + # Hack to allow client 1.6 to connect to server 1.5.2 + process_tag_io = True + try: + if (processlist[processes]['io_counters'][4] == 0): + process_tag_io = True + except: + process_tag_io = False if tag_io: - if processlist[processes]['io_counters'][4] == 0: + if (not process_tag_io): # If io_tag == 0 (['io_counters'][4]) # then do not diplay IO rate self.term_window.addnstr( @@ -3110,6 +3121,47 @@ class GlancesHandler(SimpleXMLRPCRequestHandler): """ rpc_paths = ('/RPC2',) + def authenticate(self, headers): + auth = headers.get('Authorization') + try: + (basic, _, encoded) = headers.get('Authorization').partition(' ') + except: + # Client did not ask for authentidaction + # If server need it then exit + return not self.server.isAuth + else: + # Client authentication + (basic, _, encoded) = headers.get('Authorization').partition(' ') + assert basic == 'Basic', 'Only basic authentication supported' + # Encoded portion of the header is a string + # Need to convert to bytestring + encodedByteString = encoded.encode() + # Decode Base64 byte String to a decoded Byte String + decodedBytes = b64decode(encodedByteString) + # Convert from byte string to a regular String + decodedString = decodedBytes.decode() + # Get the username and password from the string + (username, _, password) = decodedString.partition(':') + # Check that username and password match internal global dictionary + return self.check_user(username, password) + + def check_user(self, username, password): + # Check username and password in the dictionnary + if username in self.server.user_dict: + if self.server.user_dict[username] == md5(password).hexdigest(): + return True + return False + + def parse_request(self): + if SimpleXMLRPCRequestHandler.parse_request(self): + # Next we authenticate + if self.authenticate(self.headers): + return True + else: + # if authentication fails, tell the client + self.send_error(401, 'Authentication failed') + return False + def log_message(self, format, *args): # No message displayed on the server side pass @@ -3215,14 +3267,27 @@ class GlancesServer(): This class creates and manages the TCP client """ - def __init__(self, bind_address, bind_port=61209, - RequestHandler=GlancesHandler, - refresh_time=1): + + def __init__(self, bind_address, bind_port = 61209, + RequestHandler = GlancesHandler, + refresh_time = 1): self.server = SimpleXMLRPCServer((bind_address, bind_port), requestHandler=RequestHandler) + # The users dict + # username / MD5 password couple + # By default, no auth is needed + self.server.user_dict = {} + self.server.isAuth = False + # Register functions self.server.register_introspection_functions() self.server.register_instance(GlancesInstance(refresh_time)) - return + + def add_user(self, username, password): + ''' + Add an user to the dictionnary + ''' + self.server.user_dict[username] = md5(password).hexdigest() + self.server.isAuth = True def serve_forever(self): self.server.serve_forever() @@ -3236,19 +3301,36 @@ class GlancesClient(): This class creates and manages the TCP client """ - def __init__(self, server_address, server_port=61209): + def __init__(self, server_address, server_port=61209, + username = "glances", password = ""): + # Build the URI + if (password != ""): + uri = 'http://%s:%s@%s:%d' % (username, password, server_address, server_port) + else: + uri = 'http://%s:%d' % (server_address, server_port) + + # Try to connect to the URI try: - self.client = ServerProxy('http://%s:%d' % (server_address, server_port)) + self.client = ServerProxy(uri) except: - print(_("Error: creating client socket") + " http://%s:%d" % (server_address, server_port)) + print(_("Error: creating client socket") + " %s" % uri) pass return def client_init(self): + try: + self.client.init() + except ProtocolError as err: + if (str(err).find(" 401 ") > 0): + print(_("Error: Connection to server failed. Bad password.")) + sys.exit(-1) + else: + print(_("Error: Connection to server failed. Unknown error.")) + sys.exit(-1) try: client_version = self.client.init()[:3] except: - print(_("Error: Connection to server failed")) + print(_("Error: Connection to server failed. Can not get the server version.")) sys.exit(-1) else: return __version__[:2] == client_version[:2] @@ -3296,6 +3378,7 @@ def printSyntax(): print(_("\t-o output\tDefine additional output (available: HTML or CSV)")) print(_("\t-p PORT\t\tDefine the client or server TCP port (default: %d)" % server_port)) + print(_("\t-P password\tClient/server password")) print(_("\t-s\t\tRun Glances in server mode")) print(_("\t-t sec\t\tSet the refresh time in seconds (default: %d)" % refresh_time)) @@ -3361,13 +3444,17 @@ def main(): server_port = 61209 bind_ip = "0.0.0.0" + # Default username/password + username = "glances" + password = "" + # Manage args try: - opts, args = getopt.getopt(sys.argv[1:], "B:bdemnho:f:t:vsc:p:C:", + opts, args = getopt.getopt(sys.argv[1:], "B:bdemnho:f:t:vsc:p:C:P:", ["bind", "bytepersec", "diskio", "mount", "sensors", "netrate", "help", "output", "file", "time", "version", "server", - "client", "port", "config"]) + "client", "port", "config", "password"]) except getopt.GetoptError as err: # Print help information and exit: print(str(err)) @@ -3379,6 +3466,13 @@ def main(): sys.exit(0) elif opt in ("-s", "--server"): server_tag = True + elif opt in ("-P", "--password"): + try: + arg + except NameError: + print(_("Error: -P flag need an argument (password)")) + sys.exit(2) + password = arg elif opt in ("-B", "--bind"): try: arg @@ -3500,6 +3594,10 @@ def main(): print(_("Glances server is running on") + " %s:%s" % (bind_ip, server_port)) server = GlancesServer(bind_ip, server_port, GlancesHandler, refresh_time) + # Set the server login/password (if -P tag) + if (password != ""): + server.add_user(username, password) + # Init Limits limits = glancesLimits(conf_file) @@ -3508,7 +3606,7 @@ def main(): stats.update({}) elif client_tag: # Init the client (displaying server stat in the CLI) - client = GlancesClient(server_ip, server_port) + client = GlancesClient(server_ip, server_port, username, password) # Test if client and server are in the same major version if not client.client_init(): -- cgit v1.2.3