gajim.py 14.7 KB
Newer Older
roidelapluie's avatar
roidelapluie committed
1
# -*- coding:utf-8 -*-
roidelapluie's avatar
roidelapluie committed
2
## src/common/gajim.py
Yann Leboulanger's avatar
Yann Leboulanger committed
3
##
Dicson's avatar
Dicson committed
4
## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
roidelapluie's avatar
roidelapluie committed
5 6 7
## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
##                         Travis Shirk <travis AT pobox.com>
##                         Nikos Kouremenos <kourem AT gmail.com>
roidelapluie's avatar
roidelapluie committed
8
## Copyright (C) 2006 Junglecow J <junglecow AT gmail.com>
roidelapluie's avatar
roidelapluie committed
9 10 11 12 13
##                    Stefan Bethge <stefan AT lanpartei.de>
## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
##                         Stephan Erb <steve-e AT h3c.de>
## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
Yann Leboulanger's avatar
Yann Leboulanger committed
14
##
Yann Leboulanger's avatar
Yann Leboulanger committed
15 16 17
## This file is part of Gajim.
##
## Gajim is free software; you can redistribute it and/or modify
Yann Leboulanger's avatar
Yann Leboulanger committed
18
## it under the terms of the GNU General Public License as published
Yann Leboulanger's avatar
Yann Leboulanger committed
19
## by the Free Software Foundation; version 3 only.
Yann Leboulanger's avatar
Yann Leboulanger committed
20
##
Yann Leboulanger's avatar
Yann Leboulanger committed
21
## Gajim is distributed in the hope that it will be useful,
Yann Leboulanger's avatar
Yann Leboulanger committed
22
## but WITHOUT ANY WARRANTY; without even the implied warranty of
roidelapluie's avatar
roidelapluie committed
23
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Yann Leboulanger's avatar
Yann Leboulanger committed
24 25
## GNU General Public License for more details.
##
Yann Leboulanger's avatar
Yann Leboulanger committed
26
## You should have received a copy of the GNU General Public License
roidelapluie's avatar
roidelapluie committed
27
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
Yann Leboulanger's avatar
Yann Leboulanger committed
28
##
Yann Leboulanger's avatar
Yann Leboulanger committed
29

30
import os
Yann Leboulanger's avatar
Yann Leboulanger committed
31
import logging
32
import locale
33
import gi
34
import uuid
35

Yann Leboulanger's avatar
Yann Leboulanger committed
36
from common import config
37
import nbxmpp
38
from common import ged as ged_module
39

40
interface = None # The actual interface (the gtk one for the moment)
41
thread_interface = None # Interface to run a thread and then a callback
42
config = config.Config()
43
version = config.get('version')
dkirov's avatar
dkirov committed
44
connections = {} # 'account name': 'account (connection.Connection) instance'
45
ipython_window = None
46

47
ged = ged_module.GlobalEventsDispatcher() # Global Events Dispatcher
48 49
nec = None # Network Events Controller
plugin_manager = None # Plugins Manager
50

51
log = logging.getLogger('gajim')
52

53
logger = None
nkour's avatar
nkour committed
54

55
from common import configpaths
56
gajimpaths = configpaths.gajimpaths
nkour's avatar
nkour committed
57

58 59 60
VCARD_PATH = gajimpaths['VCARD']
AVATAR_PATH = gajimpaths['AVATAR']
MY_EMOTS_PATH = gajimpaths['MY_EMOTS']
61
MY_ICONSETS_PATH = gajimpaths['MY_ICONSETS']
js's avatar
js committed
62
MY_MOOD_ICONSETS_PATH = gajimpaths['MY_MOOD_ICONSETS']
js's avatar
js committed
63
MY_ACTIVITY_ICONSETS_PATH = gajimpaths['MY_ACTIVITY_ICONSETS']
64
MY_CACERTS = gajimpaths['MY_CACERTS']
65
MY_PEER_CERTS_PATH = gajimpaths['MY_PEER_CERTS']
66 67
TMP = gajimpaths['TMP']
DATA_DIR = gajimpaths['DATA']
68
ICONS_DIR = gajimpaths['ICONS']
69
HOME_DIR = gajimpaths['HOME']
70
PLUGINS_DIRS = [gajimpaths['PLUGINS_BASE'],
Yann Leboulanger's avatar
Yann Leboulanger committed
71
                gajimpaths['PLUGINS_USER']]
72
PLUGINS_CONFIG_DIR = gajimpaths['PLUGINS_CONFIG_DIR']
73
MY_CERT_DIR = gajimpaths['MY_CERT']
nkour's avatar
nkour committed
74

75
try:
76
    LANG = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc..
77
except (ValueError, locale.Error):
78 79
    # unknown locale, use en is better than fail
    LANG = None
80
if LANG is None:
81
    LANG = 'en'
82
else:
83
    LANG = LANG[:2] # en, fr, el etc..
84

85 86
os_info = None # used to cache os information

87 88
from common.contacts import LegacyContactsAPI
from common.events import Events
89

sb's avatar
sb committed
90 91 92 93
gmail_domains = ['gmail.com', 'googlemail.com']

transport_type = {} # list the type of transport

94
last_message_time = {} # list of time of the latest incomming message
Dicson's avatar
Dicson committed
95 96
                       # {acct1: {jid1: time1, jid2: time2}, }
encrypted_chats = {}   # list of encrypted chats {acct1: [jid1, jid2], ..}
97

98
contacts = LegacyContactsAPI()
Dicson's avatar
Dicson committed
99 100 101 102 103 104 105 106 107
gc_connected = {}    # tell if we are connected to the room or not
                     # {acct: {room_jid: True}}
gc_passwords = {}    # list of the pass required to enter a room
                     # {room_jid: password}
automatic_rooms = {} # list of rooms that must be automaticaly configured
                     # and for which we have a list of invities
                     #{account: {room_jid: {'invities': []}}}
new_room_nick = None # if it's != None, use this nick instead of asking for
                     # a new nickname when there is a conflict.
108

109 110 111
groups = {} # list of groups
newly_added = {} # list of contacts that has just signed in
to_be_removed = {} # list of contacts that has just signed out
112

sb's avatar
sb committed
113 114
events = Events()

115
notification = None
116

117
nicks = {} # list of our nick names in each account
118
# should we block 'contact signed in' notifications for this account?
119 120
# this is only for the first 30 seconds after we change our show
# to something else than offline
sb's avatar
sb committed
121 122
# can also contain account/transport_jid to block notifications for contacts
# from this transport
123
block_signed_in_notifications = {}
124
con_types = {} # type of each connection (ssl, tls, tcp, ...)
125 126 127 128 129 130

sleeper_state = {} # whether we pass auto away / xa or not
#'off': don't use sleeper for this account
#'online': online and use sleeper
#'autoaway': autoaway and use sleeper
#'autoxa': autoxa and use sleeper
131
status_before_autoaway = {}
132

133 134 135 136
# jid of transport contacts for which we need to ask avatar when transport will
# be online
transport_avatar = {} # {transport_jid: [jid_list]}

Piotr Gaczkowski's avatar
Piotr Gaczkowski committed
137 138
# Is Gnome configured to activate on single click ?
single_click = False
nkour's avatar
nkour committed
139
SHOW_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
140
        'invisible', 'error']
141

sb's avatar
sb committed
142
# zeroconf account name
143
ZEROCONF_ACC_NAME = 'Local'
sb's avatar
sb committed
144

145 146 147 148
# These will be set in gajim.gui_interface.
idlequeue = None
socks5queue = None

149 150
HAVE_ZEROCONF = True
try:
Dicson's avatar
Dicson committed
151
    __import__('avahi')
152
except ImportError:
153
    try:
Dicson's avatar
Dicson committed
154
        __import__('pybonjour')
155 156
    except Exception: # Linux raises ImportError, Windows raises WindowsError
        HAVE_ZEROCONF = False
157

Yann Leboulanger's avatar
Yann Leboulanger committed
158 159
HAVE_PYCRYPTO = True
try:
Dicson's avatar
Dicson committed
160
    __import__('Crypto')
Yann Leboulanger's avatar
Yann Leboulanger committed
161
except ImportError:
162
    HAVE_PYCRYPTO = False
Yann Leboulanger's avatar
Yann Leboulanger committed
163

164
HAVE_GPG = True
165
GPG_BINARY = 'gpg'
166
try:
167
    __import__('gnupg')
168
except ImportError:
169
    HAVE_GPG = False
170
else:
171
    import os
172
    import subprocess
173 174 175 176 177 178 179 180 181 182 183
    def test_gpg(binary='gpg'):
        if os.name == 'nt':
            gpg_cmd = binary + ' -h >nul 2>&1'
        else:
            gpg_cmd = binary + ' -h >/dev/null 2>&1'
        if subprocess.call(gpg_cmd, shell=True):
            return False
        return True
    if test_gpg(binary='gpg2'):
        GPG_BINARY = 'gpg2'
    if not test_gpg(binary='gpg'):
184
        HAVE_GPG = False
185

186 187 188 189 190 191 192 193 194 195 196
HAVE_PYOPENSSL = True
try:
    import OpenSSL.SSL
    import OpenSSL.crypto
    ver = OpenSSL.__version__
    ver_l = [int(i) for i in ver.split('.')]
    if ver_l < [0, 12]:
        raise ImportError
except Exception:
    HAVE_PYOPENSSL = False

197
HAVE_FARSTREAM = True
198
try:
Yann Leboulanger's avatar
Yann Leboulanger committed
199
    if os.name == 'nt':
200 201
        os.environ['FS_PLUGIN_PATH'] = 'gtk\\lib\\farstream-0.1'
        os.environ['GST_PLUGIN_PATH'] = 'gtk\\lib\\gstreamer-0.10'
202 203 204 205 206
    gi.require_version('Farstream', '0.2')
    from gi.repository import Farstream
    gi.require_version('Gst', '1.0')
    from gi.repository import Gst
    from gi.repository import GLib
207
    try:
208 209 210
        Gst.init(None)
        conference = Gst.ElementFactory.make('fsrtpconference', None)
        session = conference.new_session(Farstream.MediaType.AUDIO)
211 212
        del session
        del conference
213
    except GLib.GError:
214
        HAVE_FARSTREAM = False
215

216
except (ImportError, ValueError):
217
    HAVE_FARSTREAM = False
218 219 220

HAVE_UPNP_IGD = True
try:
221
    gi.require_version('GUPnPIgd', '1.0')
222 223
    from gi.repository import GUPnPIgd
    gupnp_igd = GUPnPIgd.SimpleIgd()
224
except ValueError:
225 226
    HAVE_UPNP_IGD = False

227 228 229 230 231 232
HAVE_PYCURL = True
try:
    __import__('pycurl')
except ImportError:
    HAVE_PYCURL = False

233

234
gajim_identity = {'type': 'pc', 'category': 'client', 'name': 'Gajim'}
235 236 237 238 239 240 241 242 243
gajim_common_features = [nbxmpp.NS_BYTESTREAM, nbxmpp.NS_SI, nbxmpp.NS_FILE,
    nbxmpp.NS_MUC, nbxmpp.NS_MUC_USER, nbxmpp.NS_MUC_ADMIN, nbxmpp.NS_MUC_OWNER,
    nbxmpp.NS_MUC_CONFIG, nbxmpp.NS_COMMANDS, nbxmpp.NS_DISCO_INFO, 'ipv6',
    'jabber:iq:gateway', nbxmpp.NS_LAST, nbxmpp.NS_PRIVACY, nbxmpp.NS_PRIVATE,
    nbxmpp.NS_REGISTER, nbxmpp.NS_VERSION, nbxmpp.NS_DATA, nbxmpp.NS_ENCRYPTED,
    'msglog', 'sslc2s', 'stringprep', nbxmpp.NS_PING, nbxmpp.NS_TIME_REVISED,
    nbxmpp.NS_SSN, nbxmpp.NS_MOOD, nbxmpp.NS_ACTIVITY, nbxmpp.NS_NICK,
    nbxmpp.NS_ROSTERX, nbxmpp.NS_SECLABEL, nbxmpp.NS_HASHES,
    nbxmpp.NS_HASHES_MD5, nbxmpp.NS_HASHES_SHA1, nbxmpp.NS_HASHES_SHA256,
244
    nbxmpp.NS_HASHES_SHA512, nbxmpp.NS_CONFERENCE]
245

246 247 248 249 250
# Optional features gajim supports per account
gajim_optional_features = {}

# Capabilities hash per account
caps_hash = {}
251

252
def get_an_id():
253
    return str(uuid.uuid4())
254

255
def get_nick_from_jid(jid):
256 257
    pos = jid.find('@')
    return jid[:pos]
258

259
def get_server_from_jid(jid):
260 261
    pos = jid.find('@') + 1 # after @
    return jid[pos:]
262

sb's avatar
sb committed
263
def get_name_and_server_from_jid(jid):
264 265 266
    name = get_nick_from_jid(jid)
    server = get_server_from_jid(jid)
    return name, server
267

268
def get_room_and_nick_from_fjid(jid):
269 270 271 272 273 274 275
    # fake jid is the jid for a contact in a room
    # gaim@conference.jabber.no/nick/nick-continued
    # return ('gaim@conference.jabber.no', 'nick/nick-continued')
    l = jid.split('/', 1)
    if len(l) == 1: # No nick
        l.append('')
    return l
276 277

def get_real_jid_from_fjid(account, fjid):
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
    """
    Return real jid or returns None, if we don't know the real jid
    """
    room_jid, nick = get_room_and_nick_from_fjid(fjid)
    if not nick: # It's not a fake_jid, it is a real jid
        return fjid # we return the real jid
    real_jid = fjid
    if interface.msg_win_mgr.get_gc_control(room_jid, account):
        # It's a pm, so if we have real jid it's in contact.jid
        gc_contact = contacts.get_gc_contact(account, room_jid, nick)
        if not gc_contact:
            return
        # gc_contact.jid is None when it's not a real jid (we don't know real jid)
        real_jid = gc_contact.jid
    return real_jid
293

294
def get_room_from_fjid(jid):
295
    return get_room_and_nick_from_fjid(jid)[0]
296

297
def get_contact_name_from_jid(account, jid):
298 299
    c = contacts.get_first_contact_from_jid(account, jid)
    return c.name
300

nkour's avatar
nkour committed
301
def get_jid_without_resource(jid):
302
    return jid.split('/')[0]
303 304

def construct_fjid(room_jid, nick):
305 306 307
    # fake jid is the jid for a contact in a room
    # gaim@conference.jabber.org/nick
    return room_jid + '/' + nick
308

309
def get_resource_from_jid(jid):
310 311 312 313 314
    jids = jid.split('/', 1)
    if len(jids) > 1:
        return jids[1] # abc@doremi.org/res/res-continued
    else:
        return ''
315

316
def get_number_of_accounts():
317 318 319 320
    """
    Return the number of ALL accounts
    """
    return len(connections.keys())
nkour's avatar
nkour committed
321

322
def get_number_of_connected_accounts(accounts_list = None):
323 324 325 326 327 328 329 330 331 332 333 334 335
    """
    Returns the number of CONNECTED accounts. Uou can optionally pass an
    accounts_list and if you do those will be checked, else all will be checked
    """
    connected_accounts = 0
    if accounts_list is None:
        accounts = connections.keys()
    else:
        accounts = accounts_list
    for account in accounts:
        if account_is_connected(account):
            connected_accounts = connected_accounts + 1
    return connected_accounts
336

dkirov's avatar
dkirov committed
337
def account_is_connected(account):
338 339 340 341 342 343
    if account not in connections:
        return False
    if connections[account].connected > 1: # 0 is offline, 1 is connecting
        return True
    else:
        return False
dkirov's avatar
dkirov committed
344 345

def account_is_disconnected(account):
346
    return not account_is_connected(account)
dkirov's avatar
dkirov committed
347

348
def zeroconf_is_connected():
349 350
    return account_is_connected(ZEROCONF_ACC_NAME) and \
            config.get_per('accounts', ZEROCONF_ACC_NAME, 'is_zeroconf')
351

dkirov's avatar
dkirov committed
352
def get_number_of_securely_connected_accounts():
353 354 355 356 357 358 359 360
    """
    Return the number of the accounts that are SSL/TLS connected
    """
    num_of_secured = 0
    for account in connections.keys():
        if account_is_securely_connected(account):
            num_of_secured += 1
    return num_of_secured
dkirov's avatar
dkirov committed
361 362

def account_is_securely_connected(account):
363 364 365 366 367
    if account_is_connected(account) and \
    account in con_types and con_types[account] in ('tls', 'ssl'):
        return True
    else:
        return False
dkirov's avatar
dkirov committed
368

nkour's avatar
typo  
nkour committed
369
def get_transport_name_from_jid(jid, use_config_setting = True):
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
    """
    Returns 'aim', 'gg', 'irc' etc

    If JID is not from transport returns None.
    """
    #FIXME: jid can be None! one TB I saw had this problem:
    # in the code block # it is a groupchat presence in handle_event_notify
    # jid was None. Yann why?
    if not jid or (use_config_setting and not config.get('use_transports_iconsets')):
        return

    host = get_server_from_jid(jid)
    if host in transport_type:
        return transport_type[host]

    # host is now f.e. icq.foo.org or just icq (sometimes on hacky transports)
    host_splitted = host.split('.')
    if len(host_splitted) != 0:
        # now we support both 'icq.' and 'icq' but not icqsucks.org
        host = host_splitted[0]

    if host in ('aim', 'irc', 'icq', 'msn', 'sms', 'tlen', 'weather', 'yahoo',
    'mrim', 'facebook'):
        return host
    elif host == 'gg':
        return 'gadu-gadu'
    elif host == 'jit':
        return 'icq'
    elif host == 'facebook':
        return 'facebook'
    else:
        return None
nkour's avatar
nkour committed
402 403

def jid_is_transport(jid):
404 405 406 407
    # if not '@' or '@' starts the jid then it is transport
    if jid.find('@') <= 0:
        return True
    return False
408 409

def get_jid_from_account(account_name):
410 411 412 413 414 415 416
    """
    Return the jid we use in the given account
    """
    name = config.get_per('accounts', account_name, 'name')
    hostname = config.get_per('accounts', account_name, 'hostname')
    jid = name + '@' + hostname
    return jid
417

nkour's avatar
nkour committed
418
def get_our_jids():
419 420 421 422 423 424 425
    """
    Returns a list of the jids we use in our accounts
    """
    our_jids = list()
    for account in contacts.get_accounts():
        our_jids.append(get_jid_from_account(account))
    return our_jids
nkour's avatar
nkour committed
426

427
def get_hostname_from_account(account_name, use_srv = False):
428 429 430 431 432 433 434 435
    """
    Returns hostname (if custom hostname is used, that is returned)
    """
    if use_srv and connections[account_name].connected_hostname:
        return connections[account_name].connected_hostname
    if config.get_per('accounts', account_name, 'use_custom_host'):
        return config.get_per('accounts', account_name, 'custom_host')
    return config.get_per('accounts', account_name, 'hostname')
436

437
def get_notification_image_prefix(jid):
438 439 440 441 442 443 444 445 446
    """
    Returns the prefix for the notification images
    """
    transport_name = get_transport_name_from_jid(jid)
    if transport_name in ('aim', 'icq', 'msn', 'yahoo', 'facebook'):
        prefix = transport_name
    else:
        prefix = 'jabber'
    return prefix
447

448
def get_name_from_jid(account, jid):
449 450 451 452 453 454 455 456 457
    """
    Return from JID's shown name and if no contact returns jids
    """
    contact = contacts.get_first_contact_from_jid(account, jid)
    if contact:
        actor = contact.get_shown_name()
    else:
        actor = jid
    return actor
dkirov's avatar
dkirov committed
458 459

def get_priority(account, show):
460 461 462 463 464 465 466 467 468 469
    """
    Return the priority an account must have
    """
    if not show:
        show = 'online'

    if show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible') and \
    config.get_per('accounts', account, 'adjust_priority_with_status'):
        return config.get_per('accounts', account, 'autopriority_' + show)
    return config.get_per('accounts', account, 'priority')