summaryrefslogtreecommitdiff
path: root/beinc_server_.py
diff options
context:
space:
mode:
Diffstat (limited to 'beinc_server_.py')
-rwxr-xr-xbeinc_server_.py398
1 files changed, 0 insertions, 398 deletions
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 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4# Blackmore's Enhanced IRC-Notification Collection (BEINC) v2.0
5# Copyright (C) 2013-2015 Simeon Simeonov
6
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
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20
21import argparse
22import errno
23import getpass
24import json
25import os
26import sys
27
28import OpenSSL
29
30from functools import wraps
31
32from twisted.web import xmlrpc, server
33from twisted.internet import protocol, reactor, ssl
34from twisted.python import filepath, log
35
36try:
37 import pynotify
38except ImportError as e:
39 pynotify = None
40
41try:
42 import pyosd
43 pyosd_positions = {'top': pyosd.POS_TOP,
44 'middle': pyosd.POS_MID,
45 'bottom': pyosd.POS_BOT}
46 pyosd_alignments = {'left': pyosd.ALIGN_LEFT,
47 'center': pyosd.ALIGN_CENTER,
48 'right': pyosd.ALIGN_RIGHT}
49except ImportError as e:
50 pyosd = None
51
52
53__author__ = 'Simeon Simeonov'
54__version__ = '2.0'
55__license__ = 'GPL3'
56
57
58BEINC_OSD_TYPE_NONE = 0
59BEINC_OSD_TYPE_PYNOTIFY = 1
60BEINC_OSD_TYPE_PYOSD = 2
61
62BEINC_SSL_METHODS = {'SSLv3': OpenSSL.SSL.SSLv3_METHOD,
63 'TLSv1': OpenSSL.SSL.TLSv1_METHOD}
64try:
65 errstr = ("Warning: Current Twisted / "
66 "OpenSSL version doesn't support TLSv1.1")
67 BEINC_SSL_METHODS.update({'TLSv1_1': OpenSSL.SSL.TLSv1_1_METHOD})
68 errstr = ("Warning: Current Twisted / " +
69 "OpenSSL version doesn't support TLSv1.2")
70 BEINC_SSL_METHODS.update({'TLSv1_2': OpenSSL.SSL.TLSv1_2_METHOD})
71except:
72 sys.stderr.write(errstr + '\n')
73
74
75class BEINCInstance(object):
76 """
77 Represents a single server-instance
78 """
79
80 def __init__(self, instance_dict):
81 """
82 instance_dict: the config-dictionary node that represents this instance
83 """
84 self.__message_queue = list()
85 self.__osd_type = BEINC_OSD_TYPE_NONE
86 self.__osd_notification = None
87
88 self.__name = instance_dict.get('name')
89 self.__password = instance_dict.get('password', '')
90 self.__queue_size = int(instance_dict.get('queue_size', 3))
91 if instance_dict['osd_system'].lower() == 'pynotify':
92 self.__queue_size = 0 # disable queueing
93 if not pynotify:
94 sys.stderr.write(
95 'This server does not possess pynotify capability\n')
96 sys.stderr.write(
97 'Remove the instance {0}'.format(self.__name))
98 sys.stderr.write(
99 'or define it with "osd_system": "none" '
100 'or other available backend\n')
101 sys.exit(errno.EPERM)
102 try:
103 self.__osd_notification = pynotify.Notification(' ')
104 self.__osd_notification.set_timeout(
105 1000 * int(instance_dict.get('osd_timeout', 5)))
106 self.__osd_notification.set_property(
107 'app_name',
108 '{0} {1}'.format(sys.argv[0], __version__))
109 except Exception as e:
110 sys.stderr.write(
111 'Unable to set up a '
112 'pynotify notification object for "{0}" ({1})\n'.format(
113 self.__name,
114 e))
115 sys.exit(errno.EPERM)
116 self.__osd_type = BEINC_OSD_TYPE_PYNOTIFY
117 elif instance_dict['osd_system'].lower() == 'pyosd':
118 self.__queue_size = 0 # disable queueing
119 if not pyosd:
120 sys.stderr.write(
121 'This server does not possess pyosd capability\n')
122 sys.stderr.write(
123 'Remove the instance {0}'.format(self.__name))
124 sys.stderr.write(
125 'or define it with "osd_system": "none" '
126 'or other available backend\n')
127 sys.exit(errno.EPERM)
128 try:
129 self.__osd_notification = pyosd.osd()
130 self.__osd_notification.set_timeout(
131 int(instance_dict.get('osd_timeout', 5)))
132 pyosd_font = instance_dict.get('pyosd_font')
133 if pyosd_font:
134 self.__osd_notification.set_font(pyosd_font)
135 self.__osd_notification.set_vertical_offset(
136 instance_dict.get('pyosd_vertical_offset', 120))
137 self.__osd_notification.set_horizontal_offset(
138 instance_dict.get('pyosd_horizontal_offset', 30))
139 align_str = instance_dict.get('pyosd_align', 'left')
140 self.__osd_notification.set_align(
141 pyosd_alignments.get(align_str, pyosd.ALIGN_LEFT))
142 position_str = instance_dict.get('pyosd_position', 'bottom')
143 self.__osd_notification.set_pos(
144 pyosd_positions.get(position_str, pyosd.POS_BOT))
145 self.__osd_notification.set_colour(
146 instance_dict.get('pyosd_color', 'blue'))
147 except Exception as e:
148 sys.stderr.write(
149 'Unable to set up a pyosd '
150 'notification object for "{0}" ({1})\n'.format(
151 self.__name,
152 e))
153 sys.exit(errno.EPERM)
154 self.__osd_type = BEINC_OSD_TYPE_PYOSD
155
156 @property
157 def name(self):
158 """
159 name-property for the server instance (read-only)
160 """
161 return self.__name
162
163 @property
164 def queueable(self):
165 """
166 True if this instance has a queueing capability (read-only)
167 """
168 return bool(self.__queue_size)
169
170 def password_match(self, password):
171 """
172 Returns True if 'passowrd' matches the instance-password,
173 otherwise - False
174 """
175 return True if self.__password == password else False
176
177 def send_message(self, title, message):
178 """
179 Displays or enqueues the message,
180 depending on the instance's type in regard to the osd_system
181 """
182 if self.__osd_type == BEINC_OSD_TYPE_PYNOTIFY:
183 self.__send_pynotify_messaage(title, message)
184 elif self.__osd_type == BEINC_OSD_TYPE_PYOSD:
185 self.__send_pyosd_message(title, message)
186 else:
187 self.__send_message_to_queue(title, message)
188
189 def get_queue(self):
190 """
191 Reruens a json representation of the message queue
192 """
193 r_value = self.__message_queue
194 self.__message_queue = list()
195 return r_value
196
197 def __send_pynotify_messaage(self, title, message):
198 """
199 Displays pynotify message
200 """
201 self.__osd_notification.set_properties(summary=title, body=message)
202 self.__osd_notification.show()
203
204 def __send_pyosd_message(self, title, message):
205 """
206 Displays pyosd message
207 """
208 self.__osd_notification.display(title, line=0)
209 self.__osd_notification.display(message, line=1)
210
211 def __send_message_to_queue(self, title, message):
212 """
213 Enqueues the message
214 """
215 if len(self.__message_queue) >= self.__queue_size:
216 self.__message_queue.pop(0)
217 self.__message_queue.append({'title': title, 'message': message})
218
219
220def beinc_login_required(method):
221 """
222 Decorator for checking login credentials
223 """
224
225 @wraps(method)
226 def wrapper(self, resource_name, password, *args, **kwargs):
227 try:
228 instance = self.instances[resource_name]
229 except Exception as e:
230 raise xmlrpc.Fault(401,
231 'Wrong instance or password')
232 if not instance.password_match(password):
233 raise xmlrpc.Fault(401,
234 'Wrong instance or password')
235 return method(self, resource_name, password, *args, **kwargs)
236 return wrapper
237
238
239class XMLRPCNotifyServer(xmlrpc.XMLRPC):
240 """
241 A class representing the entire server
242 """
243
244 def __init__(self, config):
245 """
246 """
247 xmlrpc.XMLRPC.__init__(self)
248 self.__config = config
249 self.__instances = dict()
250 # initialize pynotify if the module exists and if needed
251 if pynotify:
252 for instance in self.__config['server']['instances']:
253 # check if we have at least one instance that uses pynotify
254 # before initializing it
255 if instance.get('osd_system', '').lower() == 'pynotify':
256 if not pynotify.init('BEINC Notify'):
257 sys.stderr.write('pynotify.init failed! Exiting...\n')
258 sys.exit(1)
259 break
260 try:
261 for instance in self.__config['server']['instances']:
262 self.__instances[instance['name']] = BEINCInstance(instance)
263 print('Instance "{0}" added'.format(instance['name']))
264 except Exception as e:
265 sys.stderr.write('Unable to create instance "{0}": {1}\n'.format(
266 instance['name'],
267 e))
268 sys.exit(1)
269
270 @property
271 def instances(self):
272 """
273 a property that returns the instance list (read-only)
274 """
275 return self.__instances
276
277 @beinc_login_required
278 def xmlrpc_push(self, resource_name, password, title, message):
279 """
280 Return all passed args.
281 """
282 instance = self.__instances[resource_name]
283 try:
284 instance.send_message(title, message)
285 return 'OK'
286 except Exception as e:
287 sys.stderr.write(
288 'Unable to handle message in {0}: ({1})\n'.format(
289 instance.name,
290 e))
291 raise xmlrpc.Fault(500, 'Unable to send message')
292
293 @beinc_login_required
294 def xmlrpc_pull(self, resource_name, password):
295 """
296 Return sum of arguments.
297 """
298 instance = self.__instances[resource_name]
299 if not instance.queueable:
300 raise xmlrpc.Fault(
301 405,
302 'BEINC instance "{0}" does not support queuing'.format(
303 instance.name))
304 return instance.get_queue()
305
306
307def main():
308 """
309 """
310 parser = argparse.ArgumentParser(
311 description='The following options are available')
312 parser.add_argument(
313 '-d',
314 action='store_true',
315 dest='daemonize',
316 default=False,
317 help='Run the BEINC-server in the background')
318 parser.add_argument(
319 '-H', '--hostname',
320 metavar='HOSTNAME',
321 type=str,
322 dest='hostname',
323 default='127.0.0.1',
324 help='BEINC server IP / hostname (default: 127.0.0.1)')
325 parser.add_argument(
326 '-p', '--port',
327 metavar='PORT',
328 type=int,
329 dest='port',
330 default=9998,
331 help='BEINC server port (default: 9998)')
332 parser.add_argument(
333 '-f', '--config-file',
334 metavar='FILE',
335 type=str,
336 default=os.path.expanduser('~/.beinc_server.json'),
337 dest='config_file',
338 help='BEINC config file (default: ~/.beinc_server.json)')
339 parser.add_argument(
340 '-v', '--version',
341 action='version',
342 version='%(prog)s {0}'.format(__version__),
343 help='Display program-version and exit')
344 args = parser.parse_args()
345 log.startLogging(sys.stdout)
346 try:
347 with open(args.config_file, 'r') as fp:
348 config_dict = json.load(fp)
349 except Exception as e:
350 sys.stderr.write('Unable to parse {0}: {1}\n'.format(args.config_file,
351 e))
352 sys.exit(errno.EIO)
353 try:
354 if config_dict.get('config_version') != 2:
355 sys.stderr.write(
356 'Incompatible or missing config-file version for {0}\n'.format(
357 args.config_file))
358 sys.exit(1)
359 ssl_certificate = config_dict['server']['general'].get(
360 'ssl_certificate')
361 ssl_private_key = config_dict['server']['general'].get(
362 'ssl_private_key')
363 ssl_method_str = config_dict['server']['general'].get(
364 'ssl_method', 'auto')
365 ssl_acceptable_ciphers_str = config_dict['server']['general'].get(
366 'ssl_acceptable_ciphers', 'auto')
367 beinc_server = XMLRPCNotifyServer(config_dict)
368 if ssl_certificate and ssl_private_key:
369 # SSL connection
370 cert_path = filepath.FilePath(ssl_certificate)
371 key_path = filepath.FilePath(ssl_private_key)
372 private_certificate = ssl.PrivateCertificate.loadPEM(
373 key_path.getContent() + cert_path.getContent())
374 options = private_certificate.options()
375 ssl_method = BEINC_SSL_METHODS.get(ssl_method_str)
376 if ssl_method:
377 options.method = ssl_method
378 if ssl_acceptable_ciphers_str.lower() != 'auto':
379 options.acceptableCiphers = (
380 ssl.AcceptableCiphers.fromOpenSSLCipherString(
381 ssl_acceptable_ciphers_str))
382 reactor.listenSSL(args.port,
383 server.Site(beinc_server),
384 options,
385 interface=args.hostname)
386 else:
387 reactor.listenTCP(args.port,
388 server.Site(beinc_server),
389 interface=args.hostname)
390 reactor.run()
391 except Exception as e:
392 sys.stderr.write('WebServer error: {0}\n'.format(e))
393 sys.exit(1)
394 sys.exit(0)
395
396
397if __name__ == "__main__":
398 main()