#!/usr/bin/env python

# This file is part of Window-Switch.
# Copyright (c) 2009-2013 Antoine Martin <antoine@nagafix.co.uk>
# Window-Switch is released under the terms of the GNU GPL v3

import re
import sys

from winswitch.consts import MAX_LINE_LENGTH, WINSWITCH_VERSION, APPLICATION_NAME, SITE_URL, BINASCII, OPEN_LOCALLY, OPEN_NEW_SESSION, OPEN_EXISTING_SESSION
from winswitch.objects.user import User
from winswitch.objects.server_command import ServerCommand
from winswitch.util.crypt_util import decrypt_salted_hex, sign_long, recreate_key, verify_long, encrypt_salted_hex
from winswitch.util.common import visible_command, check_remote_version, get_bool, plaintext, csv_list, parse_version_string, parse_csv, generate_UUID
from winswitch.util.format_util import format_message, parse_message_string, binencode, bindecode
from winswitch.util.simple_logger import Logger, msig
from winswitch.util.main_loop import callLater
from winswitch.net.commands import PROTOCOL_VERSION,\
	PING, PINGECHO, HELP, VERSION, \
	NOK, OK, STOP, SYNC, SYNC_END, \
	SET_REMOTE_KEY, SET_SALT, SET_HOST_INFO, \
	SET_TUNNEL_PORTS, \
	SEND_ENCRYPTED, \
	SEND_MESSAGE, \
	ADD_USER, LOGOUT, \
	SET_XMODMAP, REMOVE_USER, AUTHENTICATION_FAILED, AUTHENTICATION_SUCCESS, REQUEST_USER_ICON, SET_USER_ICON, ADD_MOUNT_POINT, \
	ADD_SESSION, SET_SESSION_STATUS, REQUEST_SESSION_ICON, SET_SESSION_ICON, REMOVE_SESSION, SEND_SESSION, START_SESSION, DISCONNECT_SESSION, \
	OPEN_FILE, SHADOW_SESSION, REQUEST_SESSION, KILL_SESSION, CLOSE_SESSION, SET_SESSION_COMMAND, \
	REQUEST_SESSION_SOUND, RECEIVE_SESSION_SOUND, \
	ADD_SERVER_COMMAND, REQUEST_COMMAND_ICON, SET_COMMAND_ICON, \
	XDG_OPEN
	#SEND_FILE_DATA, ACK_FILE_DATA, CANCEL_FILE_TRANSFER not used here!

RESPONSE_TIMEOUT = 10

DEBUG_PINGS = "--debug-pings" in sys.argv

logger = Logger("protocol", log_colour=Logger.YELLOW)

TRACE_PROTOCOL_MESSAGE = "--trace-protocol-message="
trace_messages_re = {}
for arg in sys.argv:
	if arg.startswith(TRACE_PROTOCOL_MESSAGE):
		_re_str = arg[len(TRACE_PROTOCOL_MESSAGE):]
		try:
			_re = re.compile(_re_str)
			trace_messages_re[_re_str] = _re
			logger.slog("will log all messages matching '%s'" % _re_str)
		except Exception, e:
			logger.serr("invalid trace regular expression ignored: %s" % _re_str)

def is_traced(message):
	for s,_re in trace_messages_re.items():
		if _re.match(message):
			return	s
	return None


class ProtocolHandler:

	ENCRYPTED_COMMANDS = [SEND_SESSION]
	REQUIRE_RESPONSE_COMMANDS = [PING, HELP, SYNC]

	def __init__(self, server, send_method, drop_connection, is_connected):
		"""
		This class is used by both the client (via ServerConnection) and the server (via ClientChannel).
		* server is a ServerBase instance (ServerConfig or ServerSettings),
		* send_method(message) is used to send the data,
		* drop_connection(retry,message) is used to disconnect
		"""
		Logger(self, log_colour=Logger.YELLOW)
		self.sdebug(None, server, send_method)
		self.server = server
		self.send_method = send_method
		self.drop_connection = drop_connection
		self.is_connected = is_connected
		self.command_handlers = {}
		self.local_salt = generate_UUID()
		self.remote_salt = None
		self.local_key = None
		self.remote_key = None
		self.response_counter = 0
		self.add_default_command_handlers()
		self.remote_app_version = None
		self.pingcount= 0
		self.pingechocount = 0
		self.binary_encodings = [BINASCII]

	def binencode(self, data):
		return	binencode(data or "", self.binary_encodings)

	def __str__(self):
		return	"ProtocolHandler(%s)" % str(self.server)

	def set_local_key(self, key):
		self.local_key = key

	def set_remote_key(self, key):
		self.remote_key = key

	def check_response_received(self, command, counter):
		if counter<5:
			self.sdebug("response_counter=%s" % self.response_counter, command, counter)
		if self.response_counter==counter:
			if self.server and self.is_connected():
				self.serror("no response received - terminating connection with %s" % self.drop_connection, (command, counter))
				self.drop_connection(message="Timeout: no response received after %d seconds" % RESPONSE_TIMEOUT)

	def send(self, command, params=None, do_encrypt=False):
		if command in self.REQUIRE_RESPONSE_COMMANDS:
			callLater(RESPONSE_TIMEOUT, self.check_response_received, command, self.response_counter)

		message = self.format_enc_message(command, params, encrypted_commands=self.ENCRYPTED_COMMANDS, do_encrypt=do_encrypt, key=self.remote_key, salt=self.remote_salt)
		if message:
			self.do_send(message)

	def format_enc_message(self, command, params=None, encrypted_commands=[], do_encrypt=False, key=None, salt=None):
		message = format_message(command, params)
		if command and ((command in encrypted_commands) or do_encrypt):
			sig = msig(command, visible_command(str(params)), str(encrypted_commands), do_encrypt, key, salt)
			if not key:
				self.error(sig+" remote encryption key is not set! cannot format!")
				return None
			if not salt:
				self.error(sig+" salt is not set! cannot format!")
				return None
			enc_msg = encrypt_salted_hex(key, salt, message)
			message = SEND_ENCRYPTED+" '%s'" % enc_msg
		return	message

	def parse_opts(self, s):
		d = {}
		if s:
			if s.startswith("{") and s.endswith("}"):
				s = s[1:-1]
			for item in s.split(";"):
				if not item:
					continue
				kv = item.split("=")
				if len(kv)!=2:
					self.serror("cannot parse opt: %s, found %s parts instead of 2" % (str(item), len(kv)), s)
					raise Exception("cannot parse option string: "+str(s))
				d[kv[0]] = kv[1]
		return d

	def do_send(self, message):
		_re = is_traced(message)
		if _re:
			self.cwrite(Logger.HIGHLIGHTED_BROWN, "TT", "matches tracing regular expression: %s" % _re, message)
		#check len
		l = len(message)
		if l>=(MAX_LINE_LENGTH-16):
			self.serror("message is too long: %s (maximum is %s)" % (l, MAX_LINE_LENGTH), None, visible_command(message))
		else:
			self.send_method(message)
		return	message

	def add_command_handler(self, cmd, handler, replace=True):
		#self.sdebug("current=%s" % self.command_handlers.get(cmd), cmd, handler, replace)
		if not replace and self.command_handlers.get(cmd):
			return
		self.command_handlers[cmd] = handler

	def add_default_command_handlers(self):
		self.add_command_handler(NOK, self.nok)
		self.add_command_handler(OK, self.ok)
		self.add_command_handler(SET_SALT, self.do_set_salt)
		self.add_command_handler(SET_REMOTE_KEY, self.do_set_remote_key)
		self.add_command_handler(SEND_ENCRYPTED, self.do_encrypted_command)
		self.add_command_handler(VERSION, self.do_version)
		self.add_command_handler(HELP, self.do_help)
		self.add_command_handler(AUTHENTICATION_FAILED, self.do_authentication_failed)
		self.add_command_handler(PING, self.do_ping)
		self.add_command_handler(PINGECHO, self.do_pingecho)
		self.add_command_handler(SET_HOST_INFO, self.noop)
		self.add_command_handler(LOGOUT, self.do_logout)
		self.add_command_handler(SET_SESSION_COMMAND, self.noop)

	def add_base_user_and_session_handlers(self, replace=True, client=False):
		self.add_command_handler(ADD_USER, self.do_add_user, replace)
		self.add_command_handler(REMOVE_USER, self.do_remove_user, replace)
		if client:
			self.add_command_handler(ADD_SESSION, self.do_add_client_session, replace)
		self.add_command_handler(REMOVE_SESSION, self.do_remove_session, replace)
		self.add_command_handler(SET_SESSION_STATUS, self.do_set_session_status, replace)
		self.add_command_handler(SET_SESSION_ICON, self.do_set_session_icon, replace)
		self.add_command_handler(SET_USER_ICON, self.do_set_user_icon, replace)

	def add_authenticated_command_handlers(self, replace=True, client=False):
		self.add_base_user_and_session_handlers(replace, client)
		self.add_command_handler(REQUEST_SESSION, self.noop, replace)
		self.add_command_handler(START_SESSION, self.noop, replace)
		self.add_command_handler(SHADOW_SESSION, self.noop, replace)
		self.add_command_handler(SET_COMMAND_ICON, self.noop, replace)
		self.add_command_handler(SYNC, self.do_sync_all, replace)
		self.add_command_handler(SEND_MESSAGE, self.do_message, replace)

		#if send_ok_echo:
		#	self.send("OK " + cmd)

	#Returns true if data structure is changed
	#Which means that the command may need to be forwarded to the other clients
	def handle_command(self, command):
		if not command:
			self.sdebug("empty command", command)
			return	False
		_re = is_traced(command)
		if _re:
			self.cwrite(Logger.HIGHLIGHTED_BROWN, "TT", "matches tracing regular expression: %s" % _re, command)
		(cmd,args) = parse_message_string(command)
		if not cmd:
			self.serror("command parsing failed", visible_command(command))
			self.send(NOK, [visible_command(command), "command parsing failed"])
			return False
		self.response_counter += 1		#cancel kill timer
		command_handler = self.command_handlers.get(cmd)
		try:
			if command_handler:
				return command_handler(*args)
			else:
				return self.unknown_command(cmd, args)
		except Exception, e:
			self.serr("command_handler=%s" % command_handler, e, visible_command(command))
			self.send(NOK, [visible_command(command), "error during command execution"])
			return False

	##########################################################
	#Basic handlers:
	def unknown_command(self, cmd, args):
		self.serror(None, visible_command(cmd), visible_command(str(args)))
		self.send(NOK, [visible_command(cmd)])
		return	False

	def nok(self, *args):
		self.serror("NOK !", *args)
		return	False

	def ok(self, *args):
		return False

	def noop(self, *args):
		return False

	def pass_on(self, *args):
		return True

	##########################################################
	#Methods used to send data to the other end:

	def send_logout(self):
		self.send(LOGOUT)

	def send_sync(self, arg=None):
		args = None
		if arg:
			args = [arg]
		self.send(SYNC, args)

	def send_session_to_user(self, ID, uuid, password):
		self.send(SEND_SESSION, [ID, uuid, password])

	def send_start_session(self, uuid, session_type, screen_size, opts):
		self.send(START_SESSION, [uuid, session_type, screen_size, opts])

	def send_shadow_session(self, ID, read_only, shadow_type, screen_size, options):
		args = [ID, read_only, shadow_type, screen_size, options]
		self.send(SHADOW_SESSION, args)

	def send_session_status(self, ID, status, actor, preload, screen_size):
		data = [ID, status, actor, preload, screen_size]
		self.send(SET_SESSION_STATUS, data)

	def send_session_icon(self, session, large):
		if large:
			data = session.get_screen_capture_icon_data()
		else:
			data = session.get_window_icon_data()
			if not data:
				data = session.get_default_icon_data()
		if not data:
			if not session.preload:
				self.sdebug("no icon data available...", session, large)
			return
		enc_data = self.binencode(data)
		params = [session.ID, enc_data, large]
		self.send(SET_SESSION_ICON, params)


	def send_session(self, session):
		self.send(ADD_SESSION, [session.ID, session.display, session.status, session.name,
									session.host, session.port,
									session.owner, session.actor,
									session.command, session.command_uuid,
									session.session_type, session.start_time, session.requires_tunnel,
									session.preload,
									session.full_desktop, session.screen_size,
									session.shared_desktop, session.read_only, session.shadowed_display,
									session.options,
									session.can_export_sound, session.can_import_sound, session.can_clone_sound, session.uses_sound_out, session.uses_sound_in,
									session.user,
									])

	def send_disconnect_session(self, ID):
		self.send(DISCONNECT_SESSION, [ID])

	def send_kill_session(self, ID):
		self.send(KILL_SESSION, [ID])

	def send_close_session(self, ID):
		self.send(CLOSE_SESSION, [ID])

	def send_remove_session(self, ID):
		self.send(REMOVE_SESSION, [ID])

	def send_request_session(self, ID):
		self.send(REQUEST_SESSION, [ID])

	def send_xdg_open(self, argument):
		self.slog(None, argument)
		self.send(XDG_OPEN, [argument])

	def send_user(self, user):
		password = ""
		token = ""
		self.send(ADD_USER, [user.username, user.remote_username, user.name, user.uuid, user.host,
							user.crypto_modulus, user.crypto_public_exponent, token, password,
							user.locale,
							user.supports_xpra, user.supports_nx, user.supports_vnc, user.preferred_session_type,
							user.line_speed, user.tunnel_fs, user.tunnel_sink, user.tunnel_printer, user.tunnel_source, user.tunneled,
							user.supports_rdp,
							self.binencode(user.ssh_pub_keydata), user.xpra_x11, user.vnc_x11, user.platform,		#these are optional, maybe we shouldn't disclose them?
							self.binencode(user.xkbmap_print),
							user.binary_encodings,
							user.gstaudio_codecs, user.gstvideo_codecs,
                            user.open_urls, user.open_files,
							user.client_type,
							user.supports_virtualbox,
							self.binencode(user.xkbmap_query),
                            user.xkbmap_layout, user.xkbmap_variant,
							])

	def send_remove_user(self, uuid):
		self.send(REMOVE_USER, [uuid])

	def send_request_user_icon(self, uuid):
		self.send(REQUEST_USER_ICON, [uuid])

	def send_user_icon(self, uuid, icon_data):
		if not icon_data:
			return
		enc_data = self.binencode(icon_data)
		self.send(SET_USER_ICON, [uuid, enc_data])

	def send_request_session_icon(self, ID, large):
		if not large:
			self.send(REQUEST_SESSION_ICON, [ID])
		else:
			self.send(REQUEST_SESSION_ICON, [ID, large])

	def send_request_command_icon(self, uuid, command_type):
		self.send(REQUEST_COMMAND_ICON, [uuid, command_type])

	def send_command_icon(self, command):
		icon_data = command.get_icon_data()
		if not icon_data:
			return
		enc_data = self.binencode(icon_data)
		self.send(SET_COMMAND_ICON, [command.uuid, command.type, enc_data])

	def send_server_command(self, uuid, name, command, comment, icon_filename, category, command_type, uses_sound_in, uses_sound_out, uses_video, icon_names):
		self.send(ADD_SERVER_COMMAND, [uuid, name, command, comment, icon_filename, category, command_type, uses_sound_in, uses_sound_out, uses_video, icon_names])

	def send_stop_request(self, message=None):
		self.send(STOP, [message])

	def send_version(self):
		try:
			from winswitch.build_info import BUILT_BY, BUILT_ON, BUILD_DATE, RELEASE_BUILD
			extra = "Built on %s by %s. %s" % (BUILT_ON, BUILT_BY, BUILD_DATE)
			if RELEASE_BUILD:
				extra += " (release build)"
			else:
				extra += " (beta build)"
		except:
			extra = "No build information available"
		self.send(VERSION, [PROTOCOL_VERSION, WINSWITCH_VERSION, extra])

	def send_sync_end(self):
		self.send(SYNC_END)

	def send_salt(self):
		self.send(SET_SALT, [self.local_salt])

	def send_host_info(self, *args):
		self.send(SET_HOST_INFO, args)

	def send_local_key(self):
		crypto_modulus = self.local_key.n
		crypto_public_exponent = self.local_key.e
		if not self.remote_salt:
			self.serr("cannot send local key without remote salt")
		else:
			proof = sign_long(self.local_key, self.remote_salt)
			self.send(SET_REMOTE_KEY, [crypto_modulus, crypto_public_exponent, proof])

	def send_encrypted_message(self, key, uuid, title, message):
		self.send(SEND_MESSAGE, [uuid, title, message], True)

	def send_plain_message(self, uuid, title, message, from_uuid):
		#Sanitize:
		self.send(SEND_MESSAGE, [uuid, plaintext(title), plaintext(message), from_uuid], False)

	def send_authentication_failed(self, message=None):
		explanation = None
		if message:
			explanation = [message]
		self.send(AUTHENTICATION_FAILED, [explanation])

	def send_ok(self, message=None):
		explanation = None
		if message:
			explanation = [message]
		self.send(OK, [explanation])

	def send_authentication_success(self):
		self.send(AUTHENTICATION_SUCCESS)

	def send_help(self):
		msg = "%s Communication Channel - see %s" % (APPLICATION_NAME, SITE_URL)
		self.send_ok(msg)

	def send_ping(self, timeout=RESPONSE_TIMEOUT):
		if not self.is_connected():
			self.serror("no longer connected to %s, no point in sending a ping" % self.server)
			return
		self.pingcount += 1
		self.send(PING, [self.pingcount])
		def check_ping_echo_received(pingcount_sent):
			if self.pingechocount<pingcount_sent:
				if self.is_connected():
					self.serror("server=%s, pingechocount=%s" % (self.server, self.pingechocount), pingcount_sent)
					self.drop_connection(message="Timeout: ping did not receive a response after %d seconds" % timeout)
			elif DEBUG_PINGS:
				self.sdebug("server=%s, pingechocount=%s" % (self.server, self.pingechocount), pingcount_sent)
		callLater(timeout, check_ping_echo_received, self.pingcount)
		if DEBUG_PINGS:
			self.sdebug("server=%s, pingechocount=%s, pingcount=%s" % (self.server, self.pingechocount, self.pingcount), timeout)

	def send_open_file(self, filename, mode):
		self.send(OPEN_FILE, [filename, mode])

	def send_tunnel_ports(self, samba_port, ipp_port):
		self.send(SET_TUNNEL_PORTS, [samba_port, ipp_port])

	def send_xmodmap(self, xmodmap_key_data, xmodmap_mod_data, xkbmap_print, xkbmap_query, xkbmap_layout, xkbmap_variant):
		enc_key_data = ""
		if xmodmap_key_data:
			enc_key_data = self.binencode(xmodmap_key_data)
		packet = [enc_key_data]
		if self.remote_app_version>=[0,12,12]:
			enc_mod_data = ""
			if xmodmap_key_data:
				enc_mod_data = self.binencode(xmodmap_mod_data)
			packet.append(enc_mod_data)
		if self.remote_app_version>=[0,12,16]:
			packet.append(self.binencode(xkbmap_print))
			packet.append(self.binencode(xkbmap_query))
			packet.append(xkbmap_layout)
			packet.append(xkbmap_variant)
		self.send(SET_XMODMAP, packet)

	def send_mount_point(self, protocol, namespace, host, port, path, fs_type, auth_mode, username, password, comment, options):
		self.send(ADD_MOUNT_POINT, [protocol, namespace, host, port, path, fs_type, auth_mode, username, password, comment, options])


	def request_session_sound(self, ID, start_or_stop, in_or_out, monitor, codec, codec_options):
		""" Request the other end to stop/start session sound """
		args = [ID, start_or_stop, in_or_out, monitor]
		if start_or_stop:
			args += [codec, codec_options]
		self.send(REQUEST_SESSION_SOUND, args)

	def receive_session_sound(self, ID, start_or_stop, in_or_out, monitor, port=None, codec=None, codec_options={}):
		""" Tell the other end it can start/stop receiving/sending sound for the given session ID """
		args = [ID, start_or_stop, in_or_out, monitor, port]
		if start_or_stop:
			args += [codec, codec_options]
		self.send(RECEIVE_SESSION_SOUND, args)

	##Composite:
	def send_users(self, users=None):
		if not users:
			users = self.server.get_active_users()
		for user in users:
			self.send_user(user)

	def send_sessions(self, sessions=None):
		if not sessions:
			sessions = self.server.get_live_sessions()
		self.sdebug(None, csv_list(sessions))
		for session in sessions:
			#self.sdebug("sending %s: ID=%s" % (session, session.ID), csv_list(sessions))
			self.send_session(session)


	##########################################################
	#Default command handlers:
	def do_logout(self, *args):
		self.slog(None, *args)
		self.drop_connection(message="Logout")

	def do_sync_all(self, *args):
		self.do_sync_base(*args)
		self.send_users()
		self.send_sessions()
		self.send_sync_end()
		return False

	def do_sync_base(self, *args):
		self.send_version()
		self.send_salt()

	def do_authentication_failed(self, *args):
		self.serror(None, visible_command(str(args)))

	def do_ping(self, *args):
		if not args:
			self.send_ok(PING)
			return
		""" Newer versions include a ping count, send it back """
		count = int(args[0])
		self.send(PINGECHO, [count])

	def do_pingecho(self, count, *args):
		if DEBUG_PINGS:
			self.sdebug(None, count, *args)
		self.pingechocount = int(count)

	def do_help(self, *args):
		self.slog(None, visible_command(str(args)))
		self.send_help()

	def do_encrypted_command(self, arg):
		if not self.local_key:
			self.serror("local encryption key is not set! cannot decrypt received message!", visible_command(str(arg)))
			return
		cmd = decrypt_salted_hex(self.local_key, self.local_salt, arg)
		self.slog("decrypted command=%s" % cmd, visible_command(str(arg)))
		return self.handle_command(cmd)

	def make_user_from_args(self, *args):
		user = User()
		username = args[0]
		if username.find("/")>0:
			user.username = username.split("/")[0]
			user.auth_username = username.split("/")[1]
		else:
			user.username = username
			user.auth_username = username
		user.remote_username = args[1]
		user.name = args[2]
		user.uuid = args[3]
		user.host = args[4]
		user.crypto_modulus = args[5]
		user.crypto_public_exponent = args[6]
		user.locale = args[9]
		user.supports_xpra = get_bool(args[10])
		user.supports_nx = get_bool(args[11])
		user.supports_vnc = get_bool(args[12])
		user.preferred_session_type = args[13]
		user.line_speed = int(args[14])
		user.tunnel_fs = get_bool(args[15])
		user.tunnel_sink = get_bool(args[16])
		user.tunnel_printer = get_bool(args[17])
		user.tunnel_source = get_bool(args[18])
		user.tunneled = get_bool(args[19])
		user.supports_rdp = get_bool(args[20])
		if len(args)>21 and args[21]:
			try:
				user.ssh_pub_keydata = bindecode(args[21])
			except Exception, e:
				self.serror("error parsing public key data=%s : %s" % (visible_command(args[21]), e), *args)
		if len(args)>22:
			user.xpra_x11 = get_bool(args[22])
		if len(args)>23:
			user.vnc_x11 = get_bool(args[23])
		if len(args)>24:
			user.platform = args[24]
		if len(args)>25:
			try:
				user.xkbmap_print = bindecode(args[25])
			except Exception, e:
				self.serror("error parsing xkbmap print data=%s : %s" % (visible_command(args[25]), e), *args)
		if len(args)>26:
			enc = parse_csv(args[26])
			if len(enc)>0:
				user.binary_encodings = enc
		if len(args)>28:
			user.gstaudio_codecs = parse_csv(args[27])
			user.gstvideo_codecs = parse_csv(args[28])
		if len(args)>29 and args[29] in [OPEN_LOCALLY, OPEN_NEW_SESSION, OPEN_EXISTING_SESSION]:
			user.open_urls = args[29]
		else:
			user.open_urls = OPEN_NEW_SESSION
		if len(args)>30 and args[30] in [OPEN_NEW_SESSION, OPEN_EXISTING_SESSION]:
			user.open_files = args[30]
		else:
			user.open_files = OPEN_NEW_SESSION
		if len(args)>31:
			user.client_type = args[31]
		user.supports_virtualbox = len(args)>32 and get_bool(args[32])
		if len(args)>33:
			try:
				user.xkbmap_query = bindecode(args[33])
			except Exception, e:
				self.serror("error parsing xkbmap query data=%s : %s" % (visible_command(args[33]), e), *args)
		if len(args)>34:
			self.xkbmap_layout = args[34]
		if len(args)>35:
			self.xkbmap_variant = args[35]
		return user

	def do_add_user(self, *args):
		# This handler is overriden on both client and servers (just here as an example)
		user = self.make_user_from_args(*args)
		return self.server.add_user(user)

	def do_remove_user(self, uuid):
		return self.server.remove_user_by_uuid(uuid)

	def do_add_client_session(self, *args):
		from winswitch.objects.client_session import ClientSession
		session = ClientSession()
		session.ID = args[0]
		session.display = args[1]
		session.status = args[2]
		session.name = args[3]
		session.host = args[4]
		session.port = int(args[5])
		session.owner= args[6]
		session.actor = args[7]
		session.command = args[8]
		session.command_uuid = args[9]
		session.session_type = args[10]
		session.start_time = int(args[11].split(".")[0])		#remove decimals if any
		session.requires_tunnel = get_bool(args[12])
		session.preload = get_bool(args[13])
		session.full_desktop = get_bool(args[14])
		session.screen_size = args[15]
		session.shared_desktop = get_bool(args[16])
		session.read_only = get_bool(args[17])
		session.shadowed_display = args[18]
		session.options = self.parse_opts(args[19])
		session.can_export_sound = get_bool(args[20])
		session.can_inport_sound = get_bool(args[21])
		session.can_clone_sound = get_bool(args[22])
		session.uses_sound_out = get_bool(args[23])
		session.uses_sound_in = get_bool(args[24])
		if len(args)>=26:
			session.user = args[25]
		session.validate()
		return self.server.add_session(session)

	def do_remove_session(self, session_id):
		session = self.server.remove_session_by_ID(session_id)
		return	session is not None

	def do_set_session_status(self, session_id, status, actor, preload, screen_size, *args):
		session = self.server.get_session(session_id)
		if not session:
			return False
		session.set_status(status)
		session.actor = actor
		session.preload = get_bool(preload)
		session.screen_size = screen_size
		self.server.touch()
		return True

	def do_set_user_icon(self, uid, enc_data):
		#TODO: ignore self!
		user = self.server.get_user_by_uuid(uid)
		if not user:
			self.serror("user not found!", uid, "..")
			return	False
		data = bindecode(enc_data)
		user.set_avatar_icon_data(data)
		self.sdebug("user=%s" % user, uid, "..")
		self.server.touch()
		return True

	def do_set_session_icon(self, session_id, enc_data, *args):
		session = self.server.get_session(session_id)
		if not session:
			return False
		data = bindecode(enc_data)
		large = args and len(args)>0 and get_bool(args[0])
		if large:
			session.set_screen_capture_icon_data(data)
		else:
			session.set_window_icon_data(data)
		session.touch()
		self.server.touch()
		return True

	def do_add_server_command(self, *args):
		command = self.make_server_command_from_args(*args)
		added = self.server.add_command(command)
		return added

	def make_server_command_from_args(self, *args):
		command = ServerCommand(args[0], args[1], args[2], args[3], args[4])
		command.menu_category = args[5]
		command.type = args[6]
		command.uses_sound_in = get_bool(args[7])
		command.uses_sound_out = get_bool(args[8])
		command.uses_video = get_bool(args[9])
		if len(args)>10:
			command.icon_names = parse_csv(args[10])
		return command

	def do_message(self, *args):
		self.slog("not handled!", *args)
		return False

	def do_set_salt(self, remote_salt):
		self.slog("previous salt=%s" % self.remote_salt, remote_salt)
		self.remote_salt = remote_salt
		return False

	def do_set_remote_key(self, mod, pub_e, proof):
		self.remote_key = None
		key = self.verify_key(mod, pub_e, proof)
		if key:
			self.server.set_key(key)
			self.remote_key = key
			self.slog("server.key=%s" % str(self.server.get_key()), visible_command(mod), pub_e, visible_command(proof))
		else:
			self.serror("identity verification failed!", mod, pub_e, proof)
			self.drop_connection(message="identity verification failed!")
		return False

	def verify_key(self, mod, pub_e, proof):
		modulus = long(mod)
		public_exponent = long(pub_e)
		key = recreate_key(modulus, public_exponent)
		if not key:
			return	None
		check = verify_long(key, self.local_salt, proof)
		if not check:
			return	None
		return	key;


	def check_parse_version(self, proto_ver, app_ver):
		self.sdebug(None, proto_ver, app_ver)
		remote_version = int(proto_ver)
		if remote_version != PROTOCOL_VERSION:
			self.serror("Error: line protocol version mismatch: %d vs %d" % (remote_version, PROTOCOL_VERSION), proto_ver, app_ver)
			self.drop_connection(message="Version mismatch")
			return
		self.remote_app_version = parse_version_string(app_ver)
		check_remote_version(self.remote_app_version)
		return self.remote_app_version

	def do_version(self, proto_ver, app_ver, info):
		self.check_parse_version(proto_ver, app_ver)
		return False
