root/branches/gajim_0.4/Core/core.py

Revision 364, 31.1 kB (checked in by asterix, 4 years ago)

assert there is a string to GPG-verify

  • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
Line 
1##      core/core.py
2##
3## Gajim Team:
4##      - Yann Le Boulanger <asterix@lagaule.org>
5##      - Vincent Hanquez <tab@snarc.org>
6##
7##      Copyright (C) 2003-2005 Gajim Team
8##
9## This program is free software; you can redistribute it and/or modify
10## it under the terms of the GNU General Public License as published
11## by the Free Software Foundation; version 2 only.
12##
13## This program is distributed in the hope that it will be useful,
14## but WITHOUT ANY WARRANTY; without even the implied warranty of
15## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16## GNU General Public License for more details.
17##
18
19import sys, os, time, string, logging
20
21import common.hub, common.optparser
22import common.jabber
23import socket, select, pickle
24from tempfile import *
25
26from common import i18n
27_ = i18n._
28
29log = logging.getLogger('core.core')
30log.setLevel(logging.DEBUG)
31
32CONFPATH = "~/.gajim/config"
33LOGPATH = os.path.expanduser("~/.gajim/logs/")
34
35def XMLescape(txt):
36        "Escape XML entities"
37        txt = txt.replace("&", "&amp;")
38        txt = txt.replace("<", "&lt;")
39        txt = txt.replace(">", "&gt;")
40        return txt
41
42def XMLunescape(txt):
43        "Unescape XML entities"
44        txt = txt.replace("&gt;", ">")
45        txt = txt.replace("&lt;", "<")
46        txt = txt.replace("&amp;", "&")
47        return txt
48
49USE_GPG = 1
50try:
51        import GnuPGInterface
52except:
53        USE_GPG = 0
54else:   
55        class  MyGnuPG(GnuPGInterface.GnuPG):
56                def __init__(self):
57                        GnuPGInterface.GnuPG.__init__(self)
58                        self.setup_my_options()
59
60                def setup_my_options(self):
61                        self.options.armor = 1
62                        self.options.meta_interactive = 0
63                        self.options.extra_args.append('--no-secmem-warning')
64
65                def _read_response(self, child_stdout):
66                        # Internal method: reads all the output from GPG, taking notice
67                        # only of lines that begin with the magic [GNUPG:] prefix.
68                        # (See doc/DETAILS in the GPG distribution for info on GPG's
69                        # output when --status-fd is specified.)
70                        #
71                        # Returns a dictionary, mapping GPG's keywords to the arguments
72                        # for that keyword.
73
74                        resp = {}
75                        while 1:
76                                line = child_stdout.readline()
77                                if line == "": break
78                                line = string.rstrip( line )
79                                if line[0:9] == '[GNUPG:] ':
80                                        # Chop off the prefix
81                                        line = line[9:]
82                                        L = string.split(line, None, 1)
83                                        keyword = L[0]
84                                        if len(L) > 1:
85                                                resp[ keyword ] = L[1]
86                                        else:
87                                                resp[ keyword ] = ""
88                        return resp
89
90                def encrypt(self, string, recipients):
91                        self.options.recipients = recipients   # a list!
92
93                        proc = self.run(['--encrypt'], create_fhs=['stdin', 'stdout'])
94                        proc.handles['stdin'].write(string)
95                        proc.handles['stdin'].close()
96
97                        output = proc.handles['stdout'].read()
98                        proc.handles['stdout'].close()
99
100                        try: proc.wait()
101                        except IOError: pass
102                        return self.stripHeaderFooter(output)
103
104                def decrypt(self, string, keyID):
105                        proc = self.run(['--decrypt', '-q', '-u %s'%keyID], create_fhs=['stdin', 'stdout', 'status'])
106                        enc = self.addHeaderFooter(string, 'MESSAGE')
107                        proc.handles['stdin'].write(enc)
108                        proc.handles['stdin'].close()
109               
110                        output = proc.handles['stdout'].read()
111                        proc.handles['stdout'].close()
112
113                        resp = proc.handles['status'].read()
114                        proc.handles['status'].close()
115
116                        try: proc.wait()
117                        except IOError: pass
118                        return output
119       
120                def sign(self, string, keyID):
121                        proc = self.run(['-b', '-u %s'%keyID], create_fhs=['stdin', 'stdout', 'status', 'stderr'])
122                        proc.handles['stdin'].write(string)
123                        proc.handles['stdin'].close()
124
125                        output = proc.handles['stdout'].read()
126                        proc.handles['stdout'].close()
127                        proc.handles['stderr'].close()
128
129                        stat = proc.handles['status']
130                        resp = self._read_response(stat)
131                        proc.handles['status'].close()
132
133                        try: proc.wait()
134                        except IOError: pass
135                        if resp.has_key('BAD_PASSPHRASE'):
136                                return 'BAD_PASSPHRASE'
137                        elif resp.has_key('GOOD_PASSPHRASE'):
138                                return self.stripHeaderFooter(output)
139
140                def verify(self, str, sign):
141                        if not str:
142                                return ''
143                        file = TemporaryFile(prefix='gajim')
144                        fd = file.fileno()
145                        file.write(str)
146                        file.seek(0)
147               
148                        proc = self.run(['--verify', '--enable-special-filenames', '-', '-&%s'%fd], create_fhs=['stdin', 'status', 'stderr'])
149
150                        file.close
151                        sign = self.addHeaderFooter(sign, 'SIGNATURE')
152                        proc.handles['stdin'].write(sign)
153                        proc.handles['stdin'].close()
154                        proc.handles['stderr'].close()
155
156                        stat = proc.handles['status']
157                        resp = self._read_response(stat)
158                        proc.handles['status'].close()
159
160                        try: proc.wait()
161                        except IOError: pass
162
163                        keyid = ''
164                        if resp.has_key('GOODSIG'):
165                                keyid = string.split(resp['GOODSIG'])[0]
166                        return keyid
167
168                def get_secret_keys(self):
169                        proc = self.run(['--with-colons', '--list-secret-keys'], \
170                                create_fhs=['stdout'])
171                        output = proc.handles['stdout'].read()
172                        proc.handles['stdout'].close()
173
174                        keys = {}
175                        lines = string.split(output, '\n')
176                        for line in lines:
177                                sline = string.split(line, ':')
178                                if sline[0] == 'sec':
179                                        keys[sline[4][8:]] = sline[9]
180                        return keys
181                        try: proc.wait()
182                        except IOError: pass
183
184                def stripHeaderFooter(self, data):
185                        """Remove header and footer from data"""
186                        lines = string.split(data, '\n')
187                        while lines[0] != '':
188                                lines.remove(lines[0])
189                        while lines[0] == '':
190                                lines.remove(lines[0])
191                        i = 0
192                        for line in lines:
193                                if line:
194                                        if line[0] == '-': break
195                                i = i+1
196                        line = string.join(lines[0:i], '\n')
197                        return line
198
199                def addHeaderFooter(self, data, type):
200                        """Add header and footer from data"""
201                        out = "-----BEGIN PGP %s-----\n" % type
202                        out = out + "Version: PGP\n"
203                        out = out + "\n"
204                        out = out + data + "\n"
205                        out = out + "-----END PGP %s-----\n" % type
206                        return out
207
208class GajimCore:
209        """Core"""
210        def __init__(self, mode='client'):
211                self.mode = mode
212                self.log = 0
213                self.init_cfg_file()
214                if mode == 'client':
215                        self.data = ''
216                        self.connect_core()
217                self.hub = common.hub.GajimHub()
218                if self.log:
219                        log.setLevel(logging.DEBUG)
220                else:
221                        log.setLevel(None)
222                if mode == 'server':
223                        self.connected = {}
224                        #connexions {con: name, ...}
225                        self.connexions = {}
226                        self.gpg = {}
227                        self.passwords = {}
228                        if USE_GPG:
229                                self.gpg_common = MyGnuPG()
230                        for a in self.accounts:
231                                self.connected[a] = 0 #0:offline, 1:online, 2:away,
232                                                                                         #3:xa, 4:dnd, 5:invisible
233                                self.passwords[a] = self.cfgParser.tab[a]["password"]
234                                if USE_GPG:
235                                        self.gpg[a] = MyGnuPG()
236                        self.myVCardID = []
237                        self.loadPlugins(self.cfgParser.tab['Core']['modules'])
238                else:
239                        self.loadPlugins(self.cfgParser.tab['Core_client']['modules'])
240        # END __init__
241
242        def loadPlugins(self, moduleStr):
243                """Load defaults plugins : plugins in 'modules' option of Core section
244                in ConfFile and register them to the hub"""
245                if moduleStr:
246                        mods = string.split (moduleStr, ' ')
247
248                        for mod in mods:
249                                try:
250                                        modObj = self.hub.newPlugin(mod)
251                                except:
252                                        print _("The plugin %s cannot be launched" % mod)
253                                if not modObj:
254                                        print _("The plugin %s is already launched" % mod)
255                                        return
256                                modObj.load()
257        # END loadPLugins
258
259        def connect_core(self):
260                self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
261                self.socket.connect((self.cfgParser.tab['Core_client']['host'], \
262                        self.cfgParser.tab['Core_client']['port']))
263        # END connect_core
264
265        def init_cfg_file(self):
266                """Initialize configuration file"""
267                if self.mode == 'server':
268                        default_tab = {'Profile': {'accounts': '', 'log': 0}, 'Core': \
269                                {'delauth': 1, 'alwaysauth': 0, 'modules': 'logger gtkgui', \
270                                'delroster': 1}}
271                else:
272                        default_tab = {'Profile': {'log': 0}, 'Core_client': {'host': \
273                        'localhost', 'port': 8255, 'modules': 'gtkgui'}}
274                fname = os.path.expanduser(CONFPATH)
275                reps = string.split(fname, '/')
276                path = ''
277                while len(reps) > 1:
278                        path = path + reps[0] + '/'
279                        del reps[0]
280                        try:
281                                os.stat(os.path.expanduser(path))
282                        except OSError:
283                                try:
284                                        os.mkdir(os.path.expanduser(path))
285                                except:
286                                        print _("Can't create %s") % path
287                                        sys.exit
288                try:
289                        os.stat(fname)
290                except:
291                        print _("creating %s") % fname
292                        fic = open(fname, "w")
293                        fic.close()
294                self.cfgParser = common.optparser.OptionsParser(CONFPATH)
295                self.parse()
296                for part in default_tab.keys():
297                        if not self.cfgParser.tab.has_key(part):
298                                self.cfgParser.tab[part] = {}
299                                self.cfgParser.writeCfgFile()
300                        for option in default_tab[part].keys():
301                                if not self.cfgParser.tab[part].has_key(option):
302                                        self.cfgParser.tab[part][option] = default_tab[part][option]
303                                self.cfgParser.writeCfgFile()
304                self.parse()
305        # END init_cfg_file
306
307        def parse(self):
308                """Parse configuratoin file and create self.accounts"""
309                self.cfgParser.parseCfgFile()
310                if self.cfgParser.tab.has_key('Profile'):
311                        if self.cfgParser.tab['Profile'].has_key('log'):
312                                self.log = self.cfgParser.tab['Profile']['log']
313                        if self.mode == 'server':
314                                self.accounts = {}
315                                if self.cfgParser.tab['Profile'].has_key('accounts'):
316                                        accts = string.split(self.cfgParser.tab['Profile']['accounts'], ' ')
317                                        if accts == ['']:
318                                                accts = []
319                                        for a in accts:
320                                                self.accounts[a] = self.cfgParser.tab[a]
321
322        def vCardCB(self, con, vc):
323                """Called when we recieve a vCard
324                Parse the vCard and send it to plugins"""
325                vcard = {'jid': vc.getFrom().getStripped()}
326                if vc._getTag('vCard') == common.jabber.NS_VCARD:
327                        card = vc.getChildren()[0]
328                        for info in card.getChildren():
329                                if info.getChildren() == []:
330                                        vcard[info.getName()] = info.getData()
331                                else:
332                                        vcard[info.getName()] = {}
333                                        for c in info.getChildren():
334                                                 vcard[info.getName()][c.getName()] = c.getData()
335                        if vc.getID() in self.myVCardID:
336                                self.myVCardID.remove(vc.getID())
337                                self.hub.sendPlugin('MYVCARD', self.connexions[con], vcard)
338                        else:
339                                self.hub.sendPlugin('VCARD', self.connexions[con], vcard)
340
341        def messageCB(self, con, msg):
342                """Called when we recieve a message"""
343                typ = msg.getType()
344                tim = msg.getTimestamp()
345                tim = time.strptime(tim, "%Y%m%dT%H:%M:%S")
346                msgtxt = msg.getBody()
347                xtags = msg.getXNodes()
348                encTag = None
349                decmsg = ''
350                for xtag in xtags:
351                        if xtag.getNamespace() == common.jabber.NS_XENCRYPTED:
352                                encTag = xtag
353                                break
354                if encTag and USE_GPG:
355                        #decrypt
356                        encmsg = encTag.getData()
357                        keyID = ''
358                        if self.cfgParser.tab[self.connexions[con]].has_key("keyid"):
359                                keyID = self.cfgParser.tab[self.connexions[con]]["keyid"]
360                        if keyID:
361                                decmsg = self.gpg[self.connexions[con]].decrypt(encmsg, keyID)
362                if decmsg:
363                        msgtxt = decmsg
364                if typ == 'error':
365                        self.hub.sendPlugin('MSGERROR', self.connexions[con], \
366                                (str(msg.getFrom()), msg.getErrorCode(), msg.getError(), msgtxt, tim))
367                elif typ == 'groupchat':
368                        self.hub.sendPlugin('GC_MSG', self.connexions[con], \
369                                (str(msg.getFrom()), msgtxt, tim))
370                else:
371                        self.hub.sendPlugin('MSG', self.connexions[con], \
372                                (str(msg.getFrom()), msgtxt, tim))
373        # END messageCB
374
375        def presenceCB(self, con, prs):
376                """Called when we recieve a presence"""
377#               if prs.getXNode(common.jabber.NS_DELAY): return
378                who = str(prs.getFrom())
379                prio = prs.getPriority()
380                if not prio:
381                        prio = 0
382                typ = prs.getType()
383                if typ == None: typ = 'available'
384                log.debug("PresenceCB : %s" % typ)
385                xtags = prs.getXNodes()
386                sigTag = None
387                keyID = ''
388                status = prs.getStatus()
389                for xtag in xtags:
390                        if xtag.getNamespace() == common.jabber.NS_XSIGNED:
391                                sigTag = xtag
392                                break
393                if sigTag and USE_GPG:
394                        #verify
395                        sigmsg = sigTag.getData()
396                        keyID = self.gpg[self.connexions[con]].verify(status, sigmsg)
397                if typ == 'available':
398                        show = prs.getShow()
399                        if not show:
400                                show = 'online'
401                        self.hub.sendPlugin('NOTIFY', self.connexions[con], \
402                                (prs.getFrom().getStripped(), show, status, \
403                                prs.getFrom().getResource(), prio, keyID, prs.getRole(), \
404                                prs.getAffiliation(), prs.getJid(), prs.getReason(), \
405                                prs.getActor(), prs.getStatusCode()))
406                elif typ == 'unavailable':
407                        self.hub.sendPlugin('NOTIFY', self.connexions[con], \
408                                (prs.getFrom().getStripped(), 'offline', status, \
409                                prs.getFrom().getResource(), prio, keyID, prs.getRole(), \
410                                prs.getAffiliation(), prs.getJid(), prs.getReason(), \
411                                prs.getActor(), prs.getStatusCode()))
412                elif typ == 'subscribe':
413                        log.debug("subscribe request from %s" % who)
414                        if self.cfgParser.Core['alwaysauth'] == 1 or \
415                                string.find(who, "@") <= 0:
416                                con.send(common.jabber.Presence(who, 'subscribed'))
417                                if string.find(who, "@") <= 0:
418                                        self.hub.sendPlugin('NOTIFY', self.connexions[con], \
419                                                (prs.getFrom().getStripped(), 'offline', 'offline', \
420                                                prs.getFrom().getResource(), prio, keyID, None, None, None, \
421                                                None, None, None))
422                        else:
423                                if not status:
424                                        status = _("I would like to add you to my roster.")
425                                self.hub.sendPlugin('SUBSCRIBE', self.connexions[con], (who, \
426                                        status))
427                elif typ == 'subscribed':
428                        jid = prs.getFrom()
429                        self.hub.sendPlugin('SUBSCRIBED', self.connexions[con],\
430                                (jid.getStripped(), jid.getNode(), jid.getResource()))
431                        self.hub.queueIn.put(('UPDUSER', self.connexions[con], \
432                                (jid.getStripped(), jid.getNode(), ['general'])))
433                        #BE CAREFUL : no con.updateRosterItem() in a callback
434                        log.debug("we are now subscribed to %s" % who)
435                elif typ == 'unsubscribe':
436                        log.debug("unsubscribe request from %s" % who)
437                elif typ == 'unsubscribed':
438                        log.debug("we are now unsubscribed to %s" % who)
439                        self.hub.sendPlugin('UNSUBSCRIBED', self.connexions[con], \
440                                prs.getFrom().getStripped())
441                elif typ == 'error':
442                        errmsg = prs.getError()
443                        errcode = prs.getErrorCode()
444                        if errcode == '400': #Bad Request : JID Malformed or Private message when not allowed
445                                pass
446                        elif errcode == '401': #No Password Provided
447                                pass
448                        elif errcode == '403':  #forbidden :    User is Banned
449                                                                                        #                                       Unauthorized Subject Change
450                                                                                        #                                       Attempt by Mere Member to Invite Others to a Members-Only Room
451                                                                                        #                                       Configuration Access to Non-Owner
452                                                                                        #                                       Attempt by Non-Owner to Modify Owner List
453                                                                                        #                                       Attempt by Non-Owner to Modify Admin List
454                                                                                        #                                       Destroy Request Submitted by Non-Owner
455                                pass
456                        elif errcode == '404':  #item not found :       Room Does Not Exist
457                                pass
458                        elif errcode == '405':  #Not allowed :  Attempt to Kick Moderator, Admin, or Owner
459                                                                                        #                                       Attempt to Ban an Admin or Owner
460                                                                                        #                                       Attempt to Revoke Voice from an Admin, Owner, or User with a Higher Affiliation
461                                                                                        #                                       Attempt to Revoke Moderator Privileges from an Admin or Owner
462                                pass
463                        elif errcode == '407':  #registration required :        User Is Not on Member List
464                                                                                        #                                                                       
465                                pass
466                        elif errcode == '409':  #conflict :     Nick Conflict
467                                self.hub.sendPlugin('WARNING', None, errmsg)
468                        else:
469                                self.hub.sendPlugin('NOTIFY', self.connexions[con], \
470                                        (prs.getFrom().getStripped(), 'error', errmsg, \
471                                        prs.getFrom().getResource(), prio, keyID, None, None, None, \
472                                        None, None, None))
473        # END presenceCB
474
475        def disconnectedCB(self, con):
476                """Called when we are disconnected"""
477                log.debug("disconnectedCB")
478                if self.connexions.has_key(con):
479                        self.connected[self.connexions[con]] = 0
480                        self.hub.sendPlugin('STATUS', self.connexions[con], 'offline')
481        # END disconenctedCB
482
483        def rosterSetCB(self, con, iq_obj):
484                for item in iq_obj.getQueryNode().getChildren():
485                        jid  = item.getAttr('jid')
486                        name = item.getAttr('name')
487                        sub  = item.getAttr('subscription')
488                        ask  = item.getAttr('ask')
489                        groups = []
490                        for group in item.getTags("group"):
491                                groups.append(group.getData())
492                        self.hub.sendPlugin('ROSTER_INFO', self.connexions[con], (jid, name, sub, ask, groups))
493
494        def connect(self, account):
495                """Connect and authentificate to the Jabber server"""
496                hostname = self.cfgParser.tab[account]["hostname"]
497                name = self.cfgParser.tab[account]["name"]
498                password = self.passwords[account]
499                ressource = self.cfgParser.tab[account]["ressource"]
500
501                #create connexion if it doesn't already existe
502                con = None
503                for conn in self.connexions:
504                        if self.connexions[conn] == account:
505                                con = conn
506                if not con:
507                        if self.cfgParser.tab[account]["use_proxy"]:
508                                proxy = {"host":self.cfgParser.tab[account]["proxyhost"]}
509                                proxy["port"] = self.cfgParser.tab[account]["proxyport"]
510                        else:
511                                proxy = None
512                        if self.log:
513                                con = common.jabber.Client(host = hostname, debug = [], \
514                                log = sys.stderr, connection=common.xmlstream.TCP, port=5222, \
515                                proxy = proxy)
516                        else:
517                                con = common.jabber.Client(host = hostname, debug = [], log = None,\
518                                connection=common.xmlstream.TCP, port=5222, proxy = proxy)
519                                #debug = [common.jabber.DBG_ALWAYS], log = sys.stderr, \
520                                #connection=common.xmlstream.TCP_SSL, port=5223, proxy = proxy)
521                        con.setDisconnectHandler(self.disconnectedCB)
522                        con.registerHandler('message', self.messageCB)
523                        con.registerHandler('presence', self.presenceCB)
524                        con.registerHandler('iq',self.vCardCB,'result')#common.jabber.NS_VCARD)
525                        con.registerHandler('iq',self.rosterSetCB,'set', \
526                                common.jabber.NS_ROSTER)
527                try:
528                        con.connect()
529                except IOError, e:
530                        log.debug("Couldn't connect to %s %s" % (hostname, e))
531                        self.hub.sendPlugin('STATUS', account, 'offline')
532                        self.hub.sendPlugin('WARNING', None, _("Couldn't connect to %s") \
533                                % hostname)
534                        return 0
535                except common.xmlstream.socket.error, e:
536                        log.debug("Couldn't connect to %s %s" % (hostname, e))
537                        self.hub.sendPlugin('STATUS', account, 'offline')
538                    Â