summaryrefslogtreecommitdiffhomepage
path: root/libs/apprise/plugins/NotifyXMPP/SleekXmppAdapter.py
blob: a28e9ce54be4150749099b1e5d419a8a9434ef2a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# -*- coding: utf-8 -*-

import ssl
from os.path import isfile
import logging


# Default our global support flag
SLEEKXMPP_SUPPORT_AVAILABLE = False

try:
    # Import sleekxmpp if available
    import sleekxmpp

    SLEEKXMPP_SUPPORT_AVAILABLE = True

except ImportError:
    # No problem; we just simply can't support this plugin because we're
    # either using Linux, or simply do not have sleekxmpp installed.
    pass


class SleekXmppAdapter(object):
    """
    Wrapper to sleekxmpp

    """

    # Reference to XMPP client.
    xmpp = None

    # Whether everything succeeded
    success = False

    # The default protocol
    protocol = 'xmpp'

    # The default secure protocol
    secure_protocol = 'xmpps'

    # The default XMPP port
    default_unsecure_port = 5222

    # The default XMPP secure port
    default_secure_port = 5223

    # Taken from https://golang.org/src/crypto/x509/root_linux.go
    CA_CERTIFICATE_FILE_LOCATIONS = [
        # Debian/Ubuntu/Gentoo etc.
        "/etc/ssl/certs/ca-certificates.crt",
        # Fedora/RHEL 6
        "/etc/pki/tls/certs/ca-bundle.crt",
        # OpenSUSE
        "/etc/ssl/ca-bundle.pem",
        # OpenELEC
        "/etc/pki/tls/cacert.pem",
        # CentOS/RHEL 7
        "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem",
    ]

    # This entry is a bit hacky, but it allows us to unit-test this library
    # in an environment that simply doesn't have the sleekxmpp package
    # available to us.
    #
    # If anyone is seeing this had knows a better way of testing this
    # outside of what is defined in test/test_xmpp_plugin.py, please
    # let me know! :)
    _enabled = SLEEKXMPP_SUPPORT_AVAILABLE

    def __init__(self, host=None, port=None, secure=False,
                 verify_certificate=True, xep=None, jid=None, password=None,
                 body=None, targets=None, before_message=None, logger=None):
        """
        Initialize our SleekXmppAdapter object
        """

        self.host = host
        self.port = port
        self.secure = secure
        self.verify_certificate = verify_certificate

        self.xep = xep
        self.jid = jid
        self.password = password

        self.body = body
        self.targets = targets
        self.before_message = before_message

        self.logger = logger or logging.getLogger(__name__)

        # Use the Apprise log handlers for configuring the sleekxmpp logger.
        apprise_logger = logging.getLogger('apprise')
        sleek_logger = logging.getLogger('sleekxmpp')
        for handler in apprise_logger.handlers:
            sleek_logger.addHandler(handler)
        sleek_logger.setLevel(apprise_logger.level)

        if not self.load():
            raise ValueError("Invalid XMPP Configuration")

    def load(self):

        # Prepare our object
        self.xmpp = sleekxmpp.ClientXMPP(self.jid, self.password)

        # Register our session
        self.xmpp.add_event_handler("session_start", self.session_start)

        for xep in self.xep:
            # Load xep entries
            try:
                self.xmpp.register_plugin('xep_{0:04d}'.format(xep))

            except sleekxmpp.plugins.base.PluginNotFound:
                self.logger.warning(
                    'Could not register plugin {}'.format(
                        'xep_{0:04d}'.format(xep)))
                return False

        if self.secure:
            # Don't even try to use the outdated ssl.PROTOCOL_SSLx
            self.xmpp.ssl_version = ssl.PROTOCOL_TLSv1

            # If the python version supports it, use highest TLS version
            # automatically
            if hasattr(ssl, "PROTOCOL_TLS"):
                # Use the best version of TLS available to us
                self.xmpp.ssl_version = ssl.PROTOCOL_TLS

            self.xmpp.ca_certs = None
            if self.verify_certificate:
                # Set the ca_certs variable for certificate verification
                self.xmpp.ca_certs = next(
                    (cert for cert in self.CA_CERTIFICATE_FILE_LOCATIONS
                     if isfile(cert)), None)

                if self.xmpp.ca_certs is None:
                    self.logger.warning(
                        'XMPP Secure comunication can not be verified; '
                        'no local CA certificate file')
                    return False

        # We're good
        return True

    def process(self):
        """
        Thread that handles the server/client i/o

        """

        # Establish connection to XMPP server.
        # To speed up sending messages, don't use the "reattempt" feature,
        # it will add a nasty delay even before connecting to XMPP server.
        if not self.xmpp.connect((self.host, self.port),
                                 use_ssl=self.secure, reattempt=False):

            default_port = self.default_secure_port \
                if self.secure else self.default_unsecure_port

            default_schema = self.secure_protocol \
                if self.secure else self.protocol

            # Log connection issue
            self.logger.warning(
                'Failed to authenticate {jid} with: {schema}://{host}{port}'
                .format(
                    jid=self.jid,
                    schema=default_schema,
                    host=self.host,
                    port='' if not self.port or self.port == default_port
                         else ':{}'.format(self.port),
                ))
            return False

        # Process XMPP communication.
        self.xmpp.process(block=True)

        return self.success

    def session_start(self, *args, **kwargs):
        """
        Session Manager
        """

        targets = list(self.targets)
        if not targets:
            # We always default to notifying ourselves
            targets.append(self.jid)

        while len(targets) > 0:

            # Get next target (via JID)
            target = targets.pop(0)

            # Invoke "before_message" event hook.
            self.before_message()

            # The message we wish to send, and the JID that will receive it.
            self.xmpp.send_message(mto=target, mbody=self.body, mtype='chat')

        # Using wait=True ensures that the send queue will be
        # emptied before ending the session.
        self.xmpp.disconnect(wait=True)

        # Toggle our success flag
        self.success = True