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

Revision 386, 31.2 kB (checked in by asterix, 4 years ago)

if signature is good, do not look for bad signature

  • 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                        elif resp.has_key('BADSIG'):
167                                keyid = string.split(resp['BADSIG'])[0]
168                        return keyid
169
170                def get_secret_keys(self):
171                        proc = self.run(['--with-colons', '--list-secret-keys'], \
172                                create_fhs=['stdout'])
173                        output = proc.handles['stdout'].read()
174                        proc.handles['stdout'].close()
175
176                        keys = {}
177                        lines = string.split(output, '\n')
178                        for line in lines:
179                                sline = string.split(line, ':')
180                                if sline[0] == 'sec':
181                                        keys[sline[4][8:]] = sline[9]
182                        return keys
183                        try: proc.wait()
184                        except IOError: pass
185
186                def stripHeaderFooter(self, data):
187                        """Remove header and footer from data"""
188                        lines = string.split(data, '\n')
189                        while lines[0] != '':
190                                lines.remove(lines[0])
191                        while lines[0] == '':
192                                lines.remove(lines[0])
193                        i = 0
194                        for line in lines:
195                                if line:
196                                        if line[0] == '-': break
197                                i = i+1
198                        line = string.join(lines[0:i], '\n')
199                        return line
200
201                def addHeaderFooter(self, data, type):
202                        """Add header and footer from data"""
203                        out = "-----BEGIN PGP %s-----\n" % type
204                        out = out + "Version: PGP\n"
205                        out = out + "\n"
206                        out = out + data + "\n"
207                        out = out + "-----END PGP %s-----\n" % type
208                        return out
209
210class GajimCore:
211        """Core"""
212        def __init__(self, mode='client'):
213                self.mode = mode
214                self.log = 0
215                self.init_cfg_file()
216                if mode == 'client':
217                        self.data = ''
218                        self.connect_core()
219                self.hub = common.hub.GajimHub()
220                if self.log:
221                        log.setLevel(logging.DEBUG)
222                else:
223                        log.setLevel(None)
224                if mode == 'server':
225                        self.connected = {}
226                        #connexions {con: name, ...}
227                        self.connexions = {}
228                        self.gpg = {}
229                        self.passwords = {}
230                        if USE_GPG:
231                                self.gpg_common = MyGnuPG()
232                        for a in self.accounts:
233                                self.connected[a] = 0 #0:offline, 1:online, 2:away,
234                                                                                         #3:xa, 4:dnd, 5:invisible
235                                if self.cfgParser.tab[a].has_key("password"):
236                                        self.passwords[a] = self.cfgParser.tab[a]["password"]
237                                else:
238                                        self.passwords[a] = ' '
239                                if USE_GPG:
240                                        self.gpg[a] = MyGnuPG()
241                        self.myVCardID = []
242                        self.loadPlugins(self.cfgParser.tab['Core']['modules'])
243                else:
244                        self.loadPlugins(self.cfgParser.tab['Core_client']['modules'])
245        # END __init__
246
247        def loadPlugins(self, moduleStr):
248                """Load defaults plugins : plugins in 'modules' option of Core section
249                in ConfFile and register them to the hub"""
250                if moduleStr:
251                        mods = string.split (moduleStr, ' ')
252
253                        for mod in mods:
254                                try:
255                                        modObj = self.hub.newPlugin(mod)
256                                except:
257                                        print _("The plugin %s cannot be launched" % mod)
258                                if not modObj:
259                                        print _("The plugin %s is already launched" % mod)
260                                        return
261                                modObj.load()
262        # END loadPLugins
263
264        def connect_core(self):
265                self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
266                self.socket.connect((self.cfgParser.tab['Core_client']['host'], \
267                        self.cfgParser.tab['Core_client']['port']))
268        # END connect_core
269
270        def init_cfg_file(self):
271                """Initialize configuration file"""
272                if self.mode == 'server':
273                        default_tab = {'Profile': {'accounts': '', 'log': 0}, 'Core': \
274                                {'delauth': 1, 'alwaysauth': 0, 'modules': 'logger gtkgui', \
275                                'delroster': 1}}
276                else:
277                        default_tab = {'Profile': {'log': 0}, 'Core_client': {'host': \
278                        'localhost', 'port': 8255, 'modules': 'gtkgui'}}
279                fname = os.path.expanduser(CONFPATH)
280                reps = string.split(fname, '/')
281                path = ''
282                while len(reps) > 1:
283                        path = path + reps[0] + '/'
284                        del reps[0]
285                        try:
286                                os.stat(os.path.expanduser(path))
287                        except OSError:
288                                try:
289                                        os.mkdir(os.path.expanduser(path))
290                                except:
291                                        print _("Can't create %s") % path
292                                        sys.exit
293                try:
294                        os.stat(fname)
295                except:
296                        print _("creating %s") % fname
297                        fic = open(fname, "w")
298                        fic.close()
299                self.cfgParser = common.optparser.OptionsParser(CONFPATH)
300                self.parse()
301                for part in default_tab.keys():
302                        if not self.cfgParser.tab.has_key(part):
303                                self.cfgParser.tab[part] = {}
304                        for option in default_tab[part].keys():
305                                if not self.cfgParser.tab[part].has_key(option):
306                                        self.cfgParser.tab[part][option] = default_tab[part][option]
307        # END init_cfg_file
308
309        def parse(self):
310                """Parse configuratoin file and create self.accounts"""
311                self.cfgParser.parseCfgFile()
312                if self.cfgParser.tab.has_key('Profile'):
313                        if self.cfgParser.tab['Profile'].has_key('log'):
314                                self.log = self.cfgParser.tab['Profile']['log']
315                        if self.mode == 'server':
316                                self.accounts = {}
317                                if self.cfgParser.tab['Profile'].has_key('accounts'):
318                                        accts = string.split(self.cfgParser.tab['Profile']['accounts'], ' ')
319                                        if accts == ['']:
320                                                accts = []
321                                        for a in accts:
322                                                self.accounts[a] = self.cfgParser.tab[a]
323
324        def vCardCB(self, con, vc):
325                """Called when we recieve a vCard
326                Parse the vCard and send it to plugins"""
327                vcard = {'jid': vc.getFrom().getStripped()}
328                if vc._getTag('vCard') == common.jabber.NS_VCARD:
329                        card = vc.getChildren()[0]
330                        for info in card.getChildren():
331                                if info.getChildren() == []:
332                                        vcard[info.getName()] = info.getData()
333                                else:
334                                        vcard[info.getName()] = {}
335                                        for c in info.getChildren():
336                                                 vcard[info.getName()][c.getName()] = c.getData()
337                        if vc.getID() in self.myVCardID:
338                                self.myVCardID.remove(vc.getID())
339                                self.hub.sendPlugin('MYVCARD', self.connexions[con], vcard)
340                        else:
341                                self.hub.sendPlugin('VCARD', self.connexions[con], vcard)
342
343        def messageCB(self, con, msg):
344                """Called when we recieve a message"""
345                typ = msg.getType()
346                tim = msg.getTimestamp()
347                tim = time.strptime(tim, "%Y%m%dT%H:%M:%S")
348                msgtxt = msg.getBody()
349                xtags = msg.getXNodes()
350                encTag = None
351                decmsg = ''
352                for xtag in xtags:
353                        if xtag.getNamespace() == common.jabber.NS_XENCRYPTED:
354                                encTag = xtag
355                                break
356                if encTag and USE_GPG:
357                        #decrypt
358                        encmsg = encTag.getData()
359                        keyID = ''
360                        if self.cfgParser.tab[self.connexions[con]].has_key("keyid"):
361                                keyID = self.cfgParser.tab[self.connexions[con]]["keyid"]
362                        if keyID:
363                                decmsg = self.gpg[self.connexions[con]].decrypt(encmsg, keyID)
364                if decmsg:
365                        msgtxt = decmsg
366                if typ == 'error':
367                        self.hub.sendPlugin('MSGERROR', self.connexions[con], \
368                                (str(msg.getFrom()), msg.getErrorCode(), msg.getError(), msgtxt, tim))
369                elif typ == 'groupchat':
370                        self.hub.sendPlugin('GC_MSG', self.connexions[con], \
371                                (str(msg.getFrom()), msgtxt, tim))
372                else:
373                        self.hub.sendPlugin('MSG', self.connexions[con], \
374                                (str(msg.getFrom()), msgtxt, tim))
375        # END messageCB
376
377        def presenceCB(self, con, prs):
378                """Called when we recieve a presence"""
379#               if prs.getXNode(common.jabber.NS_DELAY): return
380                who = str(prs.getFrom())
381                prio = prs.getPriority()
382                if not prio:
383                        prio = 0
384                typ = prs.getType()
385                if typ == None: typ = 'available'
386                log.debug("PresenceCB : %s" % typ)
387                xtags = prs.getXNodes()
388                sigTag = None
389                keyID = ''
390                status = prs.getStatus()
391                for xtag in xtags:
392                        if xtag.getNamespace() == common.jabber.NS_XSIGNED:
393                                sigTag = xtag
394                                break
395                if sigTag and USE_GPG:
396                        #verify
397                        sigmsg = sigTag.getData()
398                        keyID = self.gpg[self.connexions[con]].verify(status, sigmsg)
399                if typ == 'available':
400                        show = prs.getShow()
401                        if not show:
402                                show = 'online'
403                        self.hub.sendPlugin('NOTIFY', self.connexions[con], \
404                                (prs.getFrom().getStripped(), show, status, \
405                                prs.getFrom().getResource(), prio, keyID, prs.getRole(), \
406                                prs.getAffiliation(), prs.getJid(), prs.getReason(), \
407                                prs.getActor(), prs.getStatusCode()))
408                elif typ == 'unavailable':
409                        self.hub.sendPlugin('NOTIFY', self.connexions[con], \
410                                (prs.getFrom().getStripped(), 'offline', status, \
411                                prs.getFrom().getResource(), prio, keyID, prs.getRole(), \
412                                prs.getAffiliation(), prs.getJid(), prs.getReason(), \
413                                prs.getActor(), prs.getStatusCode()))
414                elif typ == 'subscribe':
415                        log.debug("subscribe request from %s" % who)
416                        if self.cfgParser.Core['alwaysauth'] == 1 or \
417                                string.find(who, "@") <= 0:
418                                con.send(common.jabber.Presence(who, 'subscribed'))
419                                if string.find(who, "@") <= 0:
420                                        self.hub.sendPlugin('NOTIFY', self.connexions[con], \
421                                                (prs.getFrom().getStripped(), 'offline', 'offline', \
422                                                prs.getFrom().getResource(), prio, keyID, None, None, None, \
423                                                None, None, None))
424                        else:
425                                if not status:
426                                        status = _("I would like to add you to my roster.")
427                                self.hub.sendPlugin('SUBSCRIBE', self.connexions[con], (who, \
428                                        status))
429                elif typ == 'subscribed':
430                        jid = prs.getFrom()
431                        self.hub.sendPlugin('SUBSCRIBED', self.connexions[con],\
432                                (jid.getStripped(), jid.getNode(), jid.getResource()))
433                        self.hub.queueIn.put(('UPDUSER', self.connexions[con], \
434                                (jid.getStripped(), jid.getNode(), ['general'])))
435                        #BE CAREFUL : no con.updateRosterItem() in a callback
436                        log.debug("we are now subscribed to %s" % who)
437                elif typ == 'unsubscribe':
438                        log.debug("unsubscribe request from %s" % who)
439                elif typ == 'unsubscribed':
440                        log.debug("we are now unsubscribed to %s" % who)
441                        self.hub.sendPlugin('UNSUBSCRIBED', self.connexions[con], \
442                                prs.getFrom().getStripped())
443                elif typ == 'error':
444                        errmsg = prs.getError()
445                        errcode = prs.getErrorCode()
446                        if errcode == '400': #Bad Request : JID Malformed or Private message when not allowed
447                                pass
448                        elif errcode == '401': #No Password Provided
449                                pass
450                        elif errcode == '403':  #forbidden :    User is Banned
451                                                                                        #                                       Unauthorized Subject Change
452                                                                                        #                                       Attempt by Mere Member to Invite Others to a Members-Only Room
453                                                                                        #                                       Configuration Access to Non-Owner
454                                                                                        #                                       Attempt by Non-Owner to Modify Owner List
455                                                                                        #                                       Attempt by Non-Owner to Modify Admin List
456                                                                                        #                                       Destroy Request Submitted by Non-Owner
457                                pass
458                        elif errcode == '404':  #item not found :       Room Does Not Exist
459                                pass
460                        elif errcode == '405':  #Not allowed :  Attempt to Kick Moderator, Admin, or Owner
461                                                                                        #                                       Attempt to Ban an Admin or Owner
462                                                                                        #                                       Attempt to Revoke Voice from an Admin, Owner, or User with a Higher Affiliation
463                                                                                        #                                       Attempt to Revoke Moderator Privileges from an Admin or Owner
464                                pass
465                        elif errcode == '407':  #registration required :        User Is Not on Member List
466                                                                                        #                                                                       
467                                pass
468                        elif errcode == '409':  #conflict :     Nick Conflict
469                                self.hub.sendPlugin('WARNING', None, errmsg)
470                        else:
471                                self.hub.sendPlugin('NOTIFY', self.connexions[con], \
472                                        (prs.getFrom().getStripped(), 'error', errmsg, \
473                                        prs.getFrom().getResource(), prio, keyID, None, None, None, \
474                                        None, None, None))
475        # END presenceCB
476
477        def disconnectedCB(self, con):
478                """Called when we are disconnected"""
479                log.debug("disconnectedCB")
480                if self.connexions.has_key(con):
481                        self.connected[self.connexions[con]] = 0
482                        self.hub.sendPlugin('STATUS', self.connexions[con], 'offline')
483        # END disconenctedCB
484
485        def rosterSetCB(self, con, iq_obj):
486                for item in iq_obj.getQueryNode().getChildren():
487                        jid  = item.getAttr('jid')
488                        name = item.getAttr('name')
489                        sub  = item.getAttr('subscription')
490                        ask  = item.getAttr('ask')
491                        groups = []
492                        for group in item.getTags("group"):
493                                groups.append(group.getData())
494                        self.hub.sendPlugin('ROSTER_INFO', self.connexions[con], (jid, name, sub, ask, groups))
495
496        def connect(self, account):
497                """Connect and authentificate to the Jabber server"""
498                hostname = self.cfgParser.tab[account]["hostname"]
499                name = self.cfgParser.tab[account]["name"]
500                password = self.passwords[account]
501                ressource = self.cfgParser.tab[account]["ressource"]
502
503                #create connexion if it doesn't already existe
504                con = None
505                for conn in self.connexions:
506                        if self.connexions[conn] == account:
507                                con = conn
508                if not con:
509                        if self.cfgParser.tab[account]["use_proxy"]:
510                                proxy = {"host":self.cfgParser.tab[account]["proxyhost"]}
511                                proxy["port"] = self.cfgParser.tab[account]["proxyport"]
512                        else:
513                                proxy = None
514                        if self.log:
515                                con = common.jabber.Client(host = hostname, debug = [], \
516                                log = sys.stderr, connection=common.xmlstream.TCP, port=5222, \
517                                proxy = proxy)
518                        else:
519                                con = common.jabber.Client(host = hostname, debug = [], log = None,\
520                                connection=common.xmlstream.TCP, port=5222, proxy = proxy)
521                                #debug = [common.jabber.DBG_ALWAYS], log = sys.stderr, \
522                                #connection=common.xmlstream.TCP_SSL, port=5223, proxy = proxy)
523                        con.setDisconnectHandler(self.disconnectedCB)
524                        con.registerHandler('message', self.messageCB)
525                        con.registerHandler('presence', self.presenceCB)
526                        con.registerHandler('iq',self.vCardCB,'result')#common.jabber.NS_VCARD)
527                        con.registerHandler('iq',self.rosterSetCB,'set', \
528                                common.jabber.NS_ROSTER)
529                try:
530                        con.connect()
531                except IOError, e:
532                        log.debug("Couldn't connect to %s %s" % (hostname, e))
533                        self.hub.sendPlugin('STATUS', account, 'offline')
534                        self.hub.sendPlugin('WARNING', None, _("Couldn't connect to %s") \
535                                % hostname)
536                        return 0
537                except commo