Skip to content

Commit

Permalink
server delegation support for .well-known files
Browse files Browse the repository at this point in the history
  • Loading branch information
caronc committed Sep 2, 2024
1 parent dfa5c61 commit 50d8e36
Show file tree
Hide file tree
Showing 2 changed files with 186 additions and 66 deletions.
151 changes: 104 additions & 47 deletions apprise/plugins/matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ class NotifyMatrix(NotifyBase):
'delegate': {
'name': _('Delegate Server Check'),
'type': 'bool',
'default': False,
'default': True,
},
'mode': {
'name': _('Webhook Mode'),
Expand Down Expand Up @@ -608,11 +608,9 @@ def _send_server_notification(self, body, title='',
self.delegated_host = self.delegated_server_lookup(
timeout=self.srv_lookup_timeout_sec)

if self.delegated_host is False:
self.logger.warning(
'Matrix delegated server could not be acquired.')
# Return; we're done
return False
if self.delegated_host is False:
self.logger.warning(
'Matrix delegated server could not be acquired.')

if self.access_token is None:
# We need to register
Expand Down Expand Up @@ -1231,7 +1229,7 @@ def _fetch(self, path, payload=None, params={}, attachment=None,
default_port = 443 if self.secure else 80

# Server Delegation
if self.delegated_host:
if self.delegate and self.delegated_host:
params.update({'m.server': self.delegated_host})

url = \
Expand All @@ -1256,6 +1254,9 @@ def _fetch(self, path, payload=None, params={}, attachment=None,
# Update our content type
headers['Content-Type'] = attachment.mimetype

elif path == '.well-known':
url += '/.well-known/matrix/server'

else:
if self.version == MatrixVersion.V3:
url += MATRIX_V3_API_PATH + path
Expand Down Expand Up @@ -1358,15 +1359,15 @@ def _fetch(self, path, payload=None, params={}, attachment=None,
self.logger.warning(
'A Connection error occurred while registering with Matrix'
' server.')
self.logger.debug('Socket Exception: %s' % str(e))
self.logger.debug('Socket Exception: %s', str(e))
# Return; we're done
return (False, response)

except (OSError, IOError) as e:
self.logger.warning(
'An I/O error occurred while reading {}.'.format(
attachment.name if attachment else 'unknown file'))
self.logger.debug('I/O Exception: %s' % str(e))
self.logger.debug('I/O Exception: %s', str(e))
return (False, {})

return (True, response)
Expand Down Expand Up @@ -1596,57 +1597,113 @@ def delegated_server_lookup(self, timeout=5):
"""
Attempts to query delegated domain
Returns resolved delegated server if found, otherwise None if not.
Returns resolved delegated server if found, otherwise '' (empty
string) if not. An empty string is intentionally returned over
`None` since `None` is what is used to trigger calls to thi
function.
Returns False if it just failed in general
"""
delegated_host = self.store.get('__delegate')
if delegated_host:
# We can use our cached value
return delegated_host

try:
query = f'_matrix._tcp.{self.host}'
postokay, response = self._fetch('.well-known')
if (postokay and isinstance(
response, dict) and 'm.server' in response):
try:
domain = response.get('m.server').split(':')[0:2]
host = domain.pop(0)
if not is_hostname(host, ipv4=False, ipv6=False):
return False

# Set timeout for the socket
socket.setdefaulttimeout(timeout)
port = None
if domain:
# a port was found
port = int(domain.pop())
# while 0 is an invalid port, we accept it as it gets
# parsed out lower down
if port < 0 or port > 65535:
# Bad port
raise ValueError(f'Bad Port {port}')

# Perform the DNS lookup using getaddrinfo
result = socket.getaddrinfo(
query, None, proto=socket.IPPROTO_TCP, type=socket.SOCK_STREAM)
# If we get here, we have our delegate server
delegated_host = '{}{}'.format(
host, f':{port}' if port else '')

if not result:
# No delegation
self.logger.debug(
'Matrix .well-known resolved to %s', delegated_host)

# Delegation determined, store cache for a while
self.store.set('__delegate', delegated_host, expires=86400)
return delegated_host

except (AttributeError, IndexError, TypeError, ValueError):
# AttributeError: Not a string
# IndexError: no host provided; pass into query lookup
# ValueError: port provided is invalid
# TypeError: bad port provided
self.logger.warning(
'Matrix %s SRV had an empty response', query)
return None

# Parse the result to find the SRV record
for no, res in enumerate(result, start=1):
self.logger.trace('Matrix SRV %02d: %s', no, str(res))
if res[0] == socket.AF_INET:
# Prepare our host and/or port; return first match
host = res[4][0]
port = res[4][1]
delegated_host = '{}{}'.format(
host,
f':{port}' if port else '')
'Matrix .well-known m.server entry is invalid: %s',
str(response.get('m.server')))
return False

self.logger.debug(
'Matrix %s SRV resolved to %s',
query, delegated_host)
queries = (
f'_matrix-fed._tcp.{self.host}', f'_matrix._tcp.{self.host}')

# Delegation determined, store cache for a while
self.store.set('__delegate', delegated_host, expires=86400)
return delegated_host
for query in queries:
try:

# No delegation
self.logger.warning(
'Matrix %s SRV had no delegated server in response')
return None
# Set timeout for the socket
socket.setdefaulttimeout(timeout)

except (socket.gaierror, socket.timeout):
return False
# Perform the DNS lookup using getaddrinfo
result = socket.getaddrinfo(
query, None, proto=socket.IPPROTO_TCP,
type=socket.SOCK_STREAM)

if not result:
# No delegation
self.logger.warning(
'Matrix %s SRV had an empty response', query)
continue

# Parse the result to find the SRV record
for no, res in enumerate(result, start=1):
self.logger.trace('Matrix SRV %02d: %s', no, str(res))
if res[0] == socket.AF_INET:
# Prepare our host and/or port; return first match
host = res[4][0]
port = res[4][1]
delegated_host = '{}{}'.format(
host,
f':{port}' if port else '')

self.logger.debug(
'Matrix %s SRV resolved to %s',
query, delegated_host)

# Delegation determined, store cache for a while
self.store.set(
'__delegate', delegated_host, expires=86400)
return delegated_host

# No delegation
self.logger.warning(
'Matrix %s SRV had no delegated server in response', query)
# Delegation determined, store cache for a while
self.store.set('__delegate', '', expires=86400)
return ''

except (socket.gaierror, socket.timeout) as e:
self.logger.warning('Matrix %s SRV query failed', query)
self.logger.debug('Matrix SRV Exception: %s', str(e))
continue

finally:
# Reset timeout to default (None)
socket.setdefaulttimeout(None)

finally:
# Reset timeout to default (None)
socket.setdefaulttimeout(None)
# Delegation determined, store cache for a while
self.store.set('__delegate', '', expires=86400)
return ''
Loading

0 comments on commit 50d8e36

Please sign in to comment.