From 0ce648a0fd2cc55c18ca9f20b8f67bbb932d3293 Mon Sep 17 00:00:00 2001
From: Simeon Simeonov
Date: Wed, 15 Apr 2015 22:34:26 +0200
Subject: beinc_poller.py v.2.0 + few bugfixes
---
beinc_poller.py | 221 +++++++++++++++++++--------
beinc_poller_xmlrpc.py | 248 ------------------------------
beinc_server.py | 398 +++++++++++++++++++++++++++++++++++++++++++++++++
beinc_server_.py | 398 -------------------------------------------------
4 files changed, 553 insertions(+), 712 deletions(-)
delete mode 100755 beinc_poller_xmlrpc.py
create mode 100755 beinc_server.py
delete mode 100755 beinc_server_.py
diff --git a/beinc_poller.py b/beinc_poller.py
index 2bdfe29..b5701ce 100755
--- a/beinc_poller.py
+++ b/beinc_poller.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-# Blackmore's Enhanced IRC-Notification Collection (BEINC) v1.0
+# Blackmore's Enhanced IRC-Notification Collection (BEINC) v2.0
# Copyright (C) 2013-2015 Simeon Simeonov
# This program is free software: you can redistribute it and/or modify
@@ -21,16 +21,13 @@
import argparse
import errno
import getpass
-import httplib
-import json
+import httplib # for Python < 2.7.9
import os
-import sched
-import socket
+import socket # for Python < 2.7.9
import ssl
import sys
import time
-import urllib
-import urllib2
+import xmlrpclib
try:
import pynotify
@@ -50,38 +47,74 @@ except ImportError as e:
__author__ = 'Simeon Simeonov'
-__version__ = '1.0'
+__version__ = '2.0'
__license__ = 'GPL3'
-class ValidHTTPSConnection(httplib.HTTPConnection):
- """
- Implements a simple CERT verification functionality
+BEINC_SSL_METHODS = {'SSLv3': ssl.PROTOCOL_SSLv3,
+ 'TLSv1': ssl.PROTOCOL_TLSv1}
+try:
+ BEINC_SSL_METHODS.update({'TLSv1_1': ssl.PROTOCOL_TLSv1_1})
+ BEINC_SSL_METHODS.update({'TLSv1_2': ssl.PROTOCOL_TLSv1_2})
+except:
+ pass
+
+
+class BEINCCustomHTTPSConnection(httplib.HTTPConnection):
"""
+ This class allows communication via SSL.
+ It is a reimplementation of httplib.HTTPSConnection and
+ allows the server certificate to be validated against CA
+ This functionality lacks in Python < 2.7.9
+ """
default_port = httplib.HTTPS_PORT
- def __init__(self, *args, **kwargs):
- httplib.HTTPConnection.__init__(self, *args, **kwargs)
+ def __init__(self, host, port=None, key_file=None, cert_file=None,
+ strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
+ source_address=None, custom_ssl_options={}):
+ httplib.HTTPConnection.__init__(self, host, port, strict, timeout,
+ source_address)
+ self.key_file = key_file
+ self.cert_file = cert_file
+ self.custom_ssl_options = custom_ssl_options
def connect(self):
+ "Connect to a host on a given (SSL) port."
sock = socket.create_connection((self.host, self.port),
self.timeout, self.source_address)
if self._tunnel_host:
self.sock = sock
self._tunnel()
self.sock = ssl.wrap_socket(sock,
- ca_certs=global_beinc_cert_file,
- cert_reqs=ssl.CERT_REQUIRED)
+ self.key_file,
+ self.cert_file,
+ **self.custom_ssl_options)
-class ValidHTTPSHandler(urllib2.HTTPSHandler):
- """
- Implements a simple CERT verification functionality
- """
+class BEINCCustomSafeTransport(xmlrpclib.Transport):
+
+ def __init__(self, use_datetime=0, custom_ssl_options={}):
+ xmlrpclib.Transport.__init__(self, use_datetime=use_datetime)
+ self.custom_ssl_options = custom_ssl_options
- def https_open(self, req):
- return self.do_open(ValidHTTPSConnection, req)
+ def make_connection(self, host):
+ if self._connection and host == self._connection[0]:
+ return self._connection[1]
+ try:
+ HTTPS = BEINCCustomHTTPSConnection
+ except AttributeError:
+ raise NotImplementedError(
+ "your version of httplib doesn't support HTTPS"
+ )
+ else:
+ chost, self._extra_headers, x509 = self.get_host_info(host)
+ self._connection = host, HTTPS(
+ chost,
+ None,
+ custom_ssl_options=self.custom_ssl_options,
+ **(x509 or {}))
+ return self._connection[1]
def display_notification(args, title, message):
@@ -141,23 +174,35 @@ def poll_notifications(scheduler, args):
args: the argparse processed command-line arguments
"""
try:
- post_values = {'password': args.password}
- data = urllib.urlencode(post_values)
- req = urllib2.Request(args.url, data)
- if args.cert: # check for cert validity
- global global_beinc_cert_file # ugly hack
- global_beinc_cert_file = args.cert
- opener = urllib2.build_opener(ValidHTTPSHandler)
- response = opener.open(req)
- else: # ... or don't
- response = urllib2.urlopen(req)
- res_code = response.code
- res_str = response.read()
- if res_code == 200 and args.debug:
- print('Server responded: OK')
- print('Body:\n{0}'.format(res_str))
- response.close()
- res_list = json.loads(res_str)
+ ssl_version = BEINC_SSL_METHODS.get(args.ssl_version,
+ ssl.PROTOCOL_SSLv23)
+ if sys.hexversion >= 0x20709f0:
+ # Python >= 2.7.9
+ context = ssl.SSLContext(ssl_version)
+ context.verify_mode = ssl.CERT_REQUIRED
+ if args.no_cert_validate:
+ context.verify_mode = ssl.CERT_NONE
+ context.check_hostname = bool(not args.disable_hostname_check)
+ if args.cert and not args.no_cert_validate:
+ context.load_verify_locations(os.path.expanduser(args.cert))
+ if args.ciphers:
+ context.set_ciphers(args.ciphers)
+ transport = xmlrpclib.SafeTransport(context=context)
+ else:
+ # Python < 2.7.9
+ ssl_options = {}
+ ssl_options['ssl_version'] = ssl_version
+ if args.cert and not args.no_cert_validate:
+ ssl_options['ca_certs'] = os.path.expanduser(args.cert)
+ if not args.no_cert_validate:
+ ssl_options['cert_reqs'] = ssl.CERT_REQUIRED
+ if args.ciphers:
+ ssl_options['ciphers'] = args.ciphers
+ transport = BEINCCustomSafeTransport(
+ custom_ssl_options=ssl_options)
+ server = xmlrpclib.ServerProxy(args.url,
+ transport=transport)
+ res_list = server.pull(args.rname, args.password)
for entry in res_list:
title = entry.get('title', '')
message = entry.get('message', '')
@@ -166,11 +211,16 @@ def poll_notifications(scheduler, args):
1,
poll_notifications,
(scheduler, args))
- except urllib2.HTTPError as e:
- sys.stderr.write('BEINC-server error ({0} - {1})\n'.format(e.code,
- e.reason))
+ except xmlrpclib.Fault as fault:
+ sys.stderr.write(
+ 'BEINC server answered with errorCode={0}: {1}\n'.format(
+ fault.faultCode,
+ fault.faultString))
+ except ssl.SSLError as e:
+ sys.stderr.write('BEINC SSL/TLS error: {0}\n'.format(e))
+ sys.exit(errno.EPERM)
except Exception as e:
- sys.stderr.write('BEINC-poller error: {0}\nTerminating...'.format(e))
+ sys.stderr.write('BEINC generic client error: {0}\n'.format(e))
sys.exit(errno.EPERM)
@@ -181,21 +231,14 @@ def main():
'url',
metavar='URL',
type=str,
- help='BEINC destination URL')
+ help='BEINC server destination URL')
parser.add_argument(
'-a', '--align',
metavar='ALIGNMENT',
type=str,
dest='alignment',
default='left',
- help='Alignment for "pyosd" (default: "left")')
- parser.add_argument(
- '-c', '--cert-file',
- metavar='FILE',
- type=str,
- dest='cert',
- default='',
- help='BEINC CA-cert to check the server-cert against (default: None)')
+ help='Alignment for "pyosd": "left" (default), "center", "right"')
parser.add_argument(
'-C', '--color',
metavar='COLOR',
@@ -204,18 +247,27 @@ def main():
default='blue',
help='Color for "pyosd" (default: "blue")')
parser.add_argument(
- '-d', '--debug',
- action='store_true',
- dest='debug',
- default=False,
- help='Run the poller-process in debug-mode')
+ '-c', '--cert-file',
+ metavar='FILE',
+ type=str,
+ dest='cert',
+ default='',
+ help='BEINC CA-cert to check the server-cert against '
+ '(default: Check disabled)')
parser.add_argument(
- '-f', '--frequency',
- metavar='SECONDS',
- type=int,
- dest='frequency',
- default=10,
- help='Polling frequency in seconds (default: 10)')
+ '--ciphers',
+ metavar='CIPHERS',
+ type=str,
+ dest='ciphers',
+ default='',
+ help='Preferred ciphers list (default: auto)')
+ if sys.hexversion >= 0x20709f0:
+ parser.add_argument(
+ '--disable-hostname-check',
+ action='store_true',
+ dest='disable_hostname_check',
+ default=False,
+ help='Do not check whether server cert matches server hostname')
parser.add_argument(
'--font',
metavar='FONT',
@@ -223,6 +275,13 @@ def main():
dest='font',
default=None,
help='Custom font for "pyosd" (default: Default font)')
+ parser.add_argument(
+ '-f', '--frequency',
+ metavar='SECONDS',
+ type=int,
+ dest='frequency',
+ default=10,
+ help='Polling frequency in seconds (default: 10)')
parser.add_argument(
'--h-offset',
metavar='OFFSET',
@@ -230,15 +289,28 @@ def main():
dest='hoffset',
default=30,
help='Horizontal offset for "pyosd" (default: 30)')
+ parser.add_argument(
+ '-n', '--resource-name',
+ metavar='NAME',
+ type=str,
+ dest='rname',
+ required=True,
+ help='The name of the BEINC-resource on the remote server')
+ parser.add_argument(
+ '--no-cert-validate',
+ action='store_true',
+ dest='no_cert_validate',
+ default=False,
+ help='Do not validate server certificate')
parser.add_argument(
'-o', '--osd-system',
metavar='SYSTEM',
type=str,
dest='osd_sys',
default='pynotify',
- help='BEINC osd-system ("pynotify" or "pyosd") (default: pynotify)')
+ help='BEINC osd-system: "pynotify" (default), "pyosd"')
parser.add_argument(
- '-P', '--password',
+ '-p', '--password',
metavar='PASSWORD[FILE]',
type=str,
dest='password',
@@ -246,12 +318,29 @@ def main():
help='BEINC taget-password / text-file containing the target password'
' (default & recommended: prompt for passwd)')
parser.add_argument(
- '-p', '--position',
+ '-P', '--position',
metavar='POSITION',
type=str,
dest='position',
default='bottom',
- help='Position for "pyosd" (default: "bottom")')
+ help='Position for "pyosd": "top", "middle", "bottom" (default)')
+ if sys.hexversion >= 0x20709f0:
+ parser.add_argument(
+ '-s', '--ssl-version',
+ metavar='VERSION',
+ type=str,
+ dest='ssl_version',
+ default='auto',
+ help='Use SSL version: "auto" (default), '
+ '"SSLv3", "TLSv1", "TLSv1_1", "TLSv1_2"')
+ else:
+ parser.add_argument(
+ '-s', '--ssl-version',
+ metavar='VERSION',
+ type=str,
+ dest='ssl_version',
+ default='auto',
+ help='Use SSL version: "auto" (default), "SSLv3", "TLSv1"')
parser.add_argument(
'-t', '--osd-timeout',
metavar='SECONDS',
diff --git a/beinc_poller_xmlrpc.py b/beinc_poller_xmlrpc.py
deleted file mode 100755
index 2efd7df..0000000
--- a/beinc_poller_xmlrpc.py
+++ /dev/null
@@ -1,248 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Blackmore's Enhanced IRC-Notification Collection (BEINC) v2.0
-# Copyright (C) 2013-2015 Simeon Simeonov
-
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-
-import argparse
-import errno
-import getpass
-import httplib # for Python < 2.7.9
-import os
-import socket # for Python < 2.7.9
-import ssl
-import sys
-import xmlrpclib
-
-
-__author__ = 'Simeon Simeonov'
-__version__ = '2.0'
-__license__ = 'GPL3'
-
-
-BEINC_SSL_METHODS = {'SSLv3': ssl.PROTOCOL_SSLv3,
- 'TLSv1': ssl.PROTOCOL_TLSv1}
-try:
- BEINC_SSL_METHODS.update({'TLSv1_1': ssl.PROTOCOL_TLSv1_1})
- BEINC_SSL_METHODS.update({'TLSv1_2': ssl.PROTOCOL_TLSv1_2})
-except:
- pass
-
-
-class BEINCCustomHTTPSConnection(httplib.HTTPConnection):
- """
- This class allows communication via SSL.
-
- It is a reimplementation of httplib.HTTPSConnection and
- allows the server certificate to be validated against CA
- This functionality lacks in Python < 2.7.9
- """
- default_port = httplib.HTTPS_PORT
-
- def __init__(self, host, port=None, key_file=None, cert_file=None,
- strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
- source_address=None, custom_ssl_options={}):
- httplib.HTTPConnection.__init__(self, host, port, strict, timeout,
- source_address)
- self.key_file = key_file
- self.cert_file = cert_file
- self.custom_ssl_options = custom_ssl_options
-
- def connect(self):
- "Connect to a host on a given (SSL) port."
- sock = socket.create_connection((self.host, self.port),
- self.timeout, self.source_address)
- if self._tunnel_host:
- self.sock = sock
- self._tunnel()
- self.sock = ssl.wrap_socket(sock,
- self.key_file,
- self.cert_file,
- **self.custom_ssl_options)
-
-
-class BEINCCustomSafeTransport(xmlrpclib.Transport):
-
- def __init__(self, use_datetime=0, custom_ssl_options={}):
- xmlrpclib.Transport.__init__(self, use_datetime=use_datetime)
- self.custom_ssl_options = custom_ssl_options
-
- def make_connection(self, host):
- if self._connection and host == self._connection[0]:
- return self._connection[1]
- try:
- HTTPS = BEINCCustomHTTPSConnection
- except AttributeError:
- raise NotImplementedError(
- "your version of httplib doesn't support HTTPS"
- )
- else:
- chost, self._extra_headers, x509 = self.get_host_info(host)
- self._connection = host, HTTPS(
- chost,
- None,
- custom_ssl_options=self.custom_ssl_options,
- **(x509 or {}))
- return self._connection[1]
-
-
-def action_execute(args):
- """
- """
- try:
- ssl_version = BEINC_SSL_METHODS.get(args.ssl_version,
- ssl.PROTOCOL_SSLv23)
- if sys.hexversion >= 0x20709f0:
- # Python >= 2.7.9
- context = ssl.SSLContext(ssl_version)
- context.verify_mode = ssl.CERT_REQUIRED
- if args.no_cert_validate:
- context.verify_mode = ssl.CERT_NONE
- context.check_hostname = bool(not args.disable_hostname_check)
- if args.cert and not args.no_cert_validate:
- context.load_verify_locations(os.path.expanduser(args.cert))
- if args.ciphers:
- context.set_ciphers(args.ciphers)
- transport = xmlrpclib.SafeTransport(context=context)
- else:
- # Python < 2.7.9
- ssl_options = {}
- ssl_options['ssl_version'] = ssl_version
- if args.cert and not args.no_cert_validate:
- ssl_options['ca_certs'] = os.path.expanduser(args.cert)
- if not args.no_cert_validate:
- ssl_options['cert_reqs'] = ssl.CERT_REQUIRED
- if args.ciphers:
- ssl_options['ciphers'] = args.ciphers
- transport = BEINCCustomSafeTransport(
- custom_ssl_options=ssl_options)
- server = xmlrpclib.ServerProxy(args.url,
- transport=transport)
- if args.pull:
- print(server.pull(args.rname, args.password))
- else:
- print(server.push(args.rname,
- args.password,
- args.title,
- args.message))
- except xmlrpclib.Fault as fault:
- sys.stderr.write(
- 'BEINC server answered with errorCode={0}: {1}\n'.format(
- fault.faultCode,
- fault.faultString))
- except ssl.SSLError as e:
- sys.stderr.write('BEINC SSL/TLS error: {0}\n'.format(e))
- sys.exit(errno.EPERM)
- except Exception as e:
- sys.stderr.write('BEINC generic client error: {0}\n'.format(e))
- sys.exit(errno.EPERM)
-
-
-def main():
- parser = argparse.ArgumentParser(
- description='The following options are available')
- parser.add_argument('url',
- metavar='URL',
- type=str,
- help='Destination URL')
- parser.add_argument('-c', '--cert-file',
- metavar='FILE',
- type=str,
- dest='cert',
- default='',
- help='BEINC CA-cert to check the server-cert against')
- parser.add_argument('-C', '--ciphers',
- metavar='CIPHERS',
- type=str,
- dest='ciphers',
- default='',
- help='Preferred ciphers list (default: auto)')
- parser.add_argument('-m', '--message',
- metavar='MESSAGE',
- type=str,
- dest='message',
- default='BEINC message',
- help='BEINC message')
- parser.add_argument('-n', '--resource-name',
- metavar='NAME',
- type=str,
- dest='rname',
- required=True,
- help='The name of the BEINC-resource on '
- 'the remote server')
- parser.add_argument('-p', '--password',
- metavar='PASSWORD',
- type=str,
- dest='password',
- default='',
- help='Password')
- if sys.hexversion >= 0x20709f0:
- parser.add_argument('-s', '--ssl-version',
- metavar='VERSION',
- type=str,
- dest='ssl_version',
- default='auto',
- help='Use SSL version: auto (default), '
- 'SSLv3, TLSv1, TLSv1_1, TLSv1_2')
- else:
- parser.add_argument('-s', '--ssl-version',
- metavar='VERSION',
- type=str,
- dest='ssl_version',
- default='auto',
- help='Use SSL version: auto (default), '
- 'SSLv3, TLSv1')
- parser.add_argument('-t', '--title',
- metavar='TITLE',
- type=str,
- dest='title',
- default='BEINC title',
- help='BEINC title')
- parser.add_argument('-v', '--version',
- action='version',
- version='%(prog)s {0}'.format(__version__),
- help='display program-version and exit')
- if sys.hexversion >= 0x20709f0:
- parser.add_argument('--disable-hostname-check',
- action='store_true',
- dest='disable_hostname_check',
- default=False,
- help='Do not check whether server cert '
- 'matches server hostname')
- parser.add_argument('--no-cert-validate',
- action='store_true',
- dest='no_cert_validate',
- default=False,
- help='Do not validate server certificate')
- parser.add_argument('--pull',
- action='store_true',
- dest='pull',
- default=False,
- help='Perform a pull operation (default: push)')
- args = parser.parse_args()
- if not args.password:
- try:
- args.password = getpass.getpass()
- except Exception as e:
- sys.stderr.write('Prompt terminated\n')
- sys.exit(errno.EACCES)
- action_execute(args)
- sys.exit(0)
-
-
-if __name__ == '__main__':
- main()
diff --git a/beinc_server.py b/beinc_server.py
new file mode 100755
index 0000000..cf4f7c7
--- /dev/null
+++ b/beinc_server.py
@@ -0,0 +1,398 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Blackmore's Enhanced IRC-Notification Collection (BEINC) v2.0
+# Copyright (C) 2013-2015 Simeon Simeonov
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+
+import argparse
+import errno
+import getpass
+import json
+import os
+import sys
+
+import OpenSSL
+
+from functools import wraps
+
+from twisted.web import xmlrpc, server
+from twisted.internet import protocol, reactor, ssl
+from twisted.python import filepath, log
+
+try:
+ import pynotify
+except ImportError as e:
+ pynotify = None
+
+try:
+ import pyosd
+ pyosd_positions = {'top': pyosd.POS_TOP,
+ 'middle': pyosd.POS_MID,
+ 'bottom': pyosd.POS_BOT}
+ pyosd_alignments = {'left': pyosd.ALIGN_LEFT,
+ 'center': pyosd.ALIGN_CENTER,
+ 'right': pyosd.ALIGN_RIGHT}
+except ImportError as e:
+ pyosd = None
+
+
+__author__ = 'Simeon Simeonov'
+__version__ = '2.0'
+__license__ = 'GPL3'
+
+
+BEINC_OSD_TYPE_NONE = 0
+BEINC_OSD_TYPE_PYNOTIFY = 1
+BEINC_OSD_TYPE_PYOSD = 2
+
+BEINC_SSL_METHODS = {'SSLv3': OpenSSL.SSL.SSLv3_METHOD,
+ 'TLSv1': OpenSSL.SSL.TLSv1_METHOD}
+try:
+ errstr = ("Warning: Current Twisted / "
+ "OpenSSL version doesn't support TLSv1.1")
+ BEINC_SSL_METHODS.update({'TLSv1_1': OpenSSL.SSL.TLSv1_1_METHOD})
+ errstr = ("Warning: Current Twisted / " +
+ "OpenSSL version doesn't support TLSv1.2")
+ BEINC_SSL_METHODS.update({'TLSv1_2': OpenSSL.SSL.TLSv1_2_METHOD})
+except:
+ sys.stderr.write(errstr + '\n')
+
+
+class BEINCInstance(object):
+ """
+ Represents a single server-instance
+ """
+
+ def __init__(self, instance_dict):
+ """
+ instance_dict: the config-dictionary node that represents this instance
+ """
+ self.__message_queue = list()
+ self.__osd_type = BEINC_OSD_TYPE_NONE
+ self.__osd_notification = None
+
+ self.__name = instance_dict.get('name')
+ self.__password = instance_dict.get('password', '')
+ self.__queue_size = int(instance_dict.get('queue_size', 3))
+ if instance_dict['osd_system'].lower() == 'pynotify':
+ self.__queue_size = 0 # disable queueing
+ if not pynotify:
+ sys.stderr.write(
+ 'This server does not possess pynotify capability\n')
+ sys.stderr.write(
+ 'Remove the instance {0}'.format(self.__name))
+ sys.stderr.write(
+ 'or define it with "osd_system": "none" '
+ 'or other available backend\n')
+ sys.exit(errno.EPERM)
+ try:
+ self.__osd_notification = pynotify.Notification(' ')
+ self.__osd_notification.set_timeout(
+ 1000 * int(instance_dict.get('osd_timeout', 5)))
+ self.__osd_notification.set_property(
+ 'app_name',
+ '{0} {1}'.format(sys.argv[0], __version__))
+ except Exception as e:
+ sys.stderr.write(
+ 'Unable to set up a '
+ 'pynotify notification object for "{0}" ({1})\n'.format(
+ self.__name,
+ e))
+ sys.exit(errno.EPERM)
+ self.__osd_type = BEINC_OSD_TYPE_PYNOTIFY
+ elif instance_dict['osd_system'].lower() == 'pyosd':
+ self.__queue_size = 0 # disable queueing
+ if not pyosd:
+ sys.stderr.write(
+ 'This server does not possess pyosd capability\n')
+ sys.stderr.write(
+ 'Remove the instance {0}'.format(self.__name))
+ sys.stderr.write(
+ 'or define it with "osd_system": "none" '
+ 'or other available backend\n')
+ sys.exit(errno.EPERM)
+ try:
+ self.__osd_notification = pyosd.osd()
+ self.__osd_notification.set_timeout(
+ int(instance_dict.get('osd_timeout', 5)))
+ pyosd_font = instance_dict.get('pyosd_font')
+ if pyosd_font:
+ self.__osd_notification.set_font(pyosd_font)
+ self.__osd_notification.set_vertical_offset(
+ instance_dict.get('pyosd_vertical_offset', 120))
+ self.__osd_notification.set_horizontal_offset(
+ instance_dict.get('pyosd_horizontal_offset', 30))
+ align_str = instance_dict.get('pyosd_align', 'left')
+ self.__osd_notification.set_align(
+ pyosd_alignments.get(align_str, pyosd.ALIGN_LEFT))
+ position_str = instance_dict.get('pyosd_position', 'bottom')
+ self.__osd_notification.set_pos(
+ pyosd_positions.get(position_str, pyosd.POS_BOT))
+ self.__osd_notification.set_colour(
+ instance_dict.get('pyosd_color', 'blue'))
+ except Exception as e:
+ sys.stderr.write(
+ 'Unable to set up a pyosd '
+ 'notification object for "{0}" ({1})\n'.format(
+ self.__name,
+ e))
+ sys.exit(errno.EPERM)
+ self.__osd_type = BEINC_OSD_TYPE_PYOSD
+
+ @property
+ def name(self):
+ """
+ name-property for the server instance (read-only)
+ """
+ return self.__name
+
+ @property
+ def queueable(self):
+ """
+ True if this instance has a queueing capability (read-only)
+ """
+ return bool(self.__queue_size)
+
+ def password_match(self, password):
+ """
+ Returns True if 'passowrd' matches the instance-password,
+ otherwise - False
+ """
+ return True if self.__password == password else False
+
+ def send_message(self, title, message):
+ """
+ Displays or enqueues the message,
+ depending on the instance's type in regard to the osd_system
+ """
+ if self.__osd_type == BEINC_OSD_TYPE_PYNOTIFY:
+ self.__send_pynotify_messaage(title, message)
+ elif self.__osd_type == BEINC_OSD_TYPE_PYOSD:
+ self.__send_pyosd_message(title, message)
+ else:
+ self.__send_message_to_queue(title, message)
+
+ def get_queue(self):
+ """
+ Reruens a json representation of the message queue
+ """
+ r_value = self.__message_queue
+ self.__message_queue = list()
+ return r_value
+
+ def __send_pynotify_messaage(self, title, message):
+ """
+ Displays pynotify message
+ """
+ self.__osd_notification.set_properties(summary=title, body=message)
+ self.__osd_notification.show()
+
+ def __send_pyosd_message(self, title, message):
+ """
+ Displays pyosd message
+ """
+ self.__osd_notification.display(title, line=0)
+ self.__osd_notification.display(message, line=1)
+
+ def __send_message_to_queue(self, title, message):
+ """
+ Enqueues the message
+ """
+ if len(self.__message_queue) >= self.__queue_size:
+ self.__message_queue.pop(0)
+ self.__message_queue.append({'title': title, 'message': message})
+
+
+def beinc_login_required(method):
+ """
+ Decorator for checking login credentials
+ """
+
+ @wraps(method)
+ def wrapper(self, resource_name, password, *args, **kwargs):
+ try:
+ instance = self.instances[resource_name]
+ except Exception as e:
+ raise xmlrpc.Fault(401,
+ 'Wrong instance or password')
+ if not instance.password_match(password):
+ raise xmlrpc.Fault(401,
+ 'Wrong instance or password')
+ return method(self, resource_name, password, *args, **kwargs)
+ return wrapper
+
+
+class XMLRPCNotifyServer(xmlrpc.XMLRPC):
+ """
+ A class representing the entire server
+ """
+
+ def __init__(self, config):
+ """
+ """
+ xmlrpc.XMLRPC.__init__(self)
+ self.__config = config
+ self.__instances = dict()
+ # initialize pynotify if the module exists and if needed
+ if pynotify:
+ for instance in self.__config['server']['instances']:
+ # check if we have at least one instance that uses pynotify
+ # before initializing it
+ if instance.get('osd_system', '').lower() == 'pynotify':
+ if not pynotify.init('BEINC Notify'):
+ sys.stderr.write('pynotify.init failed! Exiting...\n')
+ sys.exit(1)
+ break
+ try:
+ for instance in self.__config['server']['instances']:
+ self.__instances[instance['name']] = BEINCInstance(instance)
+ print('Instance "{0}" added'.format(instance['name']))
+ except Exception as e:
+ sys.stderr.write('Unable to create instance "{0}": {1}\n'.format(
+ instance['name'],
+ e))
+ sys.exit(1)
+
+ @property
+ def instances(self):
+ """
+ a property that returns the instance list (read-only)
+ """
+ return self.__instances
+
+ @beinc_login_required
+ def xmlrpc_push(self, resource_name, password, title, message):
+ """
+ Return all passed args.
+ """
+ instance = self.__instances[resource_name]
+ try:
+ instance.send_message(title, message)
+ return 'OK'
+ except Exception as e:
+ sys.stderr.write(
+ 'Unable to handle message in {0}: ({1})\n'.format(
+ instance.name,
+ e))
+ raise xmlrpc.Fault(500, 'Unable to send message')
+
+ @beinc_login_required
+ def xmlrpc_pull(self, resource_name, password):
+ """
+ Return sum of arguments.
+ """
+ instance = self.__instances[resource_name]
+ if not instance.queueable:
+ raise xmlrpc.Fault(
+ 405,
+ 'BEINC instance "{0}" does not support queuing'.format(
+ instance.name))
+ return instance.get_queue()
+
+
+def main():
+ """
+ """
+ parser = argparse.ArgumentParser(
+ description='The following options are available')
+ parser.add_argument(
+ '-d',
+ action='store_true',
+ dest='daemonize',
+ default=False,
+ help='Run the BEINC-server in the background')
+ parser.add_argument(
+ '-H', '--hostname',
+ metavar='HOSTNAME',
+ type=str,
+ dest='hostname',
+ default='127.0.0.1',
+ help='BEINC server IP / hostname (default: 127.0.0.1)')
+ parser.add_argument(
+ '-p', '--port',
+ metavar='PORT',
+ type=int,
+ dest='port',
+ default=9998,
+ help='BEINC server port (default: 9998)')
+ parser.add_argument(
+ '-f', '--config-file',
+ metavar='FILE',
+ type=str,
+ default=os.path.expanduser('~/.beinc_server.json'),
+ dest='config_file',
+ help='BEINC config file (default: ~/.beinc_server.json)')
+ parser.add_argument(
+ '-v', '--version',
+ action='version',
+ version='%(prog)s {0}'.format(__version__),
+ help='Display program-version and exit')
+ args = parser.parse_args()
+ log.startLogging(sys.stdout)
+ try:
+ with open(args.config_file, 'r') as fp:
+ config_dict = json.load(fp)
+ except Exception as e:
+ sys.stderr.write('Unable to parse {0}: {1}\n'.format(args.config_file,
+ e))
+ sys.exit(errno.EIO)
+ try:
+ if config_dict.get('config_version') != 2:
+ sys.stderr.write(
+ 'Incompatible or missing config-file version for {0}\n'.format(
+ args.config_file))
+ sys.exit(1)
+ ssl_certificate = config_dict['server']['general'].get(
+ 'ssl_certificate')
+ ssl_private_key = config_dict['server']['general'].get(
+ 'ssl_private_key')
+ ssl_method_str = config_dict['server']['general'].get(
+ 'ssl_method', 'auto')
+ ssl_acceptable_ciphers_str = config_dict['server']['general'].get(
+ 'ssl_acceptable_ciphers', 'auto')
+ beinc_server = XMLRPCNotifyServer(config_dict)
+ if ssl_certificate and ssl_private_key:
+ # SSL connection
+ cert_path = filepath.FilePath(ssl_certificate)
+ key_path = filepath.FilePath(ssl_private_key)
+ private_certificate = ssl.PrivateCertificate.loadPEM(
+ key_path.getContent() + cert_path.getContent())
+ options = private_certificate.options()
+ ssl_method = BEINC_SSL_METHODS.get(ssl_method_str)
+ if ssl_method:
+ options.method = ssl_method
+ if ssl_acceptable_ciphers_str.lower() != 'auto':
+ options.acceptableCiphers = (
+ ssl.AcceptableCiphers.fromOpenSSLCipherString(
+ ssl_acceptable_ciphers_str))
+ reactor.listenSSL(args.port,
+ server.Site(beinc_server),
+ options,
+ interface=args.hostname)
+ else:
+ reactor.listenTCP(args.port,
+ server.Site(beinc_server),
+ interface=args.hostname)
+ reactor.run()
+ except Exception as e:
+ sys.stderr.write('WebServer error: {0}\n'.format(e))
+ sys.exit(1)
+ sys.exit(0)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/beinc_server_.py b/beinc_server_.py
deleted file mode 100755
index cf4f7c7..0000000
--- a/beinc_server_.py
+++ /dev/null
@@ -1,398 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Blackmore's Enhanced IRC-Notification Collection (BEINC) v2.0
-# Copyright (C) 2013-2015 Simeon Simeonov
-
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-
-import argparse
-import errno
-import getpass
-import json
-import os
-import sys
-
-import OpenSSL
-
-from functools import wraps
-
-from twisted.web import xmlrpc, server
-from twisted.internet import protocol, reactor, ssl
-from twisted.python import filepath, log
-
-try:
- import pynotify
-except ImportError as e:
- pynotify = None
-
-try:
- import pyosd
- pyosd_positions = {'top': pyosd.POS_TOP,
- 'middle': pyosd.POS_MID,
- 'bottom': pyosd.POS_BOT}
- pyosd_alignments = {'left': pyosd.ALIGN_LEFT,
- 'center': pyosd.ALIGN_CENTER,
- 'right': pyosd.ALIGN_RIGHT}
-except ImportError as e:
- pyosd = None
-
-
-__author__ = 'Simeon Simeonov'
-__version__ = '2.0'
-__license__ = 'GPL3'
-
-
-BEINC_OSD_TYPE_NONE = 0
-BEINC_OSD_TYPE_PYNOTIFY = 1
-BEINC_OSD_TYPE_PYOSD = 2
-
-BEINC_SSL_METHODS = {'SSLv3': OpenSSL.SSL.SSLv3_METHOD,
- 'TLSv1': OpenSSL.SSL.TLSv1_METHOD}
-try:
- errstr = ("Warning: Current Twisted / "
- "OpenSSL version doesn't support TLSv1.1")
- BEINC_SSL_METHODS.update({'TLSv1_1': OpenSSL.SSL.TLSv1_1_METHOD})
- errstr = ("Warning: Current Twisted / " +
- "OpenSSL version doesn't support TLSv1.2")
- BEINC_SSL_METHODS.update({'TLSv1_2': OpenSSL.SSL.TLSv1_2_METHOD})
-except:
- sys.stderr.write(errstr + '\n')
-
-
-class BEINCInstance(object):
- """
- Represents a single server-instance
- """
-
- def __init__(self, instance_dict):
- """
- instance_dict: the config-dictionary node that represents this instance
- """
- self.__message_queue = list()
- self.__osd_type = BEINC_OSD_TYPE_NONE
- self.__osd_notification = None
-
- self.__name = instance_dict.get('name')
- self.__password = instance_dict.get('password', '')
- self.__queue_size = int(instance_dict.get('queue_size', 3))
- if instance_dict['osd_system'].lower() == 'pynotify':
- self.__queue_size = 0 # disable queueing
- if not pynotify:
- sys.stderr.write(
- 'This server does not possess pynotify capability\n')
- sys.stderr.write(
- 'Remove the instance {0}'.format(self.__name))
- sys.stderr.write(
- 'or define it with "osd_system": "none" '
- 'or other available backend\n')
- sys.exit(errno.EPERM)
- try:
- self.__osd_notification = pynotify.Notification(' ')
- self.__osd_notification.set_timeout(
- 1000 * int(instance_dict.get('osd_timeout', 5)))
- self.__osd_notification.set_property(
- 'app_name',
- '{0} {1}'.format(sys.argv[0], __version__))
- except Exception as e:
- sys.stderr.write(
- 'Unable to set up a '
- 'pynotify notification object for "{0}" ({1})\n'.format(
- self.__name,
- e))
- sys.exit(errno.EPERM)
- self.__osd_type = BEINC_OSD_TYPE_PYNOTIFY
- elif instance_dict['osd_system'].lower() == 'pyosd':
- self.__queue_size = 0 # disable queueing
- if not pyosd:
- sys.stderr.write(
- 'This server does not possess pyosd capability\n')
- sys.stderr.write(
- 'Remove the instance {0}'.format(self.__name))
- sys.stderr.write(
- 'or define it with "osd_system": "none" '
- 'or other available backend\n')
- sys.exit(errno.EPERM)
- try:
- self.__osd_notification = pyosd.osd()
- self.__osd_notification.set_timeout(
- int(instance_dict.get('osd_timeout', 5)))
- pyosd_font = instance_dict.get('pyosd_font')
- if pyosd_font:
- self.__osd_notification.set_font(pyosd_font)
- self.__osd_notification.set_vertical_offset(
- instance_dict.get('pyosd_vertical_offset', 120))
- self.__osd_notification.set_horizontal_offset(
- instance_dict.get('pyosd_horizontal_offset', 30))
- align_str = instance_dict.get('pyosd_align', 'left')
- self.__osd_notification.set_align(
- pyosd_alignments.get(align_str, pyosd.ALIGN_LEFT))
- position_str = instance_dict.get('pyosd_position', 'bottom')
- self.__osd_notification.set_pos(
- pyosd_positions.get(position_str, pyosd.POS_BOT))
- self.__osd_notification.set_colour(
- instance_dict.get('pyosd_color', 'blue'))
- except Exception as e:
- sys.stderr.write(
- 'Unable to set up a pyosd '
- 'notification object for "{0}" ({1})\n'.format(
- self.__name,
- e))
- sys.exit(errno.EPERM)
- self.__osd_type = BEINC_OSD_TYPE_PYOSD
-
- @property
- def name(self):
- """
- name-property for the server instance (read-only)
- """
- return self.__name
-
- @property
- def queueable(self):
- """
- True if this instance has a queueing capability (read-only)
- """
- return bool(self.__queue_size)
-
- def password_match(self, password):
- """
- Returns True if 'passowrd' matches the instance-password,
- otherwise - False
- """
- return True if self.__password == password else False
-
- def send_message(self, title, message):
- """
- Displays or enqueues the message,
- depending on the instance's type in regard to the osd_system
- """
- if self.__osd_type == BEINC_OSD_TYPE_PYNOTIFY:
- self.__send_pynotify_messaage(title, message)
- elif self.__osd_type == BEINC_OSD_TYPE_PYOSD:
- self.__send_pyosd_message(title, message)
- else:
- self.__send_message_to_queue(title, message)
-
- def get_queue(self):
- """
- Reruens a json representation of the message queue
- """
- r_value = self.__message_queue
- self.__message_queue = list()
- return r_value
-
- def __send_pynotify_messaage(self, title, message):
- """
- Displays pynotify message
- """
- self.__osd_notification.set_properties(summary=title, body=message)
- self.__osd_notification.show()
-
- def __send_pyosd_message(self, title, message):
- """
- Displays pyosd message
- """
- self.__osd_notification.display(title, line=0)
- self.__osd_notification.display(message, line=1)
-
- def __send_message_to_queue(self, title, message):
- """
- Enqueues the message
- """
- if len(self.__message_queue) >= self.__queue_size:
- self.__message_queue.pop(0)
- self.__message_queue.append({'title': title, 'message': message})
-
-
-def beinc_login_required(method):
- """
- Decorator for checking login credentials
- """
-
- @wraps(method)
- def wrapper(self, resource_name, password, *args, **kwargs):
- try:
- instance = self.instances[resource_name]
- except Exception as e:
- raise xmlrpc.Fault(401,
- 'Wrong instance or password')
- if not instance.password_match(password):
- raise xmlrpc.Fault(401,
- 'Wrong instance or password')
- return method(self, resource_name, password, *args, **kwargs)
- return wrapper
-
-
-class XMLRPCNotifyServer(xmlrpc.XMLRPC):
- """
- A class representing the entire server
- """
-
- def __init__(self, config):
- """
- """
- xmlrpc.XMLRPC.__init__(self)
- self.__config = config
- self.__instances = dict()
- # initialize pynotify if the module exists and if needed
- if pynotify:
- for instance in self.__config['server']['instances']:
- # check if we have at least one instance that uses pynotify
- # before initializing it
- if instance.get('osd_system', '').lower() == 'pynotify':
- if not pynotify.init('BEINC Notify'):
- sys.stderr.write('pynotify.init failed! Exiting...\n')
- sys.exit(1)
- break
- try:
- for instance in self.__config['server']['instances']:
- self.__instances[instance['name']] = BEINCInstance(instance)
- print('Instance "{0}" added'.format(instance['name']))
- except Exception as e:
- sys.stderr.write('Unable to create instance "{0}": {1}\n'.format(
- instance['name'],
- e))
- sys.exit(1)
-
- @property
- def instances(self):
- """
- a property that returns the instance list (read-only)
- """
- return self.__instances
-
- @beinc_login_required
- def xmlrpc_push(self, resource_name, password, title, message):
- """
- Return all passed args.
- """
- instance = self.__instances[resource_name]
- try:
- instance.send_message(title, message)
- return 'OK'
- except Exception as e:
- sys.stderr.write(
- 'Unable to handle message in {0}: ({1})\n'.format(
- instance.name,
- e))
- raise xmlrpc.Fault(500, 'Unable to send message')
-
- @beinc_login_required
- def xmlrpc_pull(self, resource_name, password):
- """
- Return sum of arguments.
- """
- instance = self.__instances[resource_name]
- if not instance.queueable:
- raise xmlrpc.Fault(
- 405,
- 'BEINC instance "{0}" does not support queuing'.format(
- instance.name))
- return instance.get_queue()
-
-
-def main():
- """
- """
- parser = argparse.ArgumentParser(
- description='The following options are available')
- parser.add_argument(
- '-d',
- action='store_true',
- dest='daemonize',
- default=False,
- help='Run the BEINC-server in the background')
- parser.add_argument(
- '-H', '--hostname',
- metavar='HOSTNAME',
- type=str,
- dest='hostname',
- default='127.0.0.1',
- help='BEINC server IP / hostname (default: 127.0.0.1)')
- parser.add_argument(
- '-p', '--port',
- metavar='PORT',
- type=int,
- dest='port',
- default=9998,
- help='BEINC server port (default: 9998)')
- parser.add_argument(
- '-f', '--config-file',
- metavar='FILE',
- type=str,
- default=os.path.expanduser('~/.beinc_server.json'),
- dest='config_file',
- help='BEINC config file (default: ~/.beinc_server.json)')
- parser.add_argument(
- '-v', '--version',
- action='version',
- version='%(prog)s {0}'.format(__version__),
- help='Display program-version and exit')
- args = parser.parse_args()
- log.startLogging(sys.stdout)
- try:
- with open(args.config_file, 'r') as fp:
- config_dict = json.load(fp)
- except Exception as e:
- sys.stderr.write('Unable to parse {0}: {1}\n'.format(args.config_file,
- e))
- sys.exit(errno.EIO)
- try:
- if config_dict.get('config_version') != 2:
- sys.stderr.write(
- 'Incompatible or missing config-file version for {0}\n'.format(
- args.config_file))
- sys.exit(1)
- ssl_certificate = config_dict['server']['general'].get(
- 'ssl_certificate')
- ssl_private_key = config_dict['server']['general'].get(
- 'ssl_private_key')
- ssl_method_str = config_dict['server']['general'].get(
- 'ssl_method', 'auto')
- ssl_acceptable_ciphers_str = config_dict['server']['general'].get(
- 'ssl_acceptable_ciphers', 'auto')
- beinc_server = XMLRPCNotifyServer(config_dict)
- if ssl_certificate and ssl_private_key:
- # SSL connection
- cert_path = filepath.FilePath(ssl_certificate)
- key_path = filepath.FilePath(ssl_private_key)
- private_certificate = ssl.PrivateCertificate.loadPEM(
- key_path.getContent() + cert_path.getContent())
- options = private_certificate.options()
- ssl_method = BEINC_SSL_METHODS.get(ssl_method_str)
- if ssl_method:
- options.method = ssl_method
- if ssl_acceptable_ciphers_str.lower() != 'auto':
- options.acceptableCiphers = (
- ssl.AcceptableCiphers.fromOpenSSLCipherString(
- ssl_acceptable_ciphers_str))
- reactor.listenSSL(args.port,
- server.Site(beinc_server),
- options,
- interface=args.hostname)
- else:
- reactor.listenTCP(args.port,
- server.Site(beinc_server),
- interface=args.hostname)
- reactor.run()
- except Exception as e:
- sys.stderr.write('WebServer error: {0}\n'.format(e))
- sys.exit(1)
- sys.exit(0)
-
-
-if __name__ == "__main__":
- main()
--
cgit v1.3