Compare commits

...

3 commits

2 changed files with 110 additions and 79 deletions

View file

@ -19,13 +19,15 @@ key = ./certs/statusd-key.pem
[client]
cert = ./certs/statusclient-pub.pem
# possible values: true, false, may
required = true
[api]
api = ./api
template = ./api_template
[mastodon]
send = true
send = false
host = localhost
token = aaaaa-bbbbb-ccccc-ddddd-eeeee

View file

@ -50,6 +50,36 @@ def print_config(config):
else:
logging.debug(' {}: {}'.format(i, config[section][i]))
def create_ssl_context(config):
'''
Creates the ssl context.
return: context object or None
'''
context = None
requirement = None
required = config['client']['required'].lower()
if required == 'false':
requirement = ssl.CERT_NONE
elif required == 'may':
requirement = ssl.CERT_OPTIONAL
else: requirement = ssl.CERT_REQUIRED
try:
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.verify_mode = requirement
context.load_cert_chain(certfile=config['server']['cert'],
keyfile=config['server']['key'])
context.load_verify_locations(cafile=config['client']['cert'])
# ensure, compression is disabled (disabled by default anyway at the moment)
context.options |= ssl.OP_NO_COMPRESSION
context.options = ssl.PROTOCOL_TLS_SERVER
context.options = ssl.OP_CIPHER_SERVER_PREFERENCE
logging.debug('SSL context created')
except Exception as e:
logging.error('Failed to create SSL context')
logging.error('Error: {}'.format(e))
return None
return context
def print_ciphers(cipherlist):
'''
Prints the list of allowed ciphers.
@ -108,38 +138,6 @@ def receive_buffer_is_valid(raw_data):
logging.debug('Argument is not valid: {}'.format(raw_data))
return False
def change_status(status, timestamp, filename):
'''
Write the new status together with a timestamp into the Space API JSON.
param 1: byte object
param 2: string
return: boolean
'''
logging.debug('Change status API')
# todo: use walrus operator := when migrating to python >= 3.8
data = read_api(filename)
if data is False:
return False
if os.access(filename, os.W_OK):
logging.debug('API file is writable')
with open(filename, 'w') as api_file:
logging.debug('API file open successfull')
data["state"]["open"] = status
data["state"]["lastchange"] = timestamp
try:
json.dump(data, api_file, indent=4)
except Exception as e:
logging.error('Failed to change API file')
logging.error('{}'.format(e))
return False
logging.debug('API file changed')
else:
logging.error('API file is not writable. Wrong permissions?')
return False
logging.info('API file successfull changed to {}'.format(status))
return True
def read_api(api):
'''
Reads the Space API JSON into a dict. Returns the dict on success and
@ -156,15 +154,47 @@ def read_api(api):
logging.debug('API is readable')
with open(api, 'r') as api_file:
logging.debug('API file successfully opened')
logging.debug('API file successfully readable opened')
try:
api_json_data = json.load(api_file)
logging.debug('API file read successfull')
logging.debug('API file successfully read')
except Exception as e:
logging.error('Failed to read API file: {}'.format(e))
return False
return api_json_data
def change_status(status, timestamp, filename):
'''
Write the new status together with a timestamp into the Space API JSON.
param 1: byte object
param 2: string
return: boolean
'''
logging.debug('Change status API')
# todo: use walrus operator := when migrating to python >= 3.8
data = read_api(filename)
if data is False:
return False
if os.access(filename, os.W_OK):
logging.debug('API file is writable')
with open(filename, 'w') as api_file:
logging.debug('API file successfull writable opened')
data["state"]["open"] = status
data["state"]["lastchange"] = timestamp
try:
json.dump(data, api_file, indent=4)
except Exception as e:
logging.error('Failed to change API file')
logging.error('{}'.format(e))
return False
logging.debug('API file changed')
else:
logging.error('API file is not writable. Wrong permissions?')
return False
logging.info('API file successfull changed to {}'.format(status))
return True
def get_status_and_time(raw_data):
'''
Create a timestamp, changes the value of the given byte into a string
@ -283,7 +313,8 @@ def main():
'key': './certs/server.key'
},
'client': {
'cert': './certs/client.crt'
'cert': './certs/client.crt',
'required': 'true'
},
'api': {
'api': './api',
@ -320,16 +351,11 @@ def main():
logging.error('Cert check failed\nExit')
sys.exit(1)
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.verify_mode = ssl.CERT_OPTIONAL
context.load_cert_chain(certfile=config['server']['cert'],
keyfile=config['server']['key'])
context.load_verify_locations(cafile=config['client']['cert'])
context.options = ssl.OP_CIPHER_SERVER_PREFERENCE
# ensure, compression is disabled (disabled by default anyway at the moment)
context.options |= ssl.OP_NO_COMPRESSION
logging.debug('SSL context created')
print_context(context)
# ssl context erstellen
context = create_ssl_context(config)
if context is not None:
print_context(context)
else: sys.exit(2)
try:
# tcp socket öffnen => MySocket
@ -347,50 +373,53 @@ def main():
except Exception as e:
logging.error('Unable to bind and listen')
logging.error('{}'.format(e))
sys.exit(1)
sys.exit(3)
# endlos auf verbindungen warten => ClientSocket
while True:
ClientSocket, ClientAddress = MySocket.accept()
logging.info('Client connected: {}:{}'.format(ClientAddress[0], ClientAddress[1]))
# die verbindung in den ssl-context verpacken => Connection
with context.wrap_socket(ClientSocket, server_side=True) as Connection:
try:
Connection = context.wrap_socket(ClientSocket, server_side=True)
logging.info('SSL Connection established')
try:
Connection.settimeout(float(config['general']['timeout']))
logging.debug('Connection timeout set to {}'.format(config['general']['timeout']))
cert = Connection.getpeercert(binary_form=False)
display_peercert(cert)
except Exception as e:
logging.error('Unexpected error: {}'.format(e))
continue
# empfangen und antworten
raw_data = Connection.recv(1)
if receive_buffer_is_valid(raw_data) is True:
status, timestamp = get_status_and_time(raw_data)
if change_status(status, timestamp, config['api']['api']) is True:
answer = raw_data
if config['mastodon']['send'].lower() == 'true':
logging.debug('Toot is set to true')
try:
toot_thread = Toot(status, timestamp, config)
toot_thread.run()
except InitException as e:
logging.error('InitException: {}'.format(e))
except Exception as ex:
logging.debug('Exception: {}'.format(ex))
else: logging.debug('Toot is set to false')
logging.debug('Send {} back'.format(raw_data))
Connection.send(answer)
Connection.close()
Connection.settimeout(float(config['general']['timeout']))
logging.debug('Connection timeout set to {}'.format(config['general']['timeout']))
cert = Connection.getpeercert(binary_form=False)
display_peercert(cert)
except Exception as e:
logging.error('Unexpected error: {}'.format(e))
continue
# empfangen und antworten
raw_data = Connection.recv(1)
if receive_buffer_is_valid(raw_data) is True:
status, timestamp = get_status_and_time(raw_data)
if change_status(status, timestamp, config['api']['api']) is True:
answer = raw_data
if config['mastodon']['send'].lower() == 'true':
logging.debug('Toot is set to true')
try:
toot_thread = Toot(status, timestamp, config)
toot_thread.run()
except InitException as e:
logging.error('InitException: {}'.format(e))
except Exception as ex:
logging.debug('Exception: {}'.format(ex))
else: logging.debug('Toot is set to false')
logging.debug('Send {} back'.format(raw_data))
Connection.send(answer)
Connection.close()
except KeyboardInterrupt:
logging.info('Keyboard interrupt received')
sys.exit(1)
except Exception as e:
logging.error('{}'.format(e))
finally:
if MySocket:
MySocket.close()
logging.debug('TCP socket closed')
sys.exit(255)
except Exception as e:
logging.error('{}'.format(e))
if MySocket:
MySocket.close()
logging.debug('TCP socket closed')
sys.exit(254)
if __name__ == '__main__':