From a34fa3cb935b8411d776cc75310369bdedc2069a Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Sat, 31 Dec 2011 15:54:11 +0100 Subject: [PATCH 01/96] handle MISSING PASSPHRASE in gnupg.py --- src/common/gnupg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/gnupg.py b/src/common/gnupg.py index e3bed526e..f5c703414 100644 --- a/src/common/gnupg.py +++ b/src/common/gnupg.py @@ -962,7 +962,7 @@ class Sign(object): def handle_status(self, key, value): if key in ("USERID_HINT", "NEED_PASSPHRASE", "BAD_PASSPHRASE", - "GOOD_PASSPHRASE", "BEGIN_SIGNING"): + "GOOD_PASSPHRASE", "BEGIN_SIGNING", "MISSING_PASSPHRASE"): pass elif key == "SIG_CREATED": (self.type, From 078291921da3b1d266b72c631b48879966094ade Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Sat, 31 Dec 2011 15:54:32 +0100 Subject: [PATCH 02/96] msn auth is now availble through https --- src/common/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/config.py b/src/common/config.py index 7de2de3e3..87f8b6f44 100644 --- a/src/common/config.py +++ b/src/common/config.py @@ -386,7 +386,7 @@ class Config: 'ft_send_local_ips': [ opt_bool, True, _('If enabled, Gajim will send your local IPs so your contact can connect to your machine to transfer files.')], 'oauth2_refresh_token': [ opt_str, '', _('Latest token for Oauth2 authentication.')], 'oauth2_client_id': [ opt_str, '0000000044077801', _('client_id for Oauth2 authentication.')], - 'oauth2_redirect_url': [ opt_str, 'http%3A%2F%2Fgajim.org%2Fmsnauth%2Findex.cgi', _('redirect_url for Oauth2 authentication.')], + 'oauth2_redirect_url': [ opt_str, 'https%3A%2F%2Fgajim.org%2Fmsnauth%2Findex.cgi', _('redirect_url for Oauth2 authentication.')], }, {}), 'statusmsg': ({ 'message': [ opt_str, '' ], From aa94671708ad66fafba7138da2f36cb0046a80a5 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Mon, 2 Jan 2012 16:39:06 +0100 Subject: [PATCH 03/96] check hostname in SSL certificates. Fixes #7066 --- src/common/check_X509.py | 164 ++++++++++++++++++++++++++++++++++++++ src/common/connection.py | 26 ++++-- src/common/xmpp/tls_nb.py | 2 - 3 files changed, 184 insertions(+), 8 deletions(-) create mode 100644 src/common/check_X509.py diff --git a/src/common/check_X509.py b/src/common/check_X509.py new file mode 100644 index 000000000..ca896eee1 --- /dev/null +++ b/src/common/check_X509.py @@ -0,0 +1,164 @@ +from pyasn1.type import univ, constraint, char, namedtype, tag +from pyasn1.codec.der.decoder import decode +from common.helpers import prep, InvalidFormat + +MAX = 64 +oid_xmppaddr = '(1, 3, 6, 1, 5, 5, 7, 8, 5)' +oid_dnssrv = '(1, 3, 6, 1, 5, 5, 7, 8, 7)' + + + +class DirectoryString(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType( + 'teletexString', char.TeletexString().subtype( + subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + namedtype.NamedType( + 'printableString', char.PrintableString().subtype( + subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + namedtype.NamedType( + 'universalString', char.UniversalString().subtype( + subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + namedtype.NamedType( + 'utf8String', char.UTF8String().subtype( + subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + namedtype.NamedType( + 'bmpString', char.BMPString().subtype( + subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + namedtype.NamedType( + 'ia5String', char.IA5String().subtype( + subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + namedtype.NamedType( + 'gString', univ.OctetString().subtype( + subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + ) + +class AttributeValue(DirectoryString): + pass + +class AttributeType(univ.ObjectIdentifier): + pass + +class AttributeTypeAndValue(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('type', AttributeType()), + namedtype.NamedType('value', AttributeValue()), + ) + +class RelativeDistinguishedName(univ.SetOf): + componentType = AttributeTypeAndValue() + +class RDNSequence(univ.SequenceOf): + componentType = RelativeDistinguishedName() + +class Name(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType('', RDNSequence()), + ) + +class GeneralName(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType('otherName', univ.Sequence().subtype( + implicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatConstructed, 0x0))), + namedtype.NamedType('rfc822Name', char.IA5String().subtype( + implicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatSimple, 1))), + namedtype.NamedType('dNSName', char.IA5String().subtype( + implicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatSimple, 2))), + namedtype.NamedType('x400Address', univ.Sequence().subtype( + implicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatConstructed, 0x3))), + namedtype.NamedType('directoryName', Name().subtype( + implicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatConstructed, 0x4))), + namedtype.NamedType('ediPartyName', univ.Sequence().subtype( + implicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatConstructed, 0x5))), + namedtype.NamedType('uniformResourceIdentifier', + char.IA5String().subtype( + implicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatSimple, 6))), + namedtype.NamedType('iPAddress', univ.OctetString().subtype( + implicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatSimple, 7))), + namedtype.NamedType('registeredID', univ.ObjectIdentifier().subtype( + implicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatSimple, 8))), + ) + +class GeneralNames(univ.SequenceOf): + componentType = GeneralName() + sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, MAX) + + +#s = '0\x1a\x82\rwww.gajim.org\x82\tgajim.org' +s = '0\x81\x86\x82\x0c*.jabber.org\x82\njabber.org\xa0\x1a\x06\x08+\x06\x01\x05\x05\x07\x08\x05\xa0\x0e\x0c\x0c*.jabber.org\xa0\x1a\x06\x08+\x06\x01\x05\x05\x07\x08\x07\xa0\x0e\x16\x0c*.jabber.org\xa0\x18\x06\x08+\x06\x01\x05\x05\x07\x08\x05\xa0\x0c\x0c\njabber.org\xa0\x18\x06\x08+\x06\x01\x05\x05\x07\x08\x07\xa0\x0c\x16\njabber.org' + +def _parse_asn1(asn1): + obj = decode(asn1, asn1Spec=GeneralNames())[0] + r = {} + for o in obj: + name = o.getName() + if name == 'dNSName': + if name not in r: + r[name] = [] + r[name].append(str(o.getComponent())) + if name == 'otherName': + if name not in r: + r[name] = {} + tag = str(tuple(o.getComponent())[0]) + val = str(tuple(o.getComponent())[1]) + if tag not in r[name]: + r[name][tag] = [] + r[name][tag].append(val) + if name == 'uniformResourceIdentifier': + r['uniformResourceIdentifier'] = True + return r + +def check_certificate(cert, domain): + cnt = cert.get_extension_count() + if '.' in domain: + compared_domain = domain.split('.', 1)[1] + else: + compared_domain = '' + srv_domain = '_xmpp-client.' + domain + compared_srv_domain = '_xmpp-client.' + compared_domain + for i in range(0, cnt): + ext = cert.get_extension(i) + if ext.get_short_name() == 'subjectAltName': + r = _parse_asn1(ext.get_data()) + if 'otherName' in r: + if oid_xmppaddr in r['otherName']: + for host in r['otherName'][oid_xmppaddr]: + try: + host = prep(None, host, None) + except InvalidFormat: + continue + if host == domain: + return True + if oid_dnssrv in r['otherName']: + for host in r['otherName'][oid_dnssrv]: + if host.startswith('_xmpp-client.*.'): + if host.replace('*.', '', 1) == compared_srv_domain: + return True + continue + if host == srv_domain: + return True + if 'dNSName' in r: + for host in r['dNSName']: + if host.startswith('*.'): + if host[2:] == compared_domain: + return True + continue + if host == domain: + return True + if r: + return False + break + + subject = cert.get_subject() + if subject.commonName == domain: + return True + return False diff --git a/src/common/connection.py b/src/common/connection.py index 2eaf4a2dc..965b8b7cd 100644 --- a/src/common/connection.py +++ b/src/common/connection.py @@ -58,6 +58,7 @@ from common import gajim from common import gpg from common import passwords from common import exceptions +from common import check_X509 from connection_handlers import * from xmpp import Smacks @@ -98,6 +99,7 @@ ssl_error = { 31: _("Authority and issuer serial number mismatch"), 32: _("Key usage does not include certificate signing"), 50: _("Application verification failure") +#100 is for internal usage: host not correct } class CommonConnection: @@ -1276,9 +1278,9 @@ class Connection(CommonConnection, ConnectionHandlers): except AttributeError: errnum = -1 # we don't have an errnum if errnum > 0 and str(errnum) not in gajim.config.get_per('accounts', - self.name, 'ignore_ssl_errors'): - text = _('The authenticity of the %s certificate could be invalid.') %\ - hostname + self.name, 'ignore_ssl_errors').split(): + text = _('The authenticity of the %s certificate could be invalid.' + ) % hostname if errnum in ssl_error: text += _('\nSSL Error: %s') % ssl_error[errnum] else: @@ -1290,7 +1292,8 @@ class Connection(CommonConnection, ConnectionHandlers): certificate=con.Connection.ssl_certificate)) return True if hasattr(con.Connection, 'ssl_fingerprint_sha1'): - saved_fingerprint = gajim.config.get_per('accounts', self.name, 'ssl_fingerprint_sha1') + saved_fingerprint = gajim.config.get_per('accounts', self.name, + 'ssl_fingerprint_sha1') if saved_fingerprint: # Check sha1 fingerprint if con.Connection.ssl_fingerprint_sha1 != saved_fingerprint: @@ -1299,8 +1302,19 @@ class Connection(CommonConnection, ConnectionHandlers): new_fingerprint=con.Connection.ssl_fingerprint_sha1)) return True else: - gajim.config.set_per('accounts', self.name, 'ssl_fingerprint_sha1', - con.Connection.ssl_fingerprint_sha1) + gajim.config.set_per('accounts', self.name, + 'ssl_fingerprint_sha1', con.Connection.ssl_fingerprint_sha1) + if not check_X509.check_certificate(con.Connection.ssl_certificate, + hostname) and '100' not in gajim.config.get_per('accounts', self.name, + 'ignore_ssl_errors').split(): + txt = _('The authenticity of the %s certificate could be invalid.' + '\nThe certificate does not cover this domain.') % hostname + gajim.nec.push_incoming_event(SSLErrorEvent(None, conn=self, + error_text=txt, error_num=100, cert=con.Connection.ssl_cert_pem, + fingerprint=con.Connection.ssl_fingerprint_sha1, + certificate=con.Connection.ssl_certificate)) + return True + self._register_handlers(con, con_type) con.auth( user=name, diff --git a/src/common/xmpp/tls_nb.py b/src/common/xmpp/tls_nb.py index cc8393d52..575960fca 100644 --- a/src/common/xmpp/tls_nb.py +++ b/src/common/xmpp/tls_nb.py @@ -451,8 +451,6 @@ class NonBlockingTLS(PlugIn): try: self._owner.ssl_fingerprint_sha1 = cert.digest('sha1') self._owner.ssl_certificate = cert - if errnum == 0: - return True self._owner.ssl_errnum = errnum self._owner.ssl_cert_pem = OpenSSL.crypto.dump_certificate( OpenSSL.crypto.FILETYPE_PEM, cert) From 9804572772f3b21e122b286b9784ff9e1f02a6db Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Mon, 2 Jan 2012 17:01:31 +0100 Subject: [PATCH 04/96] fix starting Gajim when pyasn1 is not installed. see #7066 --- src/common/check_X509.py | 303 ++++++++++++++++++++------------------- 1 file changed, 157 insertions(+), 146 deletions(-) diff --git a/src/common/check_X509.py b/src/common/check_X509.py index ca896eee1..6605be707 100644 --- a/src/common/check_X509.py +++ b/src/common/check_X509.py @@ -1,164 +1,175 @@ -from pyasn1.type import univ, constraint, char, namedtype, tag -from pyasn1.codec.der.decoder import decode -from common.helpers import prep, InvalidFormat +import logging +log = logging.getLogger('gajim.c.check_X509') -MAX = 64 -oid_xmppaddr = '(1, 3, 6, 1, 5, 5, 7, 8, 5)' -oid_dnssrv = '(1, 3, 6, 1, 5, 5, 7, 8, 7)' +try: + import OpenSSL.SSL + import OpenSSL.crypto + from pyasn1.type import univ, constraint, char, namedtype, tag + from pyasn1.codec.der.decoder import decode + from common.helpers import prep, InvalidFormat + + MAX = 64 + oid_xmppaddr = '(1, 3, 6, 1, 5, 5, 7, 8, 5)' + oid_dnssrv = '(1, 3, 6, 1, 5, 5, 7, 8, 7)' -class DirectoryString(univ.Choice): - componentType = namedtype.NamedTypes( - namedtype.NamedType( - 'teletexString', char.TeletexString().subtype( - subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), - namedtype.NamedType( - 'printableString', char.PrintableString().subtype( - subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), - namedtype.NamedType( - 'universalString', char.UniversalString().subtype( - subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), - namedtype.NamedType( - 'utf8String', char.UTF8String().subtype( - subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), - namedtype.NamedType( - 'bmpString', char.BMPString().subtype( - subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), - namedtype.NamedType( - 'ia5String', char.IA5String().subtype( - subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), - namedtype.NamedType( - 'gString', univ.OctetString().subtype( - subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), - ) + class DirectoryString(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType( + 'teletexString', char.TeletexString().subtype( + subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + namedtype.NamedType( + 'printableString', char.PrintableString().subtype( + subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + namedtype.NamedType( + 'universalString', char.UniversalString().subtype( + subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + namedtype.NamedType( + 'utf8String', char.UTF8String().subtype( + subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + namedtype.NamedType( + 'bmpString', char.BMPString().subtype( + subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + namedtype.NamedType( + 'ia5String', char.IA5String().subtype( + subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + namedtype.NamedType( + 'gString', univ.OctetString().subtype( + subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + ) -class AttributeValue(DirectoryString): - pass + class AttributeValue(DirectoryString): + pass -class AttributeType(univ.ObjectIdentifier): - pass + class AttributeType(univ.ObjectIdentifier): + pass -class AttributeTypeAndValue(univ.Sequence): - componentType = namedtype.NamedTypes( - namedtype.NamedType('type', AttributeType()), - namedtype.NamedType('value', AttributeValue()), - ) + class AttributeTypeAndValue(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('type', AttributeType()), + namedtype.NamedType('value', AttributeValue()), + ) -class RelativeDistinguishedName(univ.SetOf): - componentType = AttributeTypeAndValue() + class RelativeDistinguishedName(univ.SetOf): + componentType = AttributeTypeAndValue() -class RDNSequence(univ.SequenceOf): - componentType = RelativeDistinguishedName() + class RDNSequence(univ.SequenceOf): + componentType = RelativeDistinguishedName() -class Name(univ.Choice): - componentType = namedtype.NamedTypes( - namedtype.NamedType('', RDNSequence()), - ) + class Name(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType('', RDNSequence()), + ) -class GeneralName(univ.Choice): - componentType = namedtype.NamedTypes( - namedtype.NamedType('otherName', univ.Sequence().subtype( - implicitTag=tag.Tag(tag.tagClassContext, - tag.tagFormatConstructed, 0x0))), - namedtype.NamedType('rfc822Name', char.IA5String().subtype( - implicitTag=tag.Tag(tag.tagClassContext, - tag.tagFormatSimple, 1))), - namedtype.NamedType('dNSName', char.IA5String().subtype( - implicitTag=tag.Tag(tag.tagClassContext, - tag.tagFormatSimple, 2))), - namedtype.NamedType('x400Address', univ.Sequence().subtype( - implicitTag=tag.Tag(tag.tagClassContext, - tag.tagFormatConstructed, 0x3))), - namedtype.NamedType('directoryName', Name().subtype( - implicitTag=tag.Tag(tag.tagClassContext, - tag.tagFormatConstructed, 0x4))), - namedtype.NamedType('ediPartyName', univ.Sequence().subtype( - implicitTag=tag.Tag(tag.tagClassContext, - tag.tagFormatConstructed, 0x5))), - namedtype.NamedType('uniformResourceIdentifier', - char.IA5String().subtype( - implicitTag=tag.Tag(tag.tagClassContext, - tag.tagFormatSimple, 6))), - namedtype.NamedType('iPAddress', univ.OctetString().subtype( - implicitTag=tag.Tag(tag.tagClassContext, - tag.tagFormatSimple, 7))), - namedtype.NamedType('registeredID', univ.ObjectIdentifier().subtype( - implicitTag=tag.Tag(tag.tagClassContext, - tag.tagFormatSimple, 8))), - ) + class GeneralName(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType('otherName', univ.Sequence().subtype( + implicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatConstructed, 0x0))), + namedtype.NamedType('rfc822Name', char.IA5String().subtype( + implicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatSimple, 1))), + namedtype.NamedType('dNSName', char.IA5String().subtype( + implicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatSimple, 2))), + namedtype.NamedType('x400Address', univ.Sequence().subtype( + implicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatConstructed, 0x3))), + namedtype.NamedType('directoryName', Name().subtype( + implicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatConstructed, 0x4))), + namedtype.NamedType('ediPartyName', univ.Sequence().subtype( + implicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatConstructed, 0x5))), + namedtype.NamedType('uniformResourceIdentifier', + char.IA5String().subtype( + implicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatSimple, 6))), + namedtype.NamedType('iPAddress', univ.OctetString().subtype( + implicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatSimple, 7))), + namedtype.NamedType('registeredID', univ.ObjectIdentifier().subtype( + implicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatSimple, 8))), + ) -class GeneralNames(univ.SequenceOf): - componentType = GeneralName() - sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, MAX) + class GeneralNames(univ.SequenceOf): + componentType = GeneralName() + sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, MAX) + def _parse_asn1(asn1): + obj = decode(asn1, asn1Spec=GeneralNames())[0] + r = {} + for o in obj: + name = o.getName() + if name == 'dNSName': + if name not in r: + r[name] = [] + r[name].append(str(o.getComponent())) + if name == 'otherName': + if name not in r: + r[name] = {} + tag = str(tuple(o.getComponent())[0]) + val = str(tuple(o.getComponent())[1]) + if tag not in r[name]: + r[name][tag] = [] + r[name][tag].append(val) + if name == 'uniformResourceIdentifier': + r['uniformResourceIdentifier'] = True + return r -#s = '0\x1a\x82\rwww.gajim.org\x82\tgajim.org' -s = '0\x81\x86\x82\x0c*.jabber.org\x82\njabber.org\xa0\x1a\x06\x08+\x06\x01\x05\x05\x07\x08\x05\xa0\x0e\x0c\x0c*.jabber.org\xa0\x1a\x06\x08+\x06\x01\x05\x05\x07\x08\x07\xa0\x0e\x16\x0c*.jabber.org\xa0\x18\x06\x08+\x06\x01\x05\x05\x07\x08\x05\xa0\x0c\x0c\njabber.org\xa0\x18\x06\x08+\x06\x01\x05\x05\x07\x08\x07\xa0\x0c\x16\njabber.org' - -def _parse_asn1(asn1): - obj = decode(asn1, asn1Spec=GeneralNames())[0] - r = {} - for o in obj: - name = o.getName() - if name == 'dNSName': - if name not in r: - r[name] = [] - r[name].append(str(o.getComponent())) - if name == 'otherName': - if name not in r: - r[name] = {} - tag = str(tuple(o.getComponent())[0]) - val = str(tuple(o.getComponent())[1]) - if tag not in r[name]: - r[name][tag] = [] - r[name][tag].append(val) - if name == 'uniformResourceIdentifier': - r['uniformResourceIdentifier'] = True - return r - -def check_certificate(cert, domain): - cnt = cert.get_extension_count() - if '.' in domain: - compared_domain = domain.split('.', 1)[1] - else: - compared_domain = '' - srv_domain = '_xmpp-client.' + domain - compared_srv_domain = '_xmpp-client.' + compared_domain - for i in range(0, cnt): - ext = cert.get_extension(i) - if ext.get_short_name() == 'subjectAltName': - r = _parse_asn1(ext.get_data()) - if 'otherName' in r: - if oid_xmppaddr in r['otherName']: - for host in r['otherName'][oid_xmppaddr]: - try: - host = prep(None, host, None) - except InvalidFormat: + def check_certificate(cert, domain): + cnt = cert.get_extension_count() + if '.' in domain: + compared_domain = domain.split('.', 1)[1] + else: + compared_domain = '' + srv_domain = '_xmpp-client.' + domain + compared_srv_domain = '_xmpp-client.' + compared_domain + for i in range(0, cnt): + ext = cert.get_extension(i) + if ext.get_short_name() == 'subjectAltName': + r = _parse_asn1(ext.get_data()) + if 'otherName' in r: + if oid_xmppaddr in r['otherName']: + for host in r['otherName'][oid_xmppaddr]: + try: + host = prep(None, host, None) + except InvalidFormat: + continue + if host == domain: + return True + if oid_dnssrv in r['otherName']: + for host in r['otherName'][oid_dnssrv]: + if host.startswith('_xmpp-client.*.'): + if host.replace('*.', '', 1) == compared_srv_domain: + return True + continue + if host == srv_domain: + return True + if 'dNSName' in r: + for host in r['dNSName']: + if host.startswith('*.'): + if host[2:] == compared_domain: + return True continue if host == domain: return True - if oid_dnssrv in r['otherName']: - for host in r['otherName'][oid_dnssrv]: - if host.startswith('_xmpp-client.*.'): - if host.replace('*.', '', 1) == compared_srv_domain: - return True - continue - if host == srv_domain: - return True - if 'dNSName' in r: - for host in r['dNSName']: - if host.startswith('*.'): - if host[2:] == compared_domain: - return True - continue - if host == domain: - return True - if r: - return False - break + if r: + return False + break - subject = cert.get_subject() - if subject.commonName == domain: - return True - return False + subject = cert.get_subject() + if subject.commonName == domain: + return True + return False +except ImportError: + log.warn('Import of PyOpenSSL or pyasn1 failed. Cannot correctly check ' + 'SSL certificate') + + def check_certificate(cert, domain): + subject = cert.get_subject() + if subject.commonName == domain: + return True + return False From 8e72a239189935584fb5b43c902e8734f3e4d6ac Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Tue, 3 Jan 2012 20:17:33 +0100 Subject: [PATCH 05/96] use 'login' keyring as default keyring instead of 'default'. Fixes #7067 --- src/common/passwords.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/passwords.py b/src/common/passwords.py index c6fcc36c6..e810f72c0 100644 --- a/src/common/passwords.py +++ b/src/common/passwords.py @@ -63,7 +63,7 @@ class GnomePasswordStorage(PasswordStorage): def __init__(self): self.keyring = gnomekeyring.get_default_keyring_sync() if self.keyring is None: - self.keyring = 'default' + self.keyring = 'login' try: gnomekeyring.create_sync(self.keyring, None) except gnomekeyring.AlreadyExistsError: From 359d3c3069c1e947bfbc1684b67121e425a71520 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Wed, 4 Jan 2012 11:38:09 +0100 Subject: [PATCH 06/96] check that pyopenssl > 0.12 is installed before using fonctions available after this version --- src/common/check_X509.py | 4 ++++ src/features_window.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/common/check_X509.py b/src/common/check_X509.py index 6605be707..e7af52a8e 100644 --- a/src/common/check_X509.py +++ b/src/common/check_X509.py @@ -4,6 +4,10 @@ log = logging.getLogger('gajim.c.check_X509') 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 from pyasn1.type import univ, constraint, char, namedtype, tag from pyasn1.codec.der.decoder import decode from common.helpers import prep, InvalidFormat diff --git a/src/features_window.py b/src/features_window.py index 79ffd6745..a7137b36e 100644 --- a/src/features_window.py +++ b/src/features_window.py @@ -49,8 +49,8 @@ class FeaturesWindow: self.features = { _('SSL certificate validation'): (self.pyopenssl_available, _('A library used to validate server certificates to ensure a secure connection.'), - _('Requires python-pyopenssl.'), - _('Requires python-pyopenssl.')), + _('Requires python-pyopenssl > 0.12 and pyasn1.'), + _('Requires python-pyopenssl > 0.12 and pyasn1.')), _('Bonjour / Zeroconf'): (self.zeroconf_available, _('Serverless chatting with autodetected clients in a local network.'), _('Requires python-avahi.'), From d0a23f3200f8ff1b121041190eb69c930069e660 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Thu, 5 Jan 2012 11:56:22 +0100 Subject: [PATCH 07/96] generate POTFILES.skip in autogen.sh to skip plugins from translation. add command system files to translated files. Fixes #7068 --- autogen.sh | 6 ++++-- po/POTFILES.skip | 3 --- 2 files changed, 4 insertions(+), 5 deletions(-) delete mode 100644 po/POTFILES.skip diff --git a/autogen.sh b/autogen.sh index b2e46611a..0f6b2ea86 100755 --- a/autogen.sh +++ b/autogen.sh @@ -20,8 +20,10 @@ && for p in `ls data/gui/*.ui`; do echo "[type: gettext/glade]$p" >> \ po/POTFILES.in; done \ && ls -1 data/gajim.desktop.in.in \ - src/*py src/common/*py src/common/zeroconf/*.py src/plugins/*.py| grep -v ipython_view.py >> \ - po/POTFILES.in || exit 1 + src/*.py src/common/*.py src/command_system/implementation/*.py src/common/zeroconf/*.py src/plugins/*.py | grep -v ipython_view.py >> \ + po/POTFILES.in \ + && echo -e "data/gajim.desktop.in\nsrc/ipython_view.py" > po/POTFILES.skip \ + && ls -1 plugins/*/*.py plugins/*/*.ui >> po/POTFILES.skip || exit 1 if test -z `which pkg-config 2>/dev/null`;then echo "***Error: pkg-config not found***" echo "See README.html for build requirements." diff --git a/po/POTFILES.skip b/po/POTFILES.skip deleted file mode 100644 index 26c1c02d1..000000000 --- a/po/POTFILES.skip +++ /dev/null @@ -1,3 +0,0 @@ -data/gajim.desktop.in -src/eggtrayicon.c -src/ipython_view.py From bae57a600aa355c72ca7a1dd52df3a05e5592f76 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Thu, 5 Jan 2012 13:50:15 +0100 Subject: [PATCH 08/96] check in feature window if pyopenssl >= 0.12 and if pyasn1 is available --- src/features_window.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/features_window.py b/src/features_window.py index a7137b36e..303b54f61 100644 --- a/src/features_window.py +++ b/src/features_window.py @@ -169,6 +169,11 @@ class FeaturesWindow: 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 + import pyasn1 except Exception: return False return True From d2c108d928d66130dc02bc9384d997ae4451ace8 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Thu, 5 Jan 2012 20:09:33 +0100 Subject: [PATCH 09/96] make /status /away and /back commands apply to connected and synchronized accounts. Fixes #7070 --- src/command_system/implementation/standard.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/command_system/implementation/standard.py b/src/command_system/implementation/standard.py index 8f39e939a..670c6200e 100644 --- a/src/command_system/implementation/standard.py +++ b/src/command_system/implementation/standard.py @@ -125,6 +125,17 @@ class StandardCommonCommands(CommandContainer): self.echo(formatted) + def _get_connected_accounts(self): + conns = [] + for conn in gajim.connections.itervalues(): + if not gajim.config.get_per('accounts', conn.name, + 'sync_with_global_status'): + continue + if conn.connected <= 2: + continue + conns.append(conn) + return conns + @command(raw=True, empty=True) @doc(_(""" Set current the status @@ -135,7 +146,7 @@ class StandardCommonCommands(CommandContainer): def status(self, status, message): if status not in ('online', 'away', 'chat', 'xa', 'dnd'): raise CommandError("Invalid status given") - for connection in gajim.connections.itervalues(): + for connection in self._get_connected_accounts(): connection.change_status(status, message) @command(raw=True, empty=True) @@ -143,7 +154,7 @@ class StandardCommonCommands(CommandContainer): def away(self, message): if not message: message = _("Away") - for connection in gajim.connections.itervalues(): + for connection in self._get_connected_accounts(): connection.change_status('away', message) @command('back', raw=True, empty=True) @@ -151,7 +162,7 @@ class StandardCommonCommands(CommandContainer): def online(self, message): if not message: message = _("Available") - for connection in gajim.connections.itervalues(): + for connection in self._get_connected_accounts(): connection.change_status('online', message) class StandardCommonChatCommands(CommandContainer): From 140a2bdaf5c17994c1392a7c662fc5d6739d4518 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Fri, 6 Jan 2012 09:45:11 +0100 Subject: [PATCH 10/96] check ssl certificate against hostname only if we use secured connection. Fixes #7071 --- src/common/connection.py | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/common/connection.py b/src/common/connection.py index 965b8b7cd..1060b38f6 100644 --- a/src/common/connection.py +++ b/src/common/connection.py @@ -1304,24 +1304,22 @@ class Connection(CommonConnection, ConnectionHandlers): else: gajim.config.set_per('accounts', self.name, 'ssl_fingerprint_sha1', con.Connection.ssl_fingerprint_sha1) - if not check_X509.check_certificate(con.Connection.ssl_certificate, - hostname) and '100' not in gajim.config.get_per('accounts', self.name, - 'ignore_ssl_errors').split(): - txt = _('The authenticity of the %s certificate could be invalid.' - '\nThe certificate does not cover this domain.') % hostname - gajim.nec.push_incoming_event(SSLErrorEvent(None, conn=self, - error_text=txt, error_num=100, cert=con.Connection.ssl_cert_pem, - fingerprint=con.Connection.ssl_fingerprint_sha1, - certificate=con.Connection.ssl_certificate)) - return True + if not check_X509.check_certificate(con.Connection.ssl_certificate, + hostname) and '100' not in gajim.config.get_per('accounts', + self.name, 'ignore_ssl_errors').split(): + txt = _('The authenticity of the %s certificate could be ' + 'invalid.\nThe certificate does not cover this domain.') % \ + hostname + gajim.nec.push_incoming_event(SSLErrorEvent(None, conn=self, + error_text=txt, error_num=100, + cert=con.Connection.ssl_cert_pem, + fingerprint=con.Connection.ssl_fingerprint_sha1, + certificate=con.Connection.ssl_certificate)) + return True self._register_handlers(con, con_type) - con.auth( - user=name, - password=self.password, - resource=self.server_resource, - sasl=1, - on_auth=self.__on_auth) + con.auth(user=name, password=self.password, + resource=self.server_resource, sasl=1, on_auth=self.__on_auth) def ssl_certificate_accepted(self): if not self.connection: From 71e6f991e0dc729ffffe2f25496a3d89c7e34207 Mon Sep 17 00:00:00 2001 From: Denis Fomin Date: Sat, 7 Jan 2012 12:00:04 +0400 Subject: [PATCH 11/96] Prevent traceback --- src/plugins/pluginmanager.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/plugins/pluginmanager.py b/src/plugins/pluginmanager.py index 875c64356..71bf6c469 100644 --- a/src/plugins/pluginmanager.py +++ b/src/plugins/pluginmanager.py @@ -475,7 +475,10 @@ class PluginManager(object): except TypeError, type_error: # set plugin localization - module_attr._ = _ + try: + module_attr._ = _ + except AttributeError, type_error: + pass except ConfigParser.NoOptionError, type_error: # all fields are required log.debug('%s : %s' % (module_attr_name, From 82413cafaa664e412cd165f61e070c54fa0c8776 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Sat, 7 Jan 2012 15:41:53 +0100 Subject: [PATCH 12/96] [Pablo Mazzini] ask passphrase to user when using non-sasl authentication --- src/common/xmpp/auth_nb.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/common/xmpp/auth_nb.py b/src/common/xmpp/auth_nb.py index 1063ed9a7..10d65abed 100644 --- a/src/common/xmpp/auth_nb.py +++ b/src/common/xmpp/auth_nb.py @@ -577,8 +577,19 @@ class NonBlockingNonSASL(PlugIn): else: log.warn("Secure methods unsupported, performing plain text \ authentication") - query.setTagData('password', self.password) self._method = 'plain' + self._owner._caller.get_password(self._on_password, self._method) + return + resp = self.owner.Dispatcher.SendAndWaitForResponse(iq, + func=self._on_auth) + + def _on_password(self, password): + self.password = '' if password is None else password + iq=Iq('set', NS_AUTH) + query = iq.getTag('query') + query.setTagData('username', self.user) + query.setTagData('resource', self.resource) + query.setTagData('password', self.password) resp = self.owner.Dispatcher.SendAndWaitForResponse(iq, func=self._on_auth) From f4451af64c23d8613e3cca03dc61188934f4282a Mon Sep 17 00:00:00 2001 From: Denis Fomin Date: Mon, 9 Jan 2012 14:57:21 +0400 Subject: [PATCH 13/96] coding style --- src/roster_window.py | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/roster_window.py b/src/roster_window.py index 1bd218a53..645142d14 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -4765,22 +4765,21 @@ class RosterWindow: """ Initialize opened and closed 'transport' iconset dict """ - if gajim.config.get('use_transports_iconsets'): - folder = os.path.join(helpers.get_transport_path(transport), - '16x16') - pixo, pixc = gtkgui_helpers.load_icons_meta() - self.transports_state_images['opened'][transport] = \ - gtkgui_helpers.load_iconset(folder, pixo, transport=True) - self.transports_state_images['closed'][transport] = \ - gtkgui_helpers.load_iconset(folder, pixc, transport=True) - folder = os.path.join(helpers.get_transport_path(transport), - '32x32') - self.transports_state_images['32'][transport] = \ - gtkgui_helpers.load_iconset(folder, transport=True) - folder = os.path.join(helpers.get_transport_path(transport), - '16x16') - self.transports_state_images['16'][transport] = \ - gtkgui_helpers.load_iconset(folder, transport=True) + if not gajim.config.get('use_transports_iconsets'): + return + + folder = os.path.join(helpers.get_transport_path(transport), '32x32') + self.transports_state_images['32'][transport] = \ + gtkgui_helpers.load_iconset(folder, transport=True) + folder = os.path.join(helpers.get_transport_path(transport), '16x16') + self.transports_state_images['16'][transport] = \ + gtkgui_helpers.load_iconset(folder, transport=True) + + pixo, pixc = gtkgui_helpers.load_icons_meta() + self.transports_state_images['opened'][transport] = \ + gtkgui_helpers.load_iconset(folder, pixo, transport=True) + self.transports_state_images['closed'][transport] = \ + gtkgui_helpers.load_iconset(folder, pixc, transport=True) def update_jabber_state_images(self): # Update the roster From d4f6ac9307a0536a9679b05499649a241a4f44d6 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Mon, 9 Jan 2012 17:40:02 +0100 Subject: [PATCH 14/96] fix OOB registration. Fixes #6957 --- src/config.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/config.py b/src/config.py index 379877629..8b6d1c11f 100644 --- a/src/config.py +++ b/src/config.py @@ -3803,23 +3803,24 @@ class AccountCreationWizardWindow: self.back_button.show() self.forward_button.show() self.is_form = obj.is_form + empty_config = True if obj.is_form: dataform = dataforms.ExtendForm(node=obj.config) self.data_form_widget = dataforms_widget.DataFormWidget(dataform) + empty_config = False else: self.data_form_widget = FakeDataForm(obj.config) - empty_config = True for field in obj.config: if field in ('key', 'instructions', 'x', 'registered'): continue empty_config = False break - if empty_config: - self.forward_button.set_sensitive(False) - self.notebook.set_current_page(4) # show form page - return self.data_form_widget.show_all() self.xml.get_object('form_vbox').pack_start(self.data_form_widget) + if empty_config: + self.forward_button.set_sensitive(False) + self.notebook.set_current_page(4) # show form page + return self.ssl_fingerprint = obj.ssl_fingerprint self.ssl_cert = obj.ssl_cert if obj.ssl_msg: From 9e2c1caeac82d7f82995a25c23242afe8d14eb8b Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Wed, 11 Jan 2012 20:44:49 +0100 Subject: [PATCH 15/96] fix hidding info bar in chat control --- src/chat_control.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/chat_control.py b/src/chat_control.py index 33bf6c950..f60e030a0 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -3278,6 +3278,7 @@ class ChatControl(ChatControlBase): self.info_bar_queue.remove(ib_msg) if i == 0: # We are removing the one currently displayed + self.info_bar.set_no_show_all(True) self.info_bar.hide() # show next one? gobject.idle_add(self._info_bar_show_message) From d81deb290a37bccb6fc49a7daddd14314e8f3096 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Wed, 11 Jan 2012 22:00:27 +0100 Subject: [PATCH 16/96] update README.html for dependancies --- README.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.html b/README.html index df3362337..640fefcfc 100644 --- a/README.html +++ b/README.html @@ -20,7 +20,8 @@

Optional Runtime Requirements