summaryrefslogtreecommitdiffhomepage
path: root/libs/apprise/plugins/NotifyXMPP/SliXmppAdapter.py
blob: 9450e70d7f666867a39f98d0eea84f679b67e4fc (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
# -*- coding: utf-8 -*-

import ssl
from os.path import isfile
import logging


# Default our global support flag
SLIXMPP_SUPPORT_AVAILABLE = False

try:
    # Import slixmpp if available
    import slixmpp
    import asyncio

    SLIXMPP_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 slixmpp installed.
    pass


class SliXmppAdapter(object):
    """
    Wrapper to slixmpp

    """

    # Reference to XMPP client.
    xmpp = None

    # Whether everything succeeded
    success = False

    # The default protocol
    protocol = 'xmpp'

    # The default secure protocol
    secure_protocol = 'xmpps'

    # 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 slixmpp 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 = SLIXMPP_SUPPORT_AVAILABLE

    def __init__(self, host=None, port=None, secure=False,
                 verify_certificate=True, xep=None, jid=None, password=None,
                 body=None, subject=None, targets=None, before_message=None,
                 logger=None):
        """
        Initialize our SliXmppAdapter 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.subject = subject
        self.targets = targets
        self.before_message = before_message

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

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

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

    def load(self):

        try:
            asyncio.get_event_loop()

        except RuntimeError:
            # slixmpp can not handle not having an event_loop
            # see: https://lab.louiz.org/poezio/slixmpp/-/issues/3456
            # This is a work-around to this problem
            asyncio.set_event_loop(asyncio.new_event_loop())

        # Prepare our object
        self.xmpp = slixmpp.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 slixmpp.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

        # If the user specified a port, skip SRV resolving, otherwise it is a
        # lot easier to let slixmpp handle DNS instead of the user.
        self.override_connection = \
            None if not self.port else (self.host, self.port)

        # We're good
        return True

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

        """

        # Instruct slixmpp to connect to the XMPP service.
        if not self.xmpp.connect(
                self.override_connection, use_ssl=self.secure):
            return False

        # Run the asyncio event loop, and return once disconnected,
        # for any reason.
        self.xmpp.process(forever=False)

        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, msubject=self.subject,
                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