diff --git a/source/nodemcu/statusclient/certs.template b/source/nodemcu/statusclient/certs.template deleted file mode 100644 index 07679bb..0000000 --- a/source/nodemcu/statusclient/certs.template +++ /dev/null @@ -1,36 +0,0 @@ -/* - * file: certs.template - * desc: This file is part of the Krautspace Doorstatus project. It contains - * certificates for the statusclient.ino programm, that runs on a NodeMCU - * with a ESP8266 chip. - * - * Replace the comments in certificate sections with our own certificates. - */ - - -const char SERVER_CERT[] PROGMEM = R"EOF( ------BEGIN CERTIFICATE----- -/* - * lace for the public server certificate to authenticate the doorstatus - * server. - */ ------END CERTIFICATE----- -)EOF"; - -const char CLIENT_CERT[] PROGMEM = R"EOF( ------BEGIN CERTIFICATE----- -/* - * Place for the clients (this program) public certificate to authenticate - * client against the server. - */ ------END CERTIFICATE----- -)EOF"; - -const char CLIENT_KEY[] PROGMEM = R"EOF( ------BEGIN CERTIFICATE----- -/* - * Place for the clients private key file. - */ ------END CERTIFICATE----- -)EOF"; - diff --git a/source/nodemcu/statusclient/config.h b/source/nodemcu/statusclient/config.h deleted file mode 100644 index b078888..0000000 --- a/source/nodemcu/statusclient/config.h +++ /dev/null @@ -1,18 +0,0 @@ -/* - * file: config.h - */ - -/* endpoint */ -#define SERVER_URL "status.kraut.space" -#define SERVER_PORT 10001 - -/* serial interface settings */ -#define BAUD_RATE 115200 -#define DEBUG true - -/* frequence to read the pin */ -#define FREQUENCY 5000 - -/* time server settings */ -#define NTP_URL "pool.ntp.org" -#define TZ_STRING "CET-1CDT,M3.5.0,M10.5.0/3" diff --git a/source/nodemcu/statusclient/credentials.template b/source/nodemcu/statusclient/credentials.template deleted file mode 100644 index 2ba711d..0000000 --- a/source/nodemcu/statusclient/credentials.template +++ /dev/null @@ -1,17 +0,0 @@ -/* - * file: credentials.template - * desc: This file is part of the Krautspace Doorstatus project. It contains - * wifi ssid and passwords for the statusclient.ino programm, that runs on a - * NodeMCU with a ESP8266 chip. - * - * Rename this file into 'credentials.h' and adapt the values to your - * wifi conditions. - */ - -/* wifi credentials */ -#define SSID_1 "your_first__wlan_ssid" -#define PSK_1 "your_first_wlan_passwort" -#define SSID_2 "your_second_wlan_ssid" -#define PSK_2 "your_seconde_wlan_password" -#define SSID_3 "your_third_wlan_ssid" -#define PSK_3 "your_third_wlan_password" diff --git a/source/nodemcu/statusclient/statusclient.ino b/source/nodemcu/statusclient/statusclient.ino deleted file mode 100644 index 5faaa2e..0000000 --- a/source/nodemcu/statusclient/statusclient.ino +++ /dev/null @@ -1,260 +0,0 @@ -/* - * file: statusclient.ino - * desc: This file is part of the Krautspace Doorstatus project. It's the - * main file for a client, who deals with the input from a reed sensor and - * push these values to a server. The code is make to run on a NodeMCU with - * ESP8266 chip. - */ - -#include -#include -#include - -#include "config.h" -#include "certs.h" -#include "credentials.h" - -const int LED_PIN = 16; // D0 -const int REED_PIN = 5; // D1 - -typedef enum { - DOOR_CLOSED = 0, - DOOR_OPEN = 1 -} door_state; -door_state current_door_state = DOOR_CLOSED; - -BearSSL::WiFiClientSecure client; - -void init_serial() { - /* - * set baudrate and debug modus - */ - Serial.begin(BAUD_RATE); - Serial.setDebugOutput(DEBUG); - Serial.println(); - Serial.println("[Srl] Serial interface initialized"); -} - -void init_pins() { - /* - * set gpio for reed sensor and led - */ - pinMode(REED_PIN, INPUT_PULLUP); - pinMode(LED_PIN, OUTPUT); - digitalWrite(LED_PIN, HIGH); - Serial.println("[Pin] LED and REED initialized"); -} - -void init_wifi() { - /* - * Creates the ssl context. Turns wifi off and than into - * access point mode. - * TODO: is 'turn of' needed! - */ - ESP8266WiFiMulti wifi; - WiFi.mode(WIFI_OFF); - WiFi.mode(WIFI_STA); - wifi.addAP(SSID_1, PSK_1); - wifi.addAP(SSID_2, NULL); - Serial.println("[Wifi] Wifi initialized"); - wifi.run(); - if (WiFi.status() == WL_CONNECTED) { - Serial.print("[Wif] Connected to "); - Serial.println(WiFi.SSID()); - Serial.print("[Wifi] IP: "); - Serial.println(WiFi.localIP()); - set_clock(); - } else { - Serial.println("[Wifi] Error: Failed to connect"); - signal_wifi_failed(); - } -} - -door_state read_door_state() { - /* - * die initialisierung des reed-pin mit pullup bewirkt, daß am pin - * 3,3 volt anliegen. die verbindung des pins mit GND sorgt dafür, - * daß die spannung "abfließen" kann. dadurch hat der pin dann den - * status 'low'. - * geschlossene tür -> reed geschlossen -> low - * geöffnete tür -> reed offen -> high - */ - if (digitalRead(REED_PIN) == HIGH) { - return DOOR_OPEN; - } - return DOOR_CLOSED; -} - -void signal_door_changed() { - /* - * LED signal, if door is opened - */ - uint8_t count = 2; - for(uint8_t i=0; i!= count; ++i) { - digitalWrite(LED_PIN, LOW); - delay(100); - digitalWrite(LED_PIN, HIGH); - delay(100); - } -} - -void signal_send_successful() { - /* - * LED signal, if new status was send successful - */ - uint8_t count = 5; - for(uint8_t i=0; i!= count; ++i) { - digitalWrite(LED_PIN, LOW); - delay(100); - digitalWrite(LED_PIN, HIGH); - delay(100); - } -} - -void signal_clock_failed() { - /* - * LED signal, if time setting failed - */ - uint8_t count = 2; - for(uint8_t i=0; i!= count; ++i) { - digitalWrite(LED_PIN, LOW); - delay(500); - digitalWrite(LED_PIN, HIGH); - delay(500); - } - delay(2000); -} - -void signal_wifi_failed() { - /* - * LED signal, if wifi initialication was failed - */ - uint8_t count = 3; - for(uint8_t i=0; i!= count; ++i) { - digitalWrite(LED_PIN, LOW); - delay(500); - digitalWrite(LED_PIN, HIGH); - delay(500); - } - delay(2000); -} - -void signal_connect_failed() { - /* - * LED signal, if door is opened - */ - uint8_t count = 4; - for(uint8_t i=0; i!= count; ++i) { - digitalWrite(LED_PIN, LOW); - delay(500); - digitalWrite(LED_PIN, HIGH); - delay(500); - } - delay(2000); -} - -void signal_send_failed() { - /* - * LED signal, if door is opened - */ - uint8_t count = 5; - for(uint8_t i=0; i!= count; ++i) { - digitalWrite(LED_PIN, LOW); - delay(500); - digitalWrite(LED_PIN, HIGH); - delay(500); - } - delay(2000); -} - -void set_clock() { - /* - * We need time for certificate authorization - */ - configTime(TZ_STRING, NTP_URL); - - Serial.print("[Clock] Waiting for NTP time sync"); - time_t now = time(nullptr); - while (now < 8 * 3600 * 2) { - delay(500); - Serial.print("."); - now = time(nullptr); - } - Serial.println(""); - struct tm timeinfo; - gmtime_r(&now, &timeinfo); - Serial.print("[Clock] Current time: "); - Serial.println(asctime(&timeinfo)); -} - -int send_status(door_state state) { - - /* - * Inits wifi (if needed) and send the status - */ - char status[2] = ""; - - if (state == DOOR_CLOSED) { - strncpy(status, "0", 1); - } else if (state == DOOR_OPEN) { - strncpy(status, "1", 1); - } else { - return 1; - } - - BearSSL::X509List server_cert(SERVER_CERT); - BearSSL::X509List client_cert(CLIENT_CERT); - BearSSL::PrivateKey client_key(CLIENT_KEY); - client.setTrustAnchors(&server_cert); - client.setClientRSACert(&client_cert, &client_key); - Serial.println("[Ctx] SSL Context initialized"); - Serial.printf("[Send] Connect to %s:%i\n", SERVER_URL, SERVER_PORT); - client.connect(SERVER_URL, SERVER_PORT); - if (!client.connected()) { - Serial.println("[Send] Can't connect to server"); - Serial.print("[Send] SSL Error: "); - Serial.println(client.getLastSSLError()); - signal_send_failed(); - client.stop(); - return 1; - } else { - ESP.resetFreeContStack(); - uint32_t freeStackStart = ESP.getFreeContStack(); - Serial.println("[Send] Connection successful established"); - Serial.printf("[Send] Send status: %s\n", status); - client.write(status); - signal_send_successful(); - client.stop(); - } - return 0; -} - - -void setup() { - - /* - * things to do once at boot time - */ - init_serial(); - init_pins(); - init_wifi(); -} - -void loop() { - - /* - * things are running in a endless loop - */ - if (WiFi.status() != WL_CONNECTED) { - init_wifi(); - } - door_state new_door_state = read_door_state(); - if (new_door_state != current_door_state) { - Serial.printf("[Loop] Status has changed to %i\n", new_door_state); - signal_door_changed(); - if (send_status(new_door_state) == 0) { - current_door_state = new_door_state; - } - } - delay(FREQUENCY); -} diff --git a/source/server/apistatusd.conf b/source/server/apistatusd.conf index 601204d..73433e8 100644 --- a/source/server/apistatusd.conf +++ b/source/server/apistatusd.conf @@ -14,20 +14,12 @@ loglevel = debug [server] host = localhost port = 10001 -cert = ./certs/statusd-pub.pem -key = ./certs/statusd-key.pem +cert = ./certs/server-pub.pem +key = ./certs/server-key.pem [client] -cert = ./certs/statusclient-pub.pem -# possible values: true, false, may -required = true +cert = ./certs/client-pub.pem [api] api = ./api template = ./api_template - -[mastodon] -send = false -host = localhost -token = aaaaa-bbbbb-ccccc-ddddd-eeeee - diff --git a/source/server/apistatusd.py b/source/server/apistatusd.py index 9a89b4a..eff1010 100755 --- a/source/server/apistatusd.py +++ b/source/server/apistatusd.py @@ -1,26 +1,21 @@ #!/usr/bin/python3 -# file: apistatusd.py +# file: statusd.py # date: 26.07.2019 -# mail: berhsi@web.de +# email: berhsi@web.de # Status server, listening for door status updates. The IPv4 address and port # to listen on are configurable, by default localhost:10001 is used. The # connection is secured by TLS and client side authentication. -try: - import json - import logging - import os - import socket - import ssl - import sys - import requests - import threading - from time import time, localtime, strftime, sleep - import configparser -except ImportException as e: - print('Import error: {}'.format(e)) +import json +import logging +import os +import socket +import ssl +import sys +from time import time, sleep +import configparser def certs_readable(config): @@ -37,6 +32,7 @@ def certs_readable(config): return False return True + def print_config(config): ''' Logs the config with level debug. @@ -45,40 +41,8 @@ def print_config(config): for section in config.sections(): logging.debug('Section {}'.format(section)) for i in config[section]: - if i == 'token': - logging.debug(' {}: {}'.format(i, 'aaaaa-bbbbb-ccccc-ddddd-eeeee')) - 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): ''' @@ -86,58 +50,77 @@ def print_ciphers(cipherlist): param1: dictionary return: boolean ''' - logging.debug('Available ciphers') + print('Available ciphers') for i in cipherlist: + print('\n') for j in i.keys(): - if j in ('name', 'protocol'): - logging.debug('{}: {}'.format(j, i[j])) + print('{}: {}'.format(j, i[j])) + print('\n') -def print_context(ctx): - ''' - Prints the ssl settings for the given ssl context. - param1: context object - ''' - logging.debug('----------- context ----------------') - logging.debug('Minimum version ssl: {}'.format(ctx.minimum_version)) - logging.debug('Maximum version ssl: {}'.format(ctx.maximum_version)) - logging.debug('SSL options enabled: {}'.format(ctx.options)) - logging.debug('Protocol: {}'.format(ctx.protocol)) - logging.debug('Verify flags certificates: {}'.format(ctx.verify_flags)) - logging.debug('Verify mode: {}'.format(ctx.verify_mode)) - print_ciphers(ctx.get_ciphers()) - logging.debug('------------------------------------') def display_peercert(cert): ''' Displays the values of a given certificate. - param1: dictionary or none + param1: dictionary + return: boolean ''' - if cert == None: - logging.debug('Peer does not offer a certificate') - elif len(cert) == 0: - logging.debug('Peer certificate was not valid') - else: - logging.debug('Peer certificate commonName: {}'.format( - cert['subject'][5][0][1])) - logging.debug('Peer certificate serialNumber: {}'.format( - cert['serialNumber'])) - logging.debug('Peer certificate notBefore: {}'.format( - cert['notBefore'])) - logging.debug('Peer certificate notAfter: {}'.format( - cert['notAfter'])) + for i in cert.keys(): + print('{}:'.format(i)) + if i in ('subject', 'issuer'): + for j in cert[i]: + print('\t{}'.format(j)) + else: + print('\t{}'.format(cert[i])) + def receive_buffer_is_valid(raw_data): ''' Checks validity of the received buffer contents. - param 1: byte object + param 1: byte return: boolean ''' - if raw_data.decode('utf-8', 'strict') in ('0', '1'): + if raw_data in (b'\x00', b'\x01'): logging.debug('Argument is valid: {}'.format(raw_data)) return True + logging.debug('Argument is not valid: {}'.format(raw_data)) return False + +def change_status(raw_data, api): + ''' + Write the new status together with a timestamp into the Space API JSON. + param 1: byte + param 2: string + return: boolean + ''' + + logging.debug('Change status API') + # todo: use walrus operator := when migrating to python >= 3.8 + data = read_api(api) + if data is False: + return False + + status, timestamp = set_values(raw_data) + if os.access(api, os.W_OK): + logging.debug('API file is writable') + with open(api, '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)) + logging.debug('API file changed') + else: + logging.error('API file is not writable. Wrong permissions?') + return False + logging.info('Status 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 @@ -147,6 +130,7 @@ def read_api(api): return: dict or boolean ''' logging.debug('Open API file: {}'.format(api)) + # return early if the API JSON cannot be read if not os.access(api, os.R_OK): logging.error('Failed to read API file') @@ -154,138 +138,30 @@ def read_api(api): logging.debug('API is readable') with open(api, 'r') as api_file: - logging.debug('API file successfully readable opened') + logging.debug('API file successfully opened') try: api_json_data = json.load(api_file) - logging.debug('API file successfully read') + logging.debug('API file read successfull') 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): +def set_values(raw_data): ''' Create a timestamp, changes the value of the given byte into a string and returns both. - param 1: byte object - return: tuple (boolean, integer) + param 1: byte + return: tuple ''' - status = True if raw_data.decode('utf-8', 'strict') == '1' else False + status = True if raw_data == b'\x01' else False timestamp = int(str(time()).split('.')[0]) logging.debug('Set values for timestamp: {} and status: {}'.format( str(timestamp), str(status))) return (status, timestamp) -def join_path(host, api): - ''' - Becomes two parts (host and api) of the mastodon url and concanate them - param1: string - param2: string - return: string - ''' - url = '' - try: - if host[-1] == '/' and api[0] == '/': - url = ''.join((host, api[1:])) - elif host[-1] != '/' and api[0] != '/': - url = '/'.join((host, api)) - else: - url = ''.join((host, api)) - except TypeError as e: - logging.error('Can´t join path: {}'.format(e)) - return url - -class Toot(threading.Thread): - ''' - The thread to toot the status to mastodon. - ''' - def __init__(self, status, timestamp, config): - ''' - param1: boolean - param2: integer - param3: dictionary - ''' - threading.Thread.__init__(self) - self.status = status - self.config = config - self.timestamp = timestamp - self.api = '/api/v1/statuses' - self.auth = {'Authorization': ''} - self.data = {'status': ''} - self.url = '' - - def run(self): - ''' - return: boolean - ''' - timeformat = '%d.%m.%Y %H:%M Uhr' - # check if status is valid - if self.status not in (True, False): - logging.error('Invalid status to toot') - return False - # convert seconds into timestring - try: - timestring = strftime(timeformat, localtime(self.timestamp)) - except Exception as e: - logging.error('Can not convert timestamp into timestring') - return False - # set status message - if self.status == True: - self.data['status'] = 'Krautspace is open since: {}'.format(timestring) - elif self.status == False: - self.data['status'] = 'Krautspace is closed since: {}'.format(timestring) - logging.debug('Message: {}'.format(self.data['status'])) - # build mastodon api url - self.url = join_path(self.config['mastodon']['host'], self.api) - # build authentcation header - self.auth['Authorization'] = 'Bearer {}'.format( - self.config['mastodon']['token']) - # and finaly send request to mastodon - try: - logging.debug('Try to toot status') - response = requests.post(self.url, data = self.data, - headers = self.auth) - if response.status_code == 200: - logging.info('Toot successful send') - return True - else: - logging.error('Failed to toot. Response: {}'.format(response.status_code)) - except Exception as e: - logging.error('Exception occurred: {}'.format(e)) - return False def main(): ''' @@ -296,15 +172,15 @@ def main(): OP_DONT_ISERT_EMPTY_FRAGMENTS: prevention agains CBC 4 attack (cve-2011-3389) ''' - answer = '3'.encode(encoding='utf-8', errors='strict') - loglevel = logging.INFO + + loglevel = logging.WARNING formatstring = '%(asctime)s: %(levelname)s: %(message)s' logging.basicConfig(format=formatstring, level=loglevel) default_config = { 'general': { 'timeout': 3.0, - 'loglevel': 'info' + 'loglevel': 'warning' }, 'server': { 'host': 'localhost', @@ -313,21 +189,14 @@ def main(): 'key': './certs/server.key' }, 'client': { - 'cert': './certs/client.crt', - 'required': 'true' + 'cert': './certs/client.crt' }, 'api': { 'api': './api', 'template': './api_template' - }, - 'mastodon': { - 'send': 'false', - 'host': 'localhost', - 'token': 'aaaaa-bbbbb-ccccc-ddddd-eeeee' } } - logging.info('Try to read config file') - configfile = './apistatusd.conf' + configfile = './statusd.conf' config = configparser.ConfigParser() config.read_dict(default_config) if not config.read(configfile): @@ -341,7 +210,6 @@ def main(): default_config['general']['loglevel']) config.set('general', 'loglevel', default_config['general']['loglevel']) - logging.info('Set loglevel to {}'.format(config['general']['loglevel'].upper())) logger.setLevel(config['general']['loglevel'].upper()) print_config(config) @@ -351,75 +219,88 @@ def main(): logging.error('Cert check failed\nExit') sys.exit(1) - # ssl context erstellen - context = create_ssl_context(config) - if context is not None: - print_context(context) - else: sys.exit(2) + context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + context.verify_mode = ssl.CERT_REQUIRED + context.load_cert_chain(certfile=config['server']['cert'], + keyfile=config['server']['key']) + context.load_verify_locations(cafile=config['client']['cert']) + context.set_ciphers('EECDH+AESGCM') # only ciphers for tls 1.2 and 1.3 + 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_ciphers(context.get_ciphers()) - try: - # tcp socket öffnen => MySocket - with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as MySocket: - logging.debug('TCP Socket created') - MySocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - MySocket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) - keep = MySocket.getsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE) - logging.debug('Socket keepalive: {}'.format(keep)) + with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as mySocket: + logging.debug('Socket created') + mySocket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) + keep = mySocket.getsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE) + logging.debug('Socket keepalive: {}'.format(keep)) + try: + mySocket.bind((config['server']['host'], int(config['server']['port']))) + mySocket.listen(5) + except Exception as e: + logging.error('Unable to bind and listen') + logging.error('{}'.format(e)) + sys.exit(1) + logging.info('Listening on {} at Port {}'.format(config['server']['host'], + config['server']['port'])) + + while True: try: - MySocket.bind((config['server']['host'], int(config['server']['port']))) - MySocket.listen(5) - logging.info('Listening on {} at Port {}'.format(config['server']['host'], - config['server']['port'])) - except Exception as e: - logging.error('Unable to bind and listen') - logging.error('{}'.format(e)) - 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 + fromSocket, fromAddr = mySocket.accept() + logging.info('Client connected: {}:{}'.format(fromAddr[0], fromAddr[1])) try: - Connection = context.wrap_socket(ClientSocket, server_side=True) - logging.info('SSL Connection established') - 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) + fromSocket.settimeout(float(config['general']['timeout'])) + logging.debug('Connection timeout set to {}'.format( + config['general']['timeout'])) + except Exception: + logging.error('Cannot set timeout to {}'.format( + config['general']['timeout'])) + try: + conn = context.wrap_socket(fromSocket, server_side=True) + conn.settimeout(float(config['general']['timeout'])) + except socket.timeout: + logging.error('Socket timeout') except Exception as e: - logging.error('Unexpected error: {}'.format(e)) - continue - # empfangen und antworten - raw_data = Connection.recv(1) + logging.error('Connection failed: {}'.format(e)) + logging.info('Connection established') + logging.info('Peer certificate commonName: {}'.format( + conn.getpeercert()['subject'][5][0][1])) + logging.debug('Peer certificate serialNumber: {}'.format( + conn.getpeercert()['serialNumber'])) + + raw_data = conn.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') - 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 change_status(raw_data, config['api']['api']) is True: + logging.debug('Send {} back'.format(raw_data)) + conn.send(raw_data) + # change_status returns false: + else: + logging.info('Failed to change status') + if conn: + conn.send(b'\x03') + # receive_handle returns false: + else: + logging.info('Invalid argument received: {}'.format(raw_data)) + logging.debug('Send {} back'.format(b'\x03')) + if conn: + conn.send(b'\x03') + sleep(0.1) # protection against dos + except KeyboardInterrupt: + logging.info('Keyboard interrupt received') + sys.exit(1) + except Exception as e: + logging.error('{}'.format(e)) + continue + finally: + if mySocket: + logging.info('Shutdown socket') + try: + mySocket.shutdown(socket.SHUT_RDWR) + except Exception as e: + logging.error(f'Error while shutdown socket: {e}') + return 0 if __name__ == '__main__': diff --git a/source/server/apistatusd.service b/source/server/apistatusd.service index 33111bc..a76f4dc 100644 --- a/source/server/apistatusd.service +++ b/source/server/apistatusd.service @@ -4,7 +4,6 @@ After=systemd-network.service network.target [Service] Type=simple -Restart=on-failure WorkingDirectory=/opt/doorstatus/ ExecStart=/opt/doorstatus/apistatusd.py SyslogIdentifier=doorstatus diff --git a/source/server/setstatus.py b/source/server/setstatus.py index e6fccfa..f62a2e3 100755 --- a/source/server/setstatus.py +++ b/source/server/setstatus.py @@ -9,7 +9,8 @@ # krautspaces doorstatus. # client, who connects to the statusserver at port 10001 to update the -# krautspace door status. +# krautspace door status. If no status is given as argument, he reads from +# stdin until input is 0 or 1. import os import ssl @@ -57,19 +58,16 @@ class SetStatus: def check_status(self): """ - checkes, if the self.status variable is a valid value return: boolean """ - if self.status in ('0', '1'): - self.log.debug('Set status to {}'.format(self.status)) + if self.status in (0, 1): + self.log.debug('Set value to {}'.format(self.status)) + self.status = bytes([self.status]) return True - self.log.debug('{} is not a valid status'.format(self.status)) return False def set_config(self): """ - Tries to read and use the values from the configuration file. If - this failes, we still use the default values. """ self.log = logging.getLogger() # read config file @@ -91,8 +89,7 @@ class SetStatus: def check_certs(self, certs): """ - Check if certs are readable. - return: boolean + Check if certs readable. """ self.log.debug('Check certificates') for certfile in certs: @@ -114,28 +111,25 @@ class SetStatus: def create_ssl_context(self): """ - Creates SSL context - return: context object or false """ - try: - context = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH) - except Exception as e: + context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, + cafile=self.config['server']['cert']) + if not context: self.log.error('Failed to create SSL Context') return False - context.load_verify_locations(cafile=self.config['server']['cert']) - context.load_cert_chain(certfile=self.config['client']['cert'], - keyfile=self.config['client']['key']) context.set_ciphers('EECDH+AESGCM') # only ciphers for tls 1.2 and 1.3 context.options |= getattr(ssl._ssl, 'OP_NO_COMPRESSION', 0) + try: + context.load_cert_chain(certfile=self.config['client']['cert'], + keyfile=self.config['client']['key']) + except Exception as e: + self.log.error('Failed to load cert chain') + return False; self.log.debug('SSL context created') return context def create_ssl_socket(self, config, context): """ - Opens a socket and wrapes the socket into the given ssl context. - param1: dictionary - param2: ssl context - return: ssl-socket or false """ bare_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) if not bare_socket: @@ -198,6 +192,7 @@ class SetStatus: # check given status if self.check_status() is False: + self.log.error('No valid status given') exit(1) # log config if level is debug @@ -216,20 +211,18 @@ class SetStatus: if self.context is False: exit(3) - # get a ssl encrypted connection + # get connection self.connection = self.create_ssl_connection() # send status try: self.log.debug('Send new status: {}'.format(self.status)) - self.connection.send(self.status.encode(encoding='utf-8', - errors='strict')) + self.connection.send(self.status) except Exception as e: self.log.error('Error: {}'.format(e)) exit(6) try: - response = self.connection.recv(1).decode(encoding='utf-8', - errors='strict') + response = self.connection.recv(1) self.log.debug('Server returns: {}'.format(response)) if response == self.status: self.log.info('Status sucessfull updated') @@ -240,12 +233,3 @@ class SetStatus: self.log.error('Error: {}'.format(e)) exit(7) - -if __name__ == '__main__': - s = SetStatus() - if len(argv) < 2: - log.error('Usage: setstatus.py <0|1>') - exit(255) - else: - s.run(argv[1]) -