diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py
index 3b77214324552c6202219f91276c6a6ff0a93b86..bb46f79b2fad4759d10e456ff720f51af520212d 100644
--- a/src/common/connection_handlers.py
+++ b/src/common/connection_handlers.py
@@ -1218,33 +1218,6 @@ class ConnectionHandlersBase:
 		# keep track of sessions this connection has with other JIDs
 		self.sessions = {}
 
-	def _FeatureNegCB(self, con, stanza, session):
-		gajim.log.debug('FeatureNegCB')
-		feature = stanza.getTag(name='feature', namespace=common.xmpp.NS_FEATURE)
-		form = common.xmpp.DataForm(node=feature.getTag('x'))
-
-		if form['FORM_TYPE'] == 'urn:xmpp:ssn':
-			self.dispatch('SESSION_NEG', (stanza.getFrom(), session, form))
-		else:
-			reply = stanza.buildReply()
-			reply.setType('error')
-
-			reply.addChild(feature)
-			reply.addChild(node=common.xmpp.ErrorNode('service-unavailable', typ='cancel'))
-
-			con.send(reply)
-
-		raise common.xmpp.NodeProcessed
-
-	def _InitE2ECB(self, con, stanza, session):
-		gajim.log.debug('InitE2ECB')
-		init = stanza.getTag(name='init', namespace=common.xmpp.NS_ESESSION_INIT)
-		form = common.xmpp.DataForm(node=init.getTag('x'))
-
-		self.dispatch('SESSION_NEG', (stanza.getFrom(), session, form))
-
-		raise common.xmpp.NodeProcessed
-
 	def get_or_create_session(self, jid, thread_id):
 		'''returns an existing session between this connection and 'jid', returns a new one if none exist.'''
 
@@ -1717,10 +1690,32 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 		# check if the message is a XEP-0020 feature negotiation request
 		if msg.getTag('feature', namespace=common.xmpp.NS_FEATURE):
 			if gajim.HAVE_PYCRYPTO:
-				self._FeatureNegCB(con, msg, session)
+				feature = msg.getTag(name='feature', namespace=common.xmpp.NS_FEATURE)
+				form = common.xmpp.DataForm(node=feature.getTag('x'))
+
+				if form['FORM_TYPE'] == 'urn:xmpp:ssn':
+					session.handle_negotiation(form)
+				else:
+					reply = msg.buildReply()
+					reply.setType('error')
+
+					reply.addChild(feature)
+					err = common.xmpp.ErrorNode('service-unavailable', typ='cancel')
+					reply.addChild(node=err)
+
+					con.send(reply)
+
+				raise common.xmpp.NodeProcessed
+
 			return
+
 		if msg.getTag('init', namespace=common.xmpp.NS_ESESSION_INIT):
-			self._InitE2ECB(con, msg, session)
+			init = msg.getTag(name='init', namespace=common.xmpp.NS_ESESSION_INIT)
+			form = common.xmpp.DataForm(node=init.getTag('x'))
+
+			session.handle_negotiation(form)
+
+			raise common.xmpp.NodeProcessed
 
 		tim = msg.getTimestamp()
 		tim = helpers.datetime_tuple(tim)
diff --git a/src/gajim.py b/src/gajim.py
index ea26b4bf292d182d9d13e46053aa4e1d8b666a28..3f7de1af3183d011c2dda1dfa87e3787363205dc 100755
--- a/src/gajim.py
+++ b/src/gajim.py
@@ -246,7 +246,6 @@ import math
 import gtkgui_helpers
 import notify
 import message_control
-import negotiation
 
 from chat_control import ChatControlBase
 from chat_control import ChatControl
@@ -1752,153 +1751,6 @@ class Interface:
 		else:
 			print 'failed decrypt, unable to find a control to notify you in.'
 
-	def handle_session_negotiation(self, account, data):
-		jid, session, form = data
-
-		if form.getField('accept') and not form['accept'] in ('1', 'true'):
-			session.cancelled_negotiation()
-			return
-
-		# encrypted session states. these are described in stanza_session.py
-
-		try:
-			# bob responds
-			if form.getType() == 'form' and 'security' in form.asDict():
-				def continue_with_negotiation(*args):
-					if len(args):
-						self.dialog.destroy()
-
-					# we don't support 3-message negotiation as the responder
-					if 'dhkeys' in form.asDict():
-						session.fail_bad_negotiation('3 message negotiation not supported when responding', ('dhkeys',))
-						return
-
-					negotiated, not_acceptable, ask_user = session.verify_options_bob(form)
-
-					if ask_user:
-						def accept_nondefault_options(is_checked):
-							self.dialog.destroy()
-							negotiated.update(ask_user)
-							session.respond_e2e_bob(form, negotiated, not_acceptable)
-
-						def reject_nondefault_options():
-							self.dialog.destroy()
-							for key in ask_user.keys():
-								not_acceptable.append(key)
-							session.respond_e2e_bob(form, negotiated, not_acceptable)
-
-						self.dialog = dialogs.YesNoDialog(_('Confirm these session options'),
-							_('''The remote client wants to negotiate an session with these features:
-
-		%s
-
-		Are these options acceptable?''') % (negotiation.describe_features(ask_user)),
-								on_response_yes=accept_nondefault_options,
-								on_response_no=reject_nondefault_options)
-					else:
-						session.respond_e2e_bob(form, negotiated, not_acceptable)
-
-				def ignore_negotiation(widget):
-					self.dialog.destroy()
-					return
-
-				continue_with_negotiation()
-
-				return
-
-			# alice accepts
-			elif session.status == 'requested-e2e' and form.getType() == 'submit':
-				negotiated, not_acceptable, ask_user = session.verify_options_alice(form)
-
-				if session.sigmai:
-					def _cb(on_success):
-						negotiation.show_sas_dialog(session, jid, session.sas, on_success)
-
-					session.check_identity = _cb
-
-				if ask_user:
-					def accept_nondefault_options(is_checked):
-						dialog.destroy()
-
-						negotiated.update(ask_user)
-
-						try:
-							session.accept_e2e_alice(form, negotiated)
-						except exceptions.NegotiationError, details:
-							session.fail_bad_negotiation(details)
-
-					def reject_nondefault_options():
-						session.reject_negotiation()
-						dialog.destroy()
-
-					dialog = dialogs.YesNoDialog(_('Confirm these session options'),
-							_('The remote client selected these options:\n\n%s\n\nContinue with the session?') % (negotiation.describe_features(ask_user)),
-							on_response_yes = accept_nondefault_options,
-							on_response_no = reject_nondefault_options)
-				else:
-					try:
-						session.accept_e2e_alice(form, negotiated)
-					except exceptions.NegotiationError, details:
-						session.fail_bad_negotiation(details)
-
-				return
-			elif session.status == 'responded-e2e' and form.getType() == 'result':
-
-				def _cb(on_success):
-					negotiation.show_sas_dialog(session, jid, session.sas, on_success)
-
-				session.check_identity = _cb
-
-				try:
-					session.accept_e2e_bob(form)
-				except exceptions.NegotiationError, details:
-					session.fail_bad_negotiation(details)
-
-				return
-			elif session.status == 'identified-alice' and form.getType() == 'result':
-				def _cb(on_success):
-					negotiation.show_sas_dialog(session, jid, session.sas, on_success)
-
-				session.check_identity = _cb
-
-				try:
-					session.final_steps_alice(form)
-				except exceptions.NegotiationError, details:
-					session.fail_bad_negotiation(details)
-
-				return
-		except exceptions.Cancelled:
-			# user cancelled the negotiation
-
-			session.reject_negotiation()
-
-			return
-
-		if form.getField('terminate') and\
-		form.getField('terminate').getValue() in ('1', 'true'):
-			jid = str(jid)
-
-			session.acknowledge_termination()
-
-			conn = gajim.connections[account]
-			conn.delete_session(jid, session.thread_id)
-
-			return
-
-		# non-esession negotiation. this isn't very useful, but i'm keeping it around
-		# to test my test suite.
-		if form.getType() == 'form':
-			if not session.control:
-				resource = jid.getResource()
-				contact = gajim.contacts.get_contact(account, str(jid), resource)
-				if not contact:
-					connection = gajim.connections[account]
-					contact = gajim.contacts.create_contact(jid = jid.getStripped(), 
-							resource = resource, show = connection.get_status())
-				self.new_chat(contact, account, resource = resource, session = session)
-
-			negotiation.FeatureNegotiationWindow(account, jid, session, form)
-
 	def handle_event_privacy_lists_received(self, account, data):
 		# ('PRIVACY_LISTS_RECEIVED', account, list)
 		if not self.instances.has_key(account):
@@ -2195,7 +2047,6 @@ class Interface:
 			'UNIQUE_ROOM_ID_UNSUPPORTED': \
 				self.handle_event_unique_room_id_unsupported,
 			'UNIQUE_ROOM_ID_SUPPORTED': self.handle_event_unique_room_id_supported,
-			'SESSION_NEG': self.handle_session_negotiation,
 			'GPG_PASSWORD_REQUIRED': self.handle_event_gpg_password_required,
 			'SSL_ERROR': self.handle_event_ssl_error,
 			'FINGERPRINT_ERROR': self.handle_event_fingerprint_error,
diff --git a/src/session.py b/src/session.py
index 1df578c741b1531bb1f52ab55147f30135dca0c7..7e90d4f6fccc8123989451a61f8371cab6c52281 100644
--- a/src/session.py
+++ b/src/session.py
@@ -7,12 +7,13 @@ from common import contacts
 
 import common.xmpp
 
-import dialogs
-
 import message_control
 
 import notify
 
+import dialogs
+import negotiation
+
 class ChatControlSession(stanza_session.EncryptedStanzaSession):
 	def __init__(self, conn, jid, thread_id, type = 'chat'):
 		stanza_session.EncryptedStanzaSession.__init__(self, conn, jid, thread_id, type = 'chat')
@@ -330,3 +331,137 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
 			gajim.interface.roster.show_title() # we show the * or [n]
 		# Select contact row in roster.
 		gajim.interface.roster.select_contact(jid, self.conn.name)
+
+	# ---- ESessions stuff ---
+
+	def check_identity(self, on_success):
+		negotiation.show_sas_dialog(self, self.jid, self.sas, on_success)
+
+	def handle_negotiation(self, form):
+		if form.getField('accept') and not form['accept'] in ('1', 'true'):
+			self.cancelled_negotiation()
+			return
+
+		# encrypted session states. these are described in stanza_session.py
+
+		try:
+			# bob responds
+			if form.getType() == 'form' and 'security' in form.asDict():
+				def continue_with_negotiation(*args):
+					if len(args):
+						self.dialog.destroy()
+
+					# we don't support 3-message negotiation as the responder
+					if 'dhkeys' in form.asDict():
+						self.fail_bad_negotiation('3 message negotiation not supported when responding', ('dhkeys',))
+						return
+
+					negotiated, not_acceptable, ask_user = self.verify_options_bob(form)
+
+					if ask_user:
+						def accept_nondefault_options(is_checked):
+							self.dialog.destroy()
+							negotiated.update(ask_user)
+							self.respond_e2e_bob(form, negotiated, not_acceptable)
+
+						def reject_nondefault_options():
+							self.dialog.destroy()
+							for key in ask_user.keys():
+								not_acceptable.append(key)
+							self.respond_e2e_bob(form, negotiated, not_acceptable)
+
+						self.dialog = dialogs.YesNoDialog(_('Confirm these session options'),
+							_('''The remote client wants to negotiate an session with these features:
+
+		%s
+
+		Are these options acceptable?''') % (negotiation.describe_features(ask_user)),
+								on_response_yes=accept_nondefault_options,
+								on_response_no=reject_nondefault_options)
+					else:
+						self.respond_e2e_bob(form, negotiated, not_acceptable)
+
+				def ignore_negotiation(widget):
+					self.dialog.destroy()
+					return
+
+				continue_with_negotiation()
+
+				return
+
+			# alice accepts
+			elif self.status == 'requested-e2e' and form.getType() == 'submit':
+				negotiated, not_acceptable, ask_user = self.verify_options_alice(form)
+
+				if ask_user:
+					def accept_nondefault_options(is_checked):
+						dialog.destroy()
+
+						negotiated.update(ask_user)
+
+						try:
+							self.accept_e2e_alice(form, negotiated)
+						except exceptions.NegotiationError, details:
+							self.fail_bad_negotiation(details)
+
+					def reject_nondefault_options():
+						self.reject_negotiation()
+						dialog.destroy()
+
+					dialog = dialogs.YesNoDialog(_('Confirm these session options'),
+							_('The remote client selected these options:\n\n%s\n\nContinue with the session?') % (negotiation.describe_features(ask_user)),
+							on_response_yes = accept_nondefault_options,
+							on_response_no = reject_nondefault_options)
+				else:
+					try:
+						self.accept_e2e_alice(form, negotiated)
+					except exceptions.NegotiationError, details:
+						self.fail_bad_negotiation(details)
+
+				return
+			elif self.status == 'responded-e2e' and form.getType() == 'result':
+				try:
+					self.accept_e2e_bob(form)
+				except exceptions.NegotiationError, details:
+					self.fail_bad_negotiation(details)
+
+				return
+			elif self.status == 'identified-alice' and form.getType() == 'result':
+				try:
+					self.final_steps_alice(form)
+				except exceptions.NegotiationError, details:
+					self.fail_bad_negotiation(details)
+
+				return
+		except exceptions.Cancelled:
+			# user cancelled the negotiation
+
+			self.reject_negotiation()
+
+			return
+
+		if form.getField('terminate') and\
+		form.getField('terminate').getValue() in ('1', 'true'):
+			self.acknowledge_termination()
+
+			self.conn.delete_session(self.jid, self.thread_id)
+
+			return
+
+		# non-esession negotiation. this isn't very useful, but i'm keeping it around
+		# to test my test suite.
+		if form.getType() == 'form':
+			if not self.control:
+				jid, resource = gajim.get_room_and_nick_from_fjid(self.jid)
+
+				account = self.conn.name
+				contact = gajim.contacts.get_contact(account, self.jid, resource)
+
+				if not contact:
+					contact = gajim.contacts.create_contact(jid = jidk,
+							resource = resource, show = self.conn.get_status())
+
+				gajim.interface.new_chat(contact, account,
+					resource = resource, session = self)
+
+			negotiation.FeatureNegotiationWindow(account, self.jid, self, form)