#include <stdlib.h>
#include <glib.h>
#include <gmodule.h>
#include <check.h>
#include <string.h>
#include "bitlbee.h"
#include "user.h"
#include "testsuite.h"
START_TEST(test_user_add)
irc_t *irc = torture_irc();
user_t *user;
user = user_add(irc, "foo");
fail_if(user == NULL);
fail_if(strcmp(user->nick, "foo") != 0);
fail_unless(user_find(irc, "foo") == user);
END_TEST
START_TEST(test_user_add_exists)
irc_t *irc = torture_irc();
user_t *user;
user = user_add(irc, "foo");
fail_if(user == NULL);
user = user_add(irc, "foo");
fail_unless(user == NULL);
END_TEST
START_TEST(test_user_add_invalid)
irc_t *irc = torture_irc();
user_t *user;
user = user_add(irc, ":foo");
fail_unless(user == NULL);
END_TEST
START_TEST(test_user_del_invalid)
irc_t *irc = torture_irc();
fail_unless(user_del(irc, ":foo") == 0);
END_TEST
START_TEST(test_user_del)
irc_t *irc = torture_irc();
user_t *user;
user = user_add(irc, "foo");
fail_unless(user_del(irc, "foo") == 1);
fail_unless(user_find(irc, "foo") == NULL);
END_TEST
START_TEST(test_user_del_nonexistant)
irc_t *irc = torture_irc();
fail_unless(user_del(irc, "foo") == 0);
END_TEST
START_TEST(test_user_rename)
irc_t *irc = torture_irc();
user_t *user;
user = user_add(irc, "foo");
user_rename(irc, "foo", "bar");pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */#!/usr/bin/env python2.7
#
# skyped.py
#
# 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,
# USA.
#
import sys
import os
import signal
import time
import socket
import Skype4Py
import hashlib
from ConfigParser import ConfigParser, NoOptionError
from traceback import print_exception
from fcntl import fcntl, F_SETFD, FD_CLOEXEC
import ssl
__version__ = "0.1.1"
try:
import gobject
hasgobject = True
except ImportError:
import select
import threading
hasgobject = False
def eh(type, value, tb):
global options
if type != KeyboardInterrupt:
print_exception(type, value, tb)
if hasgobject:
gobject.MainLoop().quit()
if options.conn:
options.conn.close()
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
def wait_for_lock(lock, timeout_to_print, timeout, msg):
start = time.time()
locked = lock.acquire(0)
while not(locked):
time.sleep(0.5)
if timeout_to_print and (time.time() - timeout_to_print > start):
dprint("%s: Waited %f seconds" % \
(msg, time.time() - start))
timeout_to_print = False
if timeout and (time.time() - timeout > start):
dprint("%s: Waited %f seconds, giving up" % \
(msg, time.time() - start))
return False
locked = lock.acquire(0)
return True
def input_handler(fd, io_condition = None):
global options
global skype
if options.buf:
for i in options.buf:
skype.send(i.strip())
options.buf = None
if not hasgobject:
return True
else:
if not hasgobject:
close_socket = False
if wait_for_lock(options.lock, 3, 10, "input_handler"):
try:
input = fd.recv(1024)
options.lock.release()
except Exception, s:
dprint("Warning, receiving 1024 bytes failed (%s)." % s)
fd.close()
options.conn = False
options.lock.release()
return False
for i in input.split("\n"):
if i.strip() == "SET USERSTATUS OFFLINE":
close_socket = True
skype.send(i.strip())
return not(close_socket)
try:
input = fd.recv(1024)
except Exception, s:
dprint("Warning, receiving 1024 bytes failed (%s)." % s)
fd.close()
return False
for i in input.split("\n"):
skype.send(i.strip())
return True
def skype_idle_handler(skype):
try:
c = skype.skype.Command("PING", Block=True)
skype.skype.SendCommand(c)
except (Skype4Py.SkypeAPIError, AttributeError), s:
dprint("Warning, pinging Skype failed (%s)." % (s))
time.sleep(1)
return True
def send(sock, txt, tries=10):
global options
if hasgobject:
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:
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: done = sock.sendall(txt)
options.lock.release()
except socket.error as s:
options.lock.release()
dprint("Warning, sending '%s' failed (%s). count=%d" % (txt, s, count))
time.sleep(1)
else:
break
else:
if options.conn:
options.conn.close()
options.conn = False
return done
def bitlbee_idle_handler(skype):
global options
done = False
if options.conn:
try:
e = "PING"
done = send(options.conn, "%s\n" % e)
except Exception, s:
dprint("Warning, sending '%s' failed (%s)." % (e, s))
if hasgobject:
options.conn.close()
else:
if options.conn: options.conn.close()
options.conn = False
done = False
if hasgobject:
return True
else:
return done
return True
def server(host, port, skype = None):
global options
if ":" in host:
sock = socket.socket(socket.AF_INET6)
else:
sock = socket.socket()
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
fcntl(sock, F_SETFD, FD_CLOEXEC);
sock.bind((host, port))
sock.listen(1)
if hasgobject:
gobject.io_add_watch(sock, gobject.IO_IN, listener)
else:
dprint("Waiting for connection...")
listener(sock, skype)
def listener(sock, skype):
global options
if not hasgobject:
if not(wait_for_lock(options.lock, 3, 10, "listener")): return False
rawsock, addr = sock.accept()
try:
options.conn = ssl.wrap_socket(rawsock,
server_side=True,
certfile=options.config.sslcert,
keyfile=options.config.sslkey,
ssl_version=ssl.PROTOCOL_TLSv1)
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()
except Exception:
if not hasgobject:
options.lock.release()
dprint("Warning, handshake failed, closing connection.")
return False
ret = 0
try:
line = options.conn.recv(1024)
if line.startswith("USERNAME") and line.split(' ')[1].strip() == options.config.username:
ret += 1
line = options.conn.recv(1024)
if line.startswith("PASSWORD") and hashlib.sha1(line.split(' ')[1].strip()).hexdigest() == options.config.password:
ret += 1
except Exception, s:
dprint("Warning, receiving 1024 bytes failed (%s)." % s)
options.conn.close()
if not hasgobject:
options.conn = False
options.lock.release()
return False
if ret == 2:
dprint("Username and password OK.")
options.conn.send("PASSWORD OK\n")
if hasgobject:
gobject.io_add_watch(options.conn, gobject.IO_IN, input_handler)
else:
options.lock.release()
serverloop(options, skype)
return True
else:
dprint("Username and/or password WRONG.")
options.conn.send("PASSWORD KO\n")
if not hasgobject:
options.conn.close()
options.conn = False
options.lock.release()
return False
def dprint(msg):
from time import strftime
global options
if options.debug:
import inspect
prefix = strftime("[%Y-%m-%d %H:%M:%S]") + " %s:%d" % inspect.stack()[1][1:3]
sanitized = msg
try:
print prefix + ": " + msg
except Exception, s:
try:
sanitized = msg.encode("ascii", "backslashreplace")
except Error, s:
try:
sanitized = "hex [" + msg.encode("hex") + "]"
except Error, s:
sanitized = "[unable to print debug message]"
print prefix + "~=" + sanitized
if options.log:
sock = open(options.log, "a")
sock.write("%s: %s\n" % (prefix, sanitized))
sock.close()
sys.stdout.flush()
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, 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
if msg_text == "PONG":
return
if "\n" in msg_text:
# crappy skype prefixes only the first line for
# multiline messages so we need to do so for the other
# lines, too. this is something like:
# 'CHATMESSAGE id BODY first line\nsecond line' ->
# 'CHATMESSAGE id BODY first line\nCHATMESSAGE id BODY second line'
prefix = " ".join(msg_text.split(" ")[:3])
msg_text = ["%s %s" % (prefix, i) for i in " ".join(msg_text.split(" ")[3:]).split("\n")]
else:
msg_text = [msg_text]
for i in msg_text:
try:
# Internally, BitlBee always uses UTF-8 and encodes/decodes as
# necessary to communicate with the IRC client; thus send the
# UTF-8 it expects
e = i.encode('UTF-8')
except:
# Should never happen, but it's better to send difficult to
# read data than crash because some message couldn't be encoded
e = i.encode('ascii', 'backslashreplace')
if options.conn:
dprint('<< ' + e)
try:
send(options.conn, e + "\n")
except Exception, s:
dprint("Warning, sending '%s' failed (%s)." % (e, s))
if options.conn: options.conn.close()
options.conn = False
else:
dprint('-- ' + e)
def send(self, msg_text):
if not len(msg_text) or msg_text == "PONG":
if msg_text == "PONG":
options.last_bitlbee_pong = time.time()
return
try:
# Internally, BitlBee always uses UTF-8 and encodes/decodes as
# necessary to communicate with the IRC client; thus decode the
# UTF-8 it sent us
e = msg_text.decode('UTF-8')
except:
# Should never happen, but it's better to send difficult to read
# data to Skype than to crash
e = msg_text.decode('ascii', 'backslashreplace')
dprint('>> ' + e)
try:
c = self.skype.Command(e, Block=True)
self.skype.SendCommand(c)
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))
def serverloop(options, skype):
timeout = 1; # in seconds
skype_ping_period = 5
bitlbee_ping_period = 10
bitlbee_pong_timeout = 30
now = time.time()
skype_ping_start_time = now
bitlbee_ping_start_time = now
options.last_bitlbee_pong = now
in_error = []
handler_ok = True
while (len(in_error) == 0) and handler_ok and options.conn:
ready_to_read, ready_to_write, in_error = \
select.select([options.conn], [], [options.conn], \
timeout)
now = time.time()
handler_ok = len(in_error) == 0
if (len(ready_to_read) == 1) and handler_ok:
handler_ok = input_handler(ready_to_read.pop())
# don't ping bitlbee/skype if they already received data
now = time.time() # allow for the input_handler to take some time
bitlbee_ping_start_time = now
skype_ping_start_time = now
options.last_bitlbee_pong = now
if (now - skype_ping_period > skype_ping_start_time) and handler_ok:
handler_ok = skype_idle_handler(skype)
skype_ping_start_time = now
if now - bitlbee_ping_period > bitlbee_ping_start_time:
handler_ok = bitlbee_idle_handler(skype)
bitlbee_ping_start_time = now
if options.last_bitlbee_pong:
if (now - options.last_bitlbee_pong) > bitlbee_pong_timeout:
dprint("Bitlbee pong timeout")
# TODO is following line necessary? Should there be a options.conn.unwrap() somewhere?
# options.conn.shutdown()
if options.conn:
options.conn.close()
options.conn = False
else:
options.last_bitlbee_pong = now
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)
# 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(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('#', 1)[0])
if not options.port:
options.port = options.config.port
except NoOptionError:
pass
if not options.port:
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')
nullout = file(os.devnull, 'w')
os.dup2(nullin.fileno(), sys.stdin.fileno())
os.dup2(nullout.fileno(), sys.stdout.fileno())
os.dup2(nullout.fileno(), sys.stderr.fileno())
else:
print 'skyped is started on port %s, pid: %d' % (options.port, pid)
sys.exit(0)
else:
dprint('skyped is started on port %s' % options.port)
if hasgobject:
server(options.host, options.port)
try:
skype = SkypeApi(options.mock)
except Skype4Py.SkypeAPIError, s:
sys.exit("%s. Are you sure you have started Skype?" % s)
if hasgobject:
gobject.timeout_add(2000, skype_idle_handler, skype)
gobject.timeout_add(60000, bitlbee_idle_handler, skype)
gobject.MainLoop().run()
else:
while 1:
options.conn = False
options.lock = threading.Lock()
server(options.host, options.port, skype)
if __name__ == '__main__': main()