#!/usr/bin/python3 # file: statusd.py # date: 26.07.2019 # email: berhsi@web.de # server, who listen for ipv4 connections at port 10001. import socket import os import logging from time import time, ctime, sleep from sys import exit, byteorder def read_config(CONFIGFILE, CONFIG): ''' reads the given config file and sets the values are founded. param 1: string param 2: dictionary return: boolean ''' logging.debug('Read configfile {}'.format(CONFIGFILE)) if os.access(CONFIGFILE, os.R_OK): logging.debug('Configfile is readable') with open(CONFIGFILE, 'r') as config: logging.debug('Configfile successfull read') for line in config.readlines(): if not line[0] in ('#', ';', '\n', '\r'): logging.debug('Read entry') key, value = (line.strip().split('=')) CONFIG[key.upper().strip()] = value.strip() logging.debug('Set {} to {}'.format(key.upper().strip(), value.strip())) else: logging.error('Failed to read {}'.format(CONFIGFILE)) logging.error('Using default values') return False return True def print_config(CONFIG): ''' Prints the used configuration, if loglevel ist debug. param 1: dictionary return: boolean (allways true) ''' logging.debug('Using config:') for i in CONFIG.keys(): logging.debug('{}: {}'.format(i, CONFIG[i])) return True def receive_buffer_is_valid(raw_data): ''' checks, if the received buffer from the connection is valid or not. param 1: byte return: boolean ''' data = bytes2int(raw_data) if data == 0 or data == 1: logging.debug('Argument is valid: {}'.format(raw_data)) return True else: logging.debug('Argument is not valid: {}'.format(raw_data)) return False def bytes2int(raw_data): ''' Convert a given byte value into a integer. If it is possible, it returns the integer, otherwise false. param 1: byte return: integer or false ''' bom = byteorder try: data = int.from_bytes(raw_data, bom) except Exception as e: logging.error('Unabele to convert raw data to int: {}'.format(raw_data)) return False return data def replace_entry(line, new): ''' The function becomes two strings and replaces the second part of the first string from ":" until end with the second string. Than appends a "," and returns the result. !!! Todo: needs exception handling !!! param 1: string param 2: string return: string ''' array = line.split(':') logging.debug('Replace {} with {}'.format(array[1], new)) array[1] = ''.join((new, ',')) line = ':'.join((array[0], array[1])) return line def change_status(raw_data, api): ''' Becomes the received byte and the path to API file. Grabs the content of the API with read_api() and replaces "open" and "lastchange". Write all lines back to API file. param 1: byte param 2: string return: boolean ''' edit = False logging.debug('Change status API') data = read_api(api) if data != 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') for line in data.splitlines(): if line.strip().startswith('"state":'): edit = True elif edit == True and line.strip().startswith('"open":'): line = replace_entry(line, status) elif edit == True and line.strip().startswith('"lastchange":'): line = replace_entry(line, timestamp) edit = False try: api_file.write(line) api_file.write('\n') except Exception as e: logging.error('Failed to write line to API file') logging.error('Line: {}'.format(line)) logging.error('{}'.format(e)) logging.error('Failed to open API file. Wrong permissions?') return False else: logging.error('API file is not writable. Wrong permissions?') return False logging.info('Status successfull changed to {}'.format(status)) return True return False def read_api(api): ''' Reads the API file in an buffer und returns the buffer. If anything goes wrong, it returns False - otherwise it returns the buffer. param 1: string return: string or boolean ''' logging.debug('Open API file: {}'.format(api)) if os.access(api, os.R_OK): logging.debug('API is readable') with open(api, 'r') as api_file: logging.debug('API opened successfull') try: api_data = api_file.read() logging.debug('API read successfull') except Exception as e: logging.error('Failed to read API file(): {}'.format(e)) return False return (api_data) logging.error('Failed to read API file') return False def set_values(raw_data): ''' Create a timestamp, changes the value of the given byte into a string and returns both. param 1: byte return: tuple ''' timestamp = str(time()).split('.')[0] callback = bytes2int(raw_data) if callback == 1: status = "true" else: status = "false" return (status, timestamp) def main(): ''' The main function - opens a socket und listen for connections. ''' CONFIG = { 'HOST': 'localhost', 'PORT': 10001, 'CERT': None, 'KEY' : None, 'TIMEOUT': 3.0, 'API': './api' } CONFIG_FILE = './statusd.conf' loglevel = logging.DEBUG logging.basicConfig(format='%(levelname)s: %(asctime)s: %(message)s', level=loglevel) read_config(CONFIG_FILE, CONFIG) print_config(CONFIG) with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as mySocket: logging.debug('Socket created') try: mySocket.bind((CONFIG['HOST'], int(CONFIG['PORT']))) mySocket.listen(5) logging.info('Listen on {} at Port {}'.format(CONFIG['HOST'], CONFIG['PORT'])) except Exception as e: logging.error('unable to bind and listen') logging.error('{}'.format(e)) exit() while True: try: conn, addr = mySocket.accept() logging.info('Connection from {}:{}'.format(addr[0], addr[1])) try: conn.settimeout(float(CONFIG['TIMEOUT'])) logging.debug('Connection timeout set to {}'.format(CONFIG['TIMEOUT'])) except Exception as e: logging.error('Canot set timeout to {}'.format(CONFIG['TIMEOUT'])) logging.error('Use default value: 3.0') conn.settimeout(3.0) raw_data = conn.recv(1) if receive_buffer_is_valid(raw_data) == True: if change_status(raw_data, CONFIG['API']) == True: logging.debug('Send {} back'.format(raw_data)) conn.send(raw_data) # change_status returns false: else: logging.info('Failed to change status') conn.send(b'\x03') # recive_handle returns false: else: logging.info('Inalid argument recived: {}'.format(raw_data)) logging.debug('Send {} back'.format(b'\x03')) conn.send(b'\x03') sleep(0.1) # protection against dos except KeyboardInterrupt: print('\rExit') logging.info('Exit') exit() except Exception as e: logging.error('{}'.format(e)) if __name__ == '__main__': main()