diff options
Diffstat (limited to 'protocols/skype/skyped.py')
-rw-r--r-- | protocols/skype/skyped.py | 279 |
1 files changed, 152 insertions, 127 deletions
diff --git a/protocols/skype/skyped.py b/protocols/skype/skyped.py index 07c08435..86b1e96a 100644 --- a/protocols/skype/skyped.py +++ b/protocols/skype/skyped.py @@ -1,32 +1,30 @@ #!/usr/bin/env python2.7 -# +# # skyped.py -# -# Copyright (c) 2007, 2008, 2009, 2010, 2011 by Miklos Vajna <vmiklos@frugalware.org> +# +# Copyright (c) 2007-2013 by Miklos Vajna <vmiklos@vmiklos.hu> # # 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 2 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, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, # USA. # import sys import os import signal -import locale import time import socket -import getopt import Skype4Py import hashlib from ConfigParser import ConfigParser, NoOptionError @@ -53,11 +51,12 @@ def eh(type, value, tb): gobject.MainLoop().quit() if options.conn: options.conn.close() - # shut down client if it's running - try: - skype.skype.Client.Shutdown() - except NameError: - pass + if not options.dont_start_skype: + # shut down client if it's running + try: + skype.skype.Client.Shutdown() + except NameError: + pass sys.exit("Exiting.") sys.excepthook = eh @@ -119,39 +118,35 @@ def skype_idle_handler(skype): try: c = skype.skype.Command("PING", Block=True) skype.skype.SendCommand(c) - except Skype4Py.SkypeAPIError, s: + except (Skype4Py.SkypeAPIError, AttributeError), s: dprint("Warning, pinging Skype failed (%s)." % (s)) + time.sleep(1) return True -def send(sock, txt): +def send(sock, txt, tries=10): global options - from time import sleep - count = 1 - done = False if hasgobject: - while (not done) and (count < 10): - try: - sock.send(txt) - done = True - except Exception, s: - count += 1 - dprint("Warning, sending '%s' failed (%s). count=%d" % (txt, s, count)) - sleep(1) - if not done: + if not options.conn: return + try: + done = sock.sendall(txt) + except socket.error as s: + dprint("Warning, sending '%s' failed (%s)." % (txt, s)) options.conn.close() + options.conn = False else: - while (not done) and (count < 10) and options.conn: + for attempt in xrange(1, tries+1): + if not options.conn: break if wait_for_lock(options.lock, 3, 10, "socket send"): try: - if options.conn: sock.send(txt) + if options.conn: done = sock.sendall(txt) options.lock.release() - done = True - except Exception, s: + except socket.error as s: options.lock.release() - count += 1 dprint("Warning, sending '%s' failed (%s). count=%d" % (txt, s, count)) - sleep(1) - if not done: + time.sleep(1) + else: + break + else: if options.conn: options.conn.close() options.conn = False @@ -206,9 +201,13 @@ def listener(sock, skype): certfile=options.config.sslcert, keyfile=options.config.sslkey, ssl_version=ssl.PROTOCOL_TLSv1) - except ssl.SSLError: - dprint("Warning, SSL init failed, did you create your certificate?") - return False + except (ssl.SSLError, socket.error) as err: + if isinstance(err, ssl.SSLError): + dprint("Warning, SSL init failed, did you create your certificate?") + return False + else: + dprint('Warning, SSL init failed') + return True if hasattr(options.conn, 'handshake'): try: options.conn.handshake() @@ -254,11 +253,13 @@ def dprint(msg): from time import strftime global options - now = strftime("%Y-%m-%d %H:%M:%S") - if options.debug: + import inspect + prefix = strftime("[%Y-%m-%d %H:%M:%S]") + " %s:%d" % inspect.stack()[1][1:3] + sanitized = msg + try: - print now + ": " + msg + print prefix + ": " + msg except Exception, s: try: sanitized = msg.encode("ascii", "backslashreplace") @@ -267,18 +268,57 @@ def dprint(msg): sanitized = "hex [" + msg.encode("hex") + "]" except Error, s: sanitized = "[unable to print debug message]" - print now + "~=" + sanitized + print prefix + "~=" + sanitized + + if options.log: + sock = open(options.log, "a") + sock.write(prefix + ": " + sanitized) + sock.close() + sys.stdout.flush() - if options.log: - sock = open(options.log, "a") - sock.write("%s: %s\n" % (now, msg)) - sock.close() + +class MockedSkype: + """Mock class for Skype4Py.Skype(), in case the -m option is used.""" + def __init__(self, mock): + sock = open(mock) + self.lines = sock.readlines() + + def SendCommand(self, c): + pass + + def Command(self, msg, Block): + if msg == "PING": + return ["PONG"] + line = self.lines[0].strip() + if not line.startswith(">> "): + raise Exception("Corrupted mock input") + line = line[3:] + if line != msg: + raise Exception("'%s' != '%s'" % (line, msg)) + self.lines = self.lines[1:] # drop the expected incoming line + ret = [] + while True: + # and now send back all the following lines, up to the next expected incoming line + if len(self.lines) == 0: + break + if self.lines[0].startswith(">> "): + break + if not self.lines[0].startswith("<< "): + raise Exception("Corrupted mock input") + ret.append(self.lines[0][3:].strip()) + self.lines = self.lines[1:] + return ret class SkypeApi: - def __init__(self): - self.skype = Skype4Py.Skype() - self.skype.OnNotify = self.recv - self.skype.Client.Start() + def __init__(self, mock): + global options + if not mock: + self.skype = Skype4Py.Skype() + self.skype.OnNotify = self.recv + if not options.dont_start_skype: + self.skype.Client.Start() + else: + self.skype = MockedSkype(mock) def recv(self, msg_text): global options @@ -333,48 +373,16 @@ class SkypeApi: try: c = self.skype.Command(e, Block=True) self.skype.SendCommand(c) - self.recv(c.Reply) + if hasattr(c, "Reply"): + self.recv(c.Reply) # Skype4Py answer + else: + for i in c: # mock may return multiple iterable answers + self.recv(i) except Skype4Py.SkypeError: pass except Skype4Py.SkypeAPIError, s: dprint("Warning, sending '%s' failed (%s)." % (e, s)) -class Options: - def __init__(self): - self.cfgpath = os.path.join(os.environ['HOME'], ".skyped", "skyped.conf") - # fall back to system-wide settings - self.syscfgpath = "/usr/local/etc/skyped/skyped.conf" - if os.path.exists(self.syscfgpath) and not os.path.exists(self.cfgpath): - self.cfgpath = self.syscfgpath - self.daemon = True - self.debug = False - self.help = False - self.host = "0.0.0.0" - self.log = None - self.port = None - self.version = False - # well, this is a bit hackish. we store the socket of the last connected client - # here and notify it. maybe later notify all connected clients? - self.conn = None - # this will be read first by the input handler - self.buf = None - - - def usage(self, ret): - print """Usage: skyped [OPTION]... - -skyped is a daemon that acts as a tcp server on top of a Skype instance. - -Options: - -c --config path to configuration file (default: %s) - -d --debug enable debug messages - -h --help this help - -H --host set the tcp host, supports IPv4 and IPv6 (default: %s) - -l --log set the log file in background mode (default: none) - -n --nofork don't run as daemon in the background - -p --port set the tcp port (default: %s) - -v --version display version information""" % (self.cfgpath, self.host, self.port) - sys.exit(ret) def serverloop(options, skype): timeout = 1; # in seconds @@ -417,58 +425,72 @@ def serverloop(options, skype): else: options.last_bitlbee_pong = now -if __name__=='__main__': - options = Options() - try: - opts, args = getopt.getopt(sys.argv[1:], "c:dhH:l:np:v", ["config=", "debug", "help", "host=", "log=", "nofork", "port=", "version"]) - except getopt.GetoptError: - options.usage(1) - for opt, arg in opts: - if opt in ("-c", "--config"): - options.cfgpath = arg - elif opt in ("-d", "--debug"): - options.debug = True - elif opt in ("-h", "--help"): - options.help = True - elif opt in ("-H", "--host"): - options.host = arg - elif opt in ("-l", "--log"): - options.log = arg - elif opt in ("-n", "--nofork"): - options.daemon = False - elif opt in ("-p", "--port"): - options.port = int(arg) - elif opt in ("-v", "--version"): - options.version = True - if options.help: - options.usage(0) - elif options.version: + +def main(args=None): + global options + global skype + + cfgpath = os.path.join(os.environ['HOME'], ".skyped", "skyped.conf") + syscfgpath = "/usr/local/etc/skyped/skyped.conf" + if not os.path.exists(cfgpath) and os.path.exists(syscfgpath): + cfgpath = syscfgpath # fall back to system-wide settings + port = 2727 + + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('-c', '--config', + metavar='path', default=cfgpath, + help='path to configuration file (default: %(default)s)') + parser.add_argument('-H', '--host', default='0.0.0.0', + help='set the tcp host, supports IPv4 and IPv6 (default: %(default)s)') + parser.add_argument('-p', '--port', type=int, + help='set the tcp port (default: %(default)s)') + parser.add_argument('-l', '--log', metavar='path', + help='set the log file in background mode (default: none)') + parser.add_argument('-v', '--version', action='store_true', help='display version information') + parser.add_argument('-n', '--nofork', + action='store_true', help="don't run as daemon in the background") + parser.add_argument('-s', '--dont-start-skype', action='store_true', + help="assume that skype is running independently, don't try to start/stop it") + parser.add_argument('-m', '--mock', help='fake interactions with skype (only useful for tests)') + parser.add_argument('-d', '--debug', action='store_true', help='enable debug messages') + options = parser.parse_args(sys.argv[1:] if args is None else args) + + if options.version: print "skyped %s" % __version__ sys.exit(0) - # parse our config - if not os.path.exists(options.cfgpath): - print "Can't find configuration file at '%s'." % options.cfgpath - print "Use the -c option to specify an alternate one." - sys.exit(1) + + # well, this is a bit hackish. we store the socket of the last connected client + # here and notify it. maybe later notify all connected clients? + options.conn = None + # this will be read first by the input handler + options.buf = None + + if not os.path.exists(options.config): + parser.error(( "Can't find configuration file at '%s'. " + "Use the -c option to specify an alternate one." )% options.config) + + cfgpath = options.config options.config = ConfigParser() - options.config.read(options.cfgpath) - options.config.username = options.config.get('skyped', 'username').split('#')[0] - options.config.password = options.config.get('skyped', 'password').split('#')[0] - options.config.sslkey = os.path.expanduser(options.config.get('skyped', 'key').split('#')[0]) - options.config.sslcert = os.path.expanduser(options.config.get('skyped', 'cert').split('#')[0]) + options.config.read(cfgpath) + options.config.username = options.config.get('skyped', 'username').split('#', 1)[0] + options.config.password = options.config.get('skyped', 'password').split('#', 1)[0] + options.config.sslkey = os.path.expanduser(options.config.get('skyped', 'key').split('#', 1)[0]) + options.config.sslcert = os.path.expanduser(options.config.get('skyped', 'cert').split('#', 1)[0]) + # hack: we have to parse the parameters first to locate the # config file but the -p option should overwrite the value from # the config file try: - options.config.port = int(options.config.get('skyped', 'port').split('#')[0]) + options.config.port = int(options.config.get('skyped', 'port').split('#', 1)[0]) if not options.port: options.port = options.config.port except NoOptionError: pass if not options.port: - options.port = 2727 - dprint("Parsing config file '%s' done, username is '%s'." % (options.cfgpath, options.config.username)) - if options.daemon: + options.port = port + dprint("Parsing config file '%s' done, username is '%s'." % (cfgpath, options.config.username)) + if not options.nofork: pid = os.fork() if pid == 0: nullin = file(os.devnull, 'r') @@ -484,7 +506,7 @@ if __name__=='__main__': if hasgobject: server(options.host, options.port) try: - skype = SkypeApi() + skype = SkypeApi(options.mock) except Skype4Py.SkypeAPIError, s: sys.exit("%s. Are you sure you have started Skype?" % s) if hasgobject: @@ -496,3 +518,6 @@ if __name__=='__main__': options.conn = False options.lock = threading.Lock() server(options.host, options.port, skype) + + +if __name__ == '__main__': main() |