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] [client]
cert = ./certs/statusclient-pub.pem cert = ./certs/statusclient-pub.pem
# possible values: true, false, may
required = true
[api] [api]
api = ./api api = ./api
template = ./api_template template = ./api_template
[mastodon] [mastodon]
send = true send = false
host = localhost host = localhost
token = aaaaa-bbbbb-ccccc-ddddd-eeeee token = aaaaa-bbbbb-ccccc-ddddd-eeeee

View file

@ -50,6 +50,36 @@ def print_config(config):
else: else:
logging.debug(' {}: {}'.format(i, config[section][i])) 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): def print_ciphers(cipherlist):
''' '''
Prints the list of allowed ciphers. 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)) logging.debug('Argument is not valid: {}'.format(raw_data))
return False 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): def read_api(api):
''' '''
Reads the Space API JSON into a dict. Returns the dict on success and 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') logging.debug('API is readable')
with open(api, 'r') as api_file: with open(api, 'r') as api_file:
logging.debug('API file successfully opened') logging.debug('API file successfully readable opened')
try: try:
api_json_data = json.load(api_file) api_json_data = json.load(api_file)
logging.debug('API file read successfull') logging.debug('API file successfully read')
except Exception as e: except Exception as e:
logging.error('Failed to read API file: {}'.format(e)) logging.error('Failed to read API file: {}'.format(e))
return False return False
return api_json_data 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): def get_status_and_time(raw_data):
''' '''
Create a timestamp, changes the value of the given byte into a string Create a timestamp, changes the value of the given byte into a string
@ -283,7 +313,8 @@ def main():
'key': './certs/server.key' 'key': './certs/server.key'
}, },
'client': { 'client': {
'cert': './certs/client.crt' 'cert': './certs/client.crt',
'required': 'true'
}, },
'api': { 'api': {
'api': './api', 'api': './api',
@ -320,16 +351,11 @@ def main():
logging.error('Cert check failed\nExit') logging.error('Cert check failed\nExit')
sys.exit(1) sys.exit(1)
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) # ssl context erstellen
context.verify_mode = ssl.CERT_OPTIONAL context = create_ssl_context(config)
context.load_cert_chain(certfile=config['server']['cert'], if context is not None:
keyfile=config['server']['key']) print_context(context)
context.load_verify_locations(cafile=config['client']['cert']) else: sys.exit(2)
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)
try: try:
# tcp socket öffnen => MySocket # tcp socket öffnen => MySocket
@ -347,50 +373,53 @@ def main():
except Exception as e: except Exception as e:
logging.error('Unable to bind and listen') logging.error('Unable to bind and listen')
logging.error('{}'.format(e)) logging.error('{}'.format(e))
sys.exit(1) sys.exit(3)
# endlos auf verbindungen warten => ClientSocket # endlos auf verbindungen warten => ClientSocket
while True: while True:
ClientSocket, ClientAddress = MySocket.accept() ClientSocket, ClientAddress = MySocket.accept()
logging.info('Client connected: {}:{}'.format(ClientAddress[0], ClientAddress[1])) logging.info('Client connected: {}:{}'.format(ClientAddress[0], ClientAddress[1]))
# die verbindung in den ssl-context verpacken => Connection # 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') logging.info('SSL Connection established')
try: Connection.settimeout(float(config['general']['timeout']))
Connection.settimeout(float(config['general']['timeout'])) logging.debug('Connection timeout set to {}'.format(config['general']['timeout']))
logging.debug('Connection timeout set to {}'.format(config['general']['timeout'])) cert = Connection.getpeercert(binary_form=False)
cert = Connection.getpeercert(binary_form=False) display_peercert(cert)
display_peercert(cert) except Exception as e:
except Exception as e: logging.error('Unexpected error: {}'.format(e))
logging.error('Unexpected error: {}'.format(e)) continue
continue # empfangen und antworten
# empfangen und antworten raw_data = Connection.recv(1)
raw_data = Connection.recv(1) if receive_buffer_is_valid(raw_data) is True:
if receive_buffer_is_valid(raw_data) is True: status, timestamp = get_status_and_time(raw_data)
status, timestamp = get_status_and_time(raw_data) if change_status(status, timestamp, config['api']['api']) is True:
if change_status(status, timestamp, config['api']['api']) is True: answer = raw_data
answer = raw_data if config['mastodon']['send'].lower() == 'true':
if config['mastodon']['send'].lower() == 'true': logging.debug('Toot is set to true')
logging.debug('Toot is set to true') try:
try: toot_thread = Toot(status, timestamp, config)
toot_thread = Toot(status, timestamp, config) toot_thread.run()
toot_thread.run() except InitException as e:
except InitException as e: logging.error('InitException: {}'.format(e))
logging.error('InitException: {}'.format(e)) except Exception as ex:
except Exception as ex: logging.debug('Exception: {}'.format(ex))
logging.debug('Exception: {}'.format(ex)) else: logging.debug('Toot is set to false')
else: logging.debug('Toot is set to false') logging.debug('Send {} back'.format(raw_data))
logging.debug('Send {} back'.format(raw_data)) Connection.send(answer)
Connection.send(answer) Connection.close()
Connection.close()
except KeyboardInterrupt: except KeyboardInterrupt:
logging.info('Keyboard interrupt received') logging.info('Keyboard interrupt received')
sys.exit(1)
except Exception as e:
logging.error('{}'.format(e))
finally:
if MySocket: if MySocket:
MySocket.close() MySocket.close()
logging.debug('TCP socket closed') 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__': if __name__ == '__main__':