summaryrefslogtreecommitdiff
path: root/beinc_server.py
diff options
context:
space:
mode:
Diffstat (limited to 'beinc_server.py')
-rwxr-xr-xbeinc_server.py156
1 files changed, 95 insertions, 61 deletions
diff --git a/beinc_server.py b/beinc_server.py
index 8957e73..77aa35e 100755
--- a/beinc_server.py
+++ b/beinc_server.py
@@ -2,7 +2,7 @@
2# -*- coding: utf-8 -*- 2# -*- coding: utf-8 -*-
3 3
4# Blackmore's Enhanced IRC-Notification Collection (BEINC) v4.0 4# Blackmore's Enhanced IRC-Notification Collection (BEINC) v4.0
5# Copyright (C) 2013-2020 Simeon Simeonov 5# Copyright (C) 2013-2022 Simeon Simeonov
6 6
7# This program is free software: you can redistribute it and/or modify 7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by 8# it under the terms of the GNU General Public License as published by
@@ -21,15 +21,15 @@
21import argparse 21import argparse
22import cgi 22import cgi
23import errno 23import errno
24import io
24import json 25import json
25import logging 26import logging
26import os 27import os
27import ssl 28import ssl
28import sys 29import sys
29
30from functools import wraps 30from functools import wraps
31from logging.config import fileConfig
32from http.server import BaseHTTPRequestHandler, HTTPServer 31from http.server import BaseHTTPRequestHandler, HTTPServer
32from logging.config import fileConfig
33 33
34try: 34try:
35 import notify2 as pynotify 35 import notify2 as pynotify
@@ -38,7 +38,7 @@ except ImportError:
38 38
39 39
40__author__ = 'Simeon Simeonov' 40__author__ = 'Simeon Simeonov'
41__version__ = '4.1' 41__version__ = '4.2'
42__license__ = 'GPL3' 42__license__ = 'GPL3'
43 43
44 44
@@ -71,6 +71,7 @@ def eprint(*arg, **kwargs):
71 71
72def beinc_login_required(method): 72def beinc_login_required(method):
73 """Decorator for checking login credentials""" 73 """Decorator for checking login credentials"""
74
74 @wraps(method) 75 @wraps(method)
75 def wrapper(self, data, *arg, **kwargs): 76 def wrapper(self, data, *arg, **kwargs):
76 if data.get('resource_name') is None: 77 if data.get('resource_name') is None:
@@ -84,6 +85,7 @@ def beinc_login_required(method):
84 if not instance.password_match(data.get('password')): 85 if not instance.password_match(data.get('password')):
85 raise BEINCError401('Wrong instance or password') 86 raise BEINCError401('Wrong instance or password')
86 return method(self, data, *arg, **kwargs) 87 return method(self, data, *arg, **kwargs)
88
87 return wrapper 89 return wrapper
88 90
89 91
@@ -105,19 +107,24 @@ class BEINCInstance:
105 self._queue_size = 0 # disable queueing 107 self._queue_size = 0 # disable queueing
106 if pynotify is None: 108 if pynotify is None:
107 eprint('This server does not possess pynotify capability') 109 eprint('This server does not possess pynotify capability')
108 eprint(f'Remove the instance {self._name} or define it with ' 110 eprint(
109 f'"osd_system": "none" or other ' 111 f'Remove the instance {self._name} or define it with '
110 f'available backend') 112 f'"osd_system": "none" or other '
113 f'available backend'
114 )
111 sys.exit(errno.EPERM) 115 sys.exit(errno.EPERM)
112 try: 116 try:
113 self._osd_notification = pynotify.Notification(' ') 117 self._osd_notification = pynotify.Notification(' ')
114 self._osd_notification.timeout = 1000 * int( 118 self._osd_notification.timeout = 1000 * int(
115 instance_dict.get('osd_timeout', 5)) 119 instance_dict.get('osd_timeout', 5)
120 )
116 self._osd_notification.set_category('im.received') 121 self._osd_notification.set_category('im.received')
117 self._osd_type = BEINC_OSD_TYPE_PYNOTIFY 122 self._osd_type = BEINC_OSD_TYPE_PYNOTIFY
118 except Exception as e: 123 except Exception as e:
119 eprint(f'Unable to set up a pynotify notification object ' 124 eprint(
120 f'for "{self._name}" ({e})') 125 f'Unable to set up a pynotify notification object '
126 f'for "{self._name}" ({e})'
127 )
121 sys.exit(errno.EPERM) 128 sys.exit(errno.EPERM)
122 129
123 @property 130 @property
@@ -195,6 +202,7 @@ class BEINCInstance:
195 202
196class BEINCCustomHandler(BaseHTTPRequestHandler): 203class BEINCCustomHandler(BaseHTTPRequestHandler):
197 """Custom handler""" 204 """Custom handler"""
205
198 def do_POST(self): 206 def do_POST(self):
199 """Handle POST requests""" 207 """Handle POST requests"""
200 if self.path.strip('/') not in ('beinc/push', 'beinc/pull'): 208 if self.path.strip('/') not in ('beinc/push', 'beinc/pull'):
@@ -203,14 +211,18 @@ class BEINCCustomHandler(BaseHTTPRequestHandler):
203 form = cgi.FieldStorage( 211 form = cgi.FieldStorage(
204 fp=self.rfile, 212 fp=self.rfile,
205 headers=self.headers, 213 headers=self.headers,
206 environ={'REQUEST_METHOD': 'POST', 214 environ={
207 'CONTENT_TYPE': self.headers['Content-Type']}) 215 'REQUEST_METHOD': 'POST',
216 'CONTENT_TYPE': self.headers['Content-Type'],
217 },
218 )
208 # extract all known fields 219 # extract all known fields
209 POST_data = dict( 220 POST_data = dict(
210 resource_name=form.getvalue('resource_name'), 221 resource_name=form.getvalue('resource_name'),
211 password=form.getvalue('password'), 222 password=form.getvalue('password'),
212 title=form.getvalue('title', ''), 223 title=form.getvalue('title', ''),
213 message=form.getvalue('message', '')) 224 message=form.getvalue('message', ''),
225 )
214 try: 226 try:
215 result = {} 227 result = {}
216 if self.path.strip('/') == 'beinc/push': 228 if self.path.strip('/') == 'beinc/push':
@@ -249,10 +261,11 @@ class BEINCCustomHandler(BaseHTTPRequestHandler):
249 instance = self.server.instances[data.get('resource_name')] 261 instance = self.server.instances[data.get('resource_name')]
250 try: 262 try:
251 if not instance.queueable: 263 if not instance.queueable:
252 raise BEINCError405( 264 raise BEINCError405('This instance does not support queuing')
253 'This instance does not support queuing') 265 return {
254 return {'message': 'OK. Fetched.', 266 'message': 'OK. Fetched.',
255 'data': {'messages': instance.get_queue()}} 267 'data': {'messages': instance.get_queue()},
268 }
256 except Exception as e: 269 except Exception as e:
257 self._generate_json_error(500, str(e)) 270 self._generate_json_error(500, str(e))
258 271
@@ -270,9 +283,9 @@ class BEINCCustomHandler(BaseHTTPRequestHandler):
270 self.send_header('Content-type', 'application/json; charset=utf-8') 283 self.send_header('Content-type', 'application/json; charset=utf-8')
271 self.end_headers() 284 self.end_headers()
272 msg = {'code': code, 'message': message, 'data': {}} 285 msg = {'code': code, 'message': message, 'data': {}}
273 self.wfile.write(json.dumps(msg, 286 self.wfile.write(
274 sort_keys=True, 287 json.dumps(msg, sort_keys=True, indent=4).encode('utf-8')
275 indent=4).encode('utf-8')) 288 )
276 289
277 def _render_to_JSON_response(self, context): 290 def _render_to_JSON_response(self, context):
278 """ 291 """
@@ -284,13 +297,14 @@ class BEINCCustomHandler(BaseHTTPRequestHandler):
284 self.send_response(200) 297 self.send_response(200)
285 self.send_header('Content-type', 'application/json; charset=utf-8') 298 self.send_header('Content-type', 'application/json; charset=utf-8')
286 self.end_headers() 299 self.end_headers()
287 self.wfile.write(json.dumps(response, 300 self.wfile.write(
288 sort_keys=True, 301 json.dumps(response, sort_keys=True, indent=4).encode('utf-8')
289 indent=4).encode('utf-8')) 302 )
290 303
291 304
292class BEINCNotifyServer(HTTPServer): 305class BEINCNotifyServer(HTTPServer):
293 """BEINCNotifyServer class""" 306 """BEINCNotifyServer class"""
307
294 def __init__(self, *arg, **kwargs): 308 def __init__(self, *arg, **kwargs):
295 """Default constructor""" 309 """Default constructor"""
296 super().__init__(*arg, **kwargs) 310 super().__init__(*arg, **kwargs)
@@ -319,9 +333,7 @@ class BEINCNotifyServer(HTTPServer):
319 self._instances[instance['name']] = BEINCInstance(instance) 333 self._instances[instance['name']] = BEINCInstance(instance)
320 logger.info('Instance %s added', instance['name']) 334 logger.info('Instance %s added', instance['name'])
321 except Exception as e: 335 except Exception as e:
322 eprint('Unable to create instance "{0}": {1}'.format( 336 eprint(f"Unable to create instance \"{instance['name']}\": {e}")
323 instance['name'],
324 e))
325 sys.exit(1) 337 sys.exit(1)
326 338
327 @property 339 @property
@@ -334,51 +346,66 @@ class BEINCNotifyServer(HTTPServer):
334 346
335if __name__ == '__main__': 347if __name__ == '__main__':
336 parser = argparse.ArgumentParser( 348 parser = argparse.ArgumentParser(
337 description='The following options are available') 349 description='The following options are available'
350 )
338 parser.add_argument( 351 parser.add_argument(
339 '-H', '--hostname', 352 '-H',
353 '--hostname',
340 metavar='HOSTNAME', 354 metavar='HOSTNAME',
341 type=str, 355 type=str,
342 dest='hostname', 356 dest='hostname',
343 default='127.0.0.1', 357 default='127.0.0.1',
344 help='BEINC server IP / hostname (default: 127.0.0.1)') 358 help='BEINC server IP / hostname (default: 127.0.0.1)',
359 )
345 parser.add_argument( 360 parser.add_argument(
346 '-L', '--logger-name', 361 '-L',
362 '--logger-name',
347 metavar='NAME', 363 metavar='NAME',
348 type=str, 364 type=str,
349 dest='logger_name', 365 dest='logger_name',
350 default='', 366 default='',
351 help="BEINC logger name (default: 'beinc')") 367 help="BEINC logger name (default: 'beinc')",
368 )
352 parser.add_argument( 369 parser.add_argument(
353 '-l', '--logger-config', 370 '-l',
371 '--logger-config',
354 metavar='CONFIG', 372 metavar='CONFIG',
355 type=str, 373 type=str,
356 dest='logger_config', 374 dest='logger_config',
357 default=os.path.expanduser('~/.beinc_server_logger.ini'), 375 default=os.path.expanduser('~/.beinc_server_logger.ini'),
358 help=('BEINC logger config (.ini) ' 376 help=(
359 '(default: ~/.beinc_server_logger.ini)')) 377 'BEINC logger config (.ini) '
378 '(default: ~/.beinc_server_logger.ini)'
379 ),
380 )
360 parser.add_argument( 381 parser.add_argument(
361 '-p', '--port', 382 '-p',
383 '--port',
362 metavar='PORT', 384 metavar='PORT',
363 type=int, 385 type=int,
364 dest='port', 386 dest='port',
365 default=9998, 387 default=9998,
366 help='BEINC server port (default: 9998)') 388 help='BEINC server port (default: 9998)',
389 )
367 parser.add_argument( 390 parser.add_argument(
368 '-f', '--config-file', 391 '-f',
392 '--config-file',
369 metavar='FILE', 393 metavar='FILE',
370 type=str, 394 type=str,
371 default=os.path.expanduser('~/.beinc_server.json'), 395 default=os.path.expanduser('~/.beinc_server.json'),
372 dest='config_file', 396 dest='config_file',
373 help='BEINC config file (default: ~/.beinc_server.json)') 397 help='BEINC config file (default: ~/.beinc_server.json)',
398 )
374 parser.add_argument( 399 parser.add_argument(
375 '-v', '--version', 400 '-v',
401 '--version',
376 action='version', 402 action='version',
377 version=f'%(prog)s {__version__}', 403 version=f'%(prog)s {__version__}',
378 help='Display program-version and exit') 404 help='Display program-version and exit',
405 )
379 args = parser.parse_args() 406 args = parser.parse_args()
380 try: 407 try:
381 with open(args.config_file, 'r') as fp: 408 with io.open(args.config_file, 'r', encoding='utf-8') as fp:
382 config_dict = json.load(fp) 409 config_dict = json.load(fp)
383 except Exception as e: 410 except Exception as e:
384 eprint(f'Unable to parse {args.config_file}: {e}') 411 eprint(f'Unable to parse {args.config_file}: {e}')
@@ -390,35 +417,42 @@ if __name__ == '__main__':
390 else: 417 else:
391 logging.basicConfig( 418 logging.basicConfig(
392 format='%(asctime)s - %(levelname)s - %(message)s', 419 format='%(asctime)s - %(levelname)s - %(message)s',
393 level=logging.DEBUG) 420 level=logging.DEBUG,
421 )
394 logger = logging.getLogger('beinc') 422 logger = logging.getLogger('beinc')
395 logger.info('BEINC starting. Loading config...') 423 logger.info('BEINC starting. Loading config...')
396 if config_dict.get('config_version') != BEINC_CURRENT_CONFIG_VERSION: 424 if config_dict.get('config_version') != BEINC_CURRENT_CONFIG_VERSION:
397 eprint( 425 eprint(
398 'WARNING: The version of the config-file: {0} ({1}) ' 426 f'WARNING: The version of the config-file: {args.config_file} '
399 'does not correspond to the latest version supported ' 427 f'({config_dict.get("config_version", "Not set")}) does not '
400 'by this program ({2})\nCheck beinc_config_sample.json ' 428 'correspond to the latest version supported by this program '
401 'for the newest features!'.format( 429 f'({BEINC_CURRENT_CONFIG_VERSION})\n'
402 args.config_file, 430 'Check beinc_config_sample.json for the newest features!'
403 config_dict.get('config_version', 'Not set'), 431 )
404 BEINC_CURRENT_CONFIG_VERSION))
405 ssl_certificate = config_dict['server']['general'].get( 432 ssl_certificate = config_dict['server']['general'].get(
406 'ssl_certificate') 433 'ssl_certificate'
434 )
407 ssl_private_key = config_dict['server']['general'].get( 435 ssl_private_key = config_dict['server']['general'].get(
408 'ssl_private_key') 436 'ssl_private_key'
437 )
409 ssl_acceptable_ciphers_str = config_dict['server']['general'].get( 438 ssl_acceptable_ciphers_str = config_dict['server']['general'].get(
410 'ssl_ciphers') 439 'ssl_ciphers'
411 beinc_server = BEINCNotifyServer((args.hostname, args.port), 440 )
412 BEINCCustomHandler) 441 beinc_server = BEINCNotifyServer(
442 (args.hostname, args.port),
443 BEINCCustomHandler,
444 )
413 beinc_server.set_config(config_dict) 445 beinc_server.set_config(config_dict)
414 if ssl_certificate and ssl_private_key: 446 if ssl_certificate and ssl_private_key:
415 beinc_server.socket = ssl.wrap_socket( 447 context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
416 beinc_server.socket, 448 context.load_cert_chain(
417 keyfile=ssl_private_key, 449 certfile=ssl_certificate, keyfile=ssl_private_key
418 certfile=ssl_certificate, 450 )
419 server_side=True, 451 if ssl_acceptable_ciphers_str is not None:
420 ssl_version=ssl.PROTOCOL_TLSv1_2, 452 context.set_ciphers(ssl_acceptable_ciphers_str)
421 ciphers=ssl_acceptable_ciphers_str) 453 beinc_server.socket = context.wrap_socket(
454 beinc_server.socket, server_side=True
455 )
422 logger.info('Done!') 456 logger.info('Done!')
423 beinc_server.serve_forever() 457 beinc_server.serve_forever()
424 except KeyboardInterrupt: 458 except KeyboardInterrupt: