source: skype/skyped.py @ 55664fc

Last change on this file since 55664fc was c7304b2, checked in by Miklos Vajna <vmiklos@…>, at 2008-01-12T20:07:10Z

auth via ssl

  • move the config file to sysconfdir/skyped/skyped.conf as there will other config files there, too
  • autogenerate the ssl paths in skyped.conf.dist
  • skype plugin: connect via ssl
  • skyped: listen via ssl
  • Property mode set to 100644
File size: 7.2 KB
Line 
1#!/usr/bin/env python
2#
3#   skyped.py
4
5#   Copyright (c) 2007 by Miklos Vajna <vmiklos@frugalware.org>
6#
7#   It uses several code from a very basic python CLI interface, available at:
8#
9#   http://forum.skype.com/index.php?showtopic=42640
10
11#   This program is free software; you can redistribute it and/or modify
12#   it under the terms of the GNU General Public License as published by
13#   the Free Software Foundation; either version 2 of the License, or
14#   (at your option) any later version.
15#
16#   This program is distributed in the hope that it will be useful,
17#   but WITHOUT ANY WARRANTY; without even the implied warranty of
18#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19#   GNU General Public License for more details.
20
21#   You should have received a copy of the GNU General Public License
22#   along with this program; if not, write to the Free Software
23#   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
24#   USA.
25#
26
27import sys
28import os
29import signal
30import locale
31import time
32import gobject
33import socket
34import getopt
35import Skype4Py
36import threading
37import sha
38from ConfigParser import ConfigParser
39from OpenSSL import SSL
40
41__version__ = "0.1.1"
42
43SKYPE_SERVICE = 'com.Skype.API'
44CLIENT_NAME = 'SkypeApiPythonShell'
45
46def input_handler(fd, io_condition):
47        global options
48        if options.buf:
49                for i in options.buf:
50                        skype.send(i.strip())
51                options.buf = None
52        else:
53                input = fd.recv(1024)
54                for i in input.split("\n"):
55                        skype.send(i.strip())
56                return True
57
58def idle_handler(skype):
59        try:
60                skype.skype.SendCommand(skype.skype.Command(-1, "PING"))
61        except Skype4Py.SkypeAPIError, s:
62                dprint("Warning, pinging Skype failed (%s)." % (s))
63        try:
64                time.sleep(2)
65        except KeyboardInterrupt:
66                sys.exit("Exiting.")
67        return True
68
69def server(host, port):
70        global options
71
72        ctx = SSL.Context(SSL.TLSv1_METHOD)
73        ctx.use_privatekey_file(options.config.sslkey)
74        ctx.use_certificate_file(options.config.sslcert)
75        sock = SSL.Connection(ctx, socket.socket())
76        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
77        sock.bind((host, port))
78        sock.listen(1)
79        gobject.io_add_watch(sock, gobject.IO_IN, listener)
80
81def listener(sock, *args):
82        global options
83        options.conn, addr = sock.accept()
84        ret = 0
85        line = options.conn.recv(1024)
86        if line.startswith("USERNAME") and line.split(' ')[1].strip() == options.config.username:
87                ret += 1
88        line = options.conn.recv(1024)
89        if line.startswith("PASSWORD") and sha.sha(line.split(' ')[1].strip()).hexdigest() == options.config.password:
90                ret += 1
91        if ret == 2:
92                dprint("Username and password OK.")
93                options.conn.send("PASSWORD OK\n")
94                gobject.io_add_watch(options.conn, gobject.IO_IN, input_handler)
95                return True
96        else:
97                dprint("Username and/or password WRONG.")
98                options.conn.send("PASSWORD KO\n")
99                return False
100
101def dprint(msg):
102        global options
103
104        if options.debug:
105                print msg
106
107class SkypeApi():
108        def __init__(self):
109                self.skype = Skype4Py.Skype()
110                self.skype.OnNotify = self.recv
111                self.skype.Attach()
112
113        def recv(self, msg_text):
114                global options
115                if msg_text == "PONG":
116                        return
117                if "\n" in msg_text:
118                        # crappy skype prefixes only the first line for
119                        # multiline messages so we need to do so for the other
120                        # lines, too. this is something like:
121                        # 'CHATMESSAGE id BODY first line\nsecond line' ->
122                        # 'CHATMESSAGE id BODY first line\nCHATMESSAGE id BODY second line'
123                        prefix = " ".join(msg_text.split(" ")[:3])
124                        msg_text = ["%s %s" % (prefix, i) for i in " ".join(msg_text.split(" ")[3:]).split("\n")]
125                else:
126                        msg_text = [msg_text]
127                for i in msg_text:
128                        # use utf-8 here to solve the following problem:
129                        # people use env vars like LC_ALL=en_US (latin1) then
130                        # they complain about why can't they receive latin2
131                        # messages.. so here it is: always use utf-8 then
132                        # everybody will be happy
133                        e = i.encode('UTF-8')
134                        dprint('<< ' + e)
135                        if options.conn:
136                                try:
137                                        options.conn.send(e + "\n")
138                                except IOError, s:
139                                        dprint("Warning, sending '%s' failed (%s)." % (e, s))
140
141        def send(self, msg_text):
142                if not len(msg_text):
143                        return
144                e = msg_text.decode(locale.getdefaultlocale()[1])
145                dprint('>> ' + e)
146                try:
147                        c = self.skype.Command(e, Block=True)
148                        self.skype.SendCommand(c)
149                        self.recv(c.Reply)
150                except Skype4Py.SkypeError:
151                        pass
152                except Skype4Py.SkypeAPIError, s:
153                        dprint("Warning, sending '%s' failed (%s)." % (e, s))
154
155class Options:
156        def __init__(self):
157                self.cfgpath = "/usr/local/etc/skyped/skyped.conf"
158                self.daemon = True
159                self.debug = False
160                self.help = False
161                self.host = "0.0.0.0"
162                self.port = 2727
163                self.version = False
164                # well, this is a bit hackish. we store the socket of the last connected client
165                # here and notify it. maybe later notify all connected clients?
166                self.conn = None
167                # this will be read first by the input handler
168                self.buf = None
169
170
171        def usage(self, ret):
172                print """Usage: skyped [OPTION]...
173
174skyped is a daemon that acts as a tcp server on top of a Skype instance.
175
176Options:
177        -c      --config        path to configuration file (default: %s)
178        -d      --debug         enable debug messages
179        -h      --help          this help
180        -H      --host          set the tcp host (default: %s)
181        -n      --nofork        don't run as daemon in the background
182        -p      --port          set the tcp port (default: %d)
183        -v      --version       display version information""" % (self.cfgpath, self.host, self.port)
184                sys.exit(ret)
185
186if __name__=='__main__':
187        options = Options()
188        try:
189                opts, args = getopt.getopt(sys.argv[1:], "c:dhH:np:v", ["config=", "daemon", "help", "host=", "nofork", "port=", "version"])
190        except getopt.GetoptError:
191                options.usage(1)
192        for opt, arg in opts:
193                if opt in ("-c", "--config"):
194                        options.cfgpath = arg
195                elif opt in ("-d", "--debug"):
196                        options.debug = True
197                elif opt in ("-h", "--help"):
198                        options.help = True
199                elif opt in ("-H", "--host"):
200                        options.host = arg
201                elif opt in ("-n", "--nofork"):
202                        options.daemon = False
203                elif opt in ("-p", "--port"):
204                        options.port = arg
205                elif opt in ("-v", "--version"):
206                        options.version = True
207        if options.help:
208                options.usage(0)
209        elif options.version:
210                print "skyped %s" % __version__
211                sys.exit(0)
212        # parse our config
213        if not os.path.exists(options.cfgpath):
214                print "Can't find configuration file at '%s'." % options.cfgpath
215                print "Use the -c option to specify an alternate one."
216                sys.exit(1)
217        options.config = ConfigParser()
218        options.config.read(options.cfgpath)
219        options.config.username = options.config.get('skyped', 'username').split('#')[0]
220        options.config.password = options.config.get('skyped', 'password').split('#')[0]
221        options.config.sslkey = options.config.get('skyped', 'key').split('#')[0]
222        options.config.sslcert = options.config.get('skyped', 'cert').split('#')[0]
223        dprint("Parsing config file '%s' done, username is '%s'." % (options.cfgpath, options.config.username))
224        if options.daemon:
225                pid = os.fork()
226                if pid == 0:
227                        nullin = file('/dev/null', 'r')
228                        nullout = file('/dev/null', 'w')
229                        os.dup2(nullin.fileno(), sys.stdin.fileno())
230                        os.dup2(nullout.fileno(), sys.stdout.fileno())
231                        os.dup2(nullout.fileno(), sys.stderr.fileno())
232                else:
233                        print 'skyped is started on port %s, pid: %d' % (options.port, pid)
234                        sys.exit(0)
235        server(options.host, options.port)
236        try:
237                skype = SkypeApi()
238        except Skype4Py.SkypeAPIError, s:
239                sys.exit("%s. Are you sure you have started Skype?" % s)
240        gobject.idle_add(idle_handler, skype)
241        gobject.MainLoop().run()
Note: See TracBrowser for help on using the repository browser.