Compare commits
4 commits
master
...
jessie-bac
Author | SHA1 | Date | |
---|---|---|---|
9ba92dd9f0 | |||
6d5ee349df | |||
abbab375e4 | |||
36b461f709 |
13 changed files with 228 additions and 324 deletions
28
README.md
28
README.md
|
@ -1,4 +1,4 @@
|
||||||
# `check_xmppng` - check plugin for XMPP
|
# check_xmppng - check plugin for XMPP
|
||||||
|
|
||||||
This program implements a nagios check plugin for XMPP servers implementing the
|
This program implements a nagios check plugin for XMPP servers implementing the
|
||||||
XMPP protocol as specified in [RFC 6120](http://tools.ietf.org/html/rfc6120).
|
XMPP protocol as specified in [RFC 6120](http://tools.ietf.org/html/rfc6120).
|
||||||
|
@ -10,9 +10,8 @@ The program implements the following features:
|
||||||
* support STARTTLS as specified in RFC 6120 section 5.
|
* support STARTTLS as specified in RFC 6120 section 5.
|
||||||
* check the validity of the server certificate presented by the XMPP server
|
* check the validity of the server certificate presented by the XMPP server
|
||||||
|
|
||||||
The plugin has been implemented because of insufficiencies in `check_ssl_cert`
|
The plugin has been implemented because of insufficiencies in check_ssl_cert
|
||||||
and the existing
|
and the existing [check_xmpp](https://exchange.icinga.org/exchange/check_xmpp).
|
||||||
[`check_xmpp`](https://exchange.icinga.org/exchange/check_xmpp).
|
|
||||||
|
|
||||||
Maximum acceptable timeouts as well as minimum acceptable number of days the
|
Maximum acceptable timeouts as well as minimum acceptable number of days the
|
||||||
server certificate needs to be valid can be specified as command line
|
server certificate needs to be valid can be specified as command line
|
||||||
|
@ -31,14 +30,14 @@ the Python standard library:
|
||||||
|
|
||||||
The software has been developed and tested with the following versions:
|
The software has been developed and tested with the following versions:
|
||||||
|
|
||||||
* Python 3.7.3
|
* Python 3.4.2
|
||||||
* defusedxml 0.5.0
|
* defusedxml 0.4.1
|
||||||
* nagiosplugin 1.2.4
|
* nagiosplugin 1.2.2
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
`check_xmppng` is free software: you can redistribute it and/or modify it under
|
check_xmppng is free software: you can redistribute it and/or modify it under
|
||||||
the terms of the GNU General Public License as published by the Free Software
|
the terms of the GNU General Public License as published by the Free Software
|
||||||
Foundation, either version 3 of the License, or (at your option) any later
|
Foundation, either version 3 of the License, or (at your option) any later
|
||||||
version.
|
version.
|
||||||
|
@ -55,10 +54,10 @@ http://www.gnu.org/licenses/.
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
usage: check_xmppng [-h] -H HOST_ADDRESS [-p PORT] [--s2s | --c2s] [-4 | -6]
|
usage: check_xmpp [-h] -H HOST_ADDRESS [-p PORT] [--s2s | --c2s] [-4 | -6]
|
||||||
[--servername SERVERNAME] [--starttls] [-w SECONDS]
|
[--servername SERVERNAME] [--starttls] [-w SECONDS]
|
||||||
[-c SECONDS] [--no-check-certificates] [-r CAROOTS]
|
[-c SECONDS] [--no-check-certificates] [-r CAROOTS]
|
||||||
[--warn-days WARNDAYS] [--crit-days CRITDAYS] [-v]
|
[--warn-days WARNDAYS] [--crit-days CRITDAYS] [-v]
|
||||||
|
|
||||||
Check XMPP services
|
Check XMPP services
|
||||||
|
|
||||||
|
@ -96,6 +95,5 @@ optional arguments:
|
||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
|
|
||||||
If you want to provide feedback or bug reports please use the [bug
|
If you want to provide feedback or bug reports please send me a mail to
|
||||||
tracker](https://git.dittberner.info/jan/check_xmppng/issues) or send me an
|
jan (at) dittberner [dot] info.
|
||||||
email to jan (at) dittberner [dot] info.
|
|
||||||
|
|
26
changes.md
26
changes.md
|
@ -1,31 +1,5 @@
|
||||||
# change log
|
# change log
|
||||||
|
|
||||||
## version 0.3.3 2023-08-04
|
|
||||||
|
|
||||||
* fix starttls behaviour with Python 3.11
|
|
||||||
|
|
||||||
## version 0.3.2 2021-03-07
|
|
||||||
|
|
||||||
* remove broken CA certificate statistics
|
|
||||||
|
|
||||||
## version 0.3.1 2019-06-23
|
|
||||||
|
|
||||||
* move to new project location
|
|
||||||
|
|
||||||
## version 0.3.0 2016-06-18
|
|
||||||
|
|
||||||
* add alternative --hostname parameter to make check_xmpp compatible with
|
|
||||||
check_v46 (thanks to Andreas Krause for the idea)
|
|
||||||
|
|
||||||
## version 0.2.1 2016-01-23
|
|
||||||
|
|
||||||
* fix perfdata output for daysvalid metric
|
|
||||||
|
|
||||||
## version 0.2 2015-12-29
|
|
||||||
|
|
||||||
* improve human readable output by returning both response time as well as
|
|
||||||
certificate expiry
|
|
||||||
|
|
||||||
## version 0.1.2 2015-02-11
|
## version 0.1.2 2015-02-11
|
||||||
|
|
||||||
* first icinga exchange release
|
* first icinga exchange release
|
||||||
|
|
270
check_xmppng
270
check_xmppng
|
@ -2,7 +2,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Nagios compatible check for XMPP servers.
|
# Nagios compatible check for XMPP servers.
|
||||||
# Copyright (C) 2015-2021 Jan Dittberner
|
# Copyright (C) 2015 Jan Dittberner
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -31,7 +31,8 @@ from defusedxml.sax import make_parser
|
||||||
import nagiosplugin
|
import nagiosplugin
|
||||||
|
|
||||||
__author__ = "Jan Dittberner"
|
__author__ = "Jan Dittberner"
|
||||||
__version__ = "0.3.2"
|
__version__ = "0.1.2"
|
||||||
|
|
||||||
|
|
||||||
NS_IETF_XMPP_SASL = 'urn:ietf:params:xml:ns:xmpp-sasl'
|
NS_IETF_XMPP_SASL = 'urn:ietf:params:xml:ns:xmpp-sasl'
|
||||||
NS_IETF_XMPP_TLS = 'urn:ietf:params:xml:ns:xmpp-tls'
|
NS_IETF_XMPP_TLS = 'urn:ietf:params:xml:ns:xmpp-tls'
|
||||||
|
@ -54,14 +55,10 @@ class XmppException(Exception):
|
||||||
Custom exception class.
|
Custom exception class.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, message):
|
def __init__(self, message):
|
||||||
self.message = message
|
self.message = message
|
||||||
super(XmppException, self).__init__()
|
super(XmppException, self).__init__()
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.message
|
|
||||||
|
|
||||||
|
|
||||||
class XmppStreamError(object):
|
class XmppStreamError(object):
|
||||||
"""
|
"""
|
||||||
|
@ -72,7 +69,7 @@ class XmppStreamError(object):
|
||||||
text = None
|
text = None
|
||||||
other_elements = {}
|
other_elements = {}
|
||||||
|
|
||||||
def __str__(self):
|
def str(self):
|
||||||
if self.text:
|
if self.text:
|
||||||
return "{condition}: {text}".format(
|
return "{condition}: {text}".format(
|
||||||
condition=self.condition, text=self.text)
|
condition=self.condition, text=self.text)
|
||||||
|
@ -87,13 +84,12 @@ class XmppResponseHandler(ContentHandler):
|
||||||
seen_elements = set()
|
seen_elements = set()
|
||||||
mechanisms = []
|
mechanisms = []
|
||||||
starttls = False
|
starttls = False
|
||||||
tls_required = False
|
tlsrequired = False
|
||||||
capabilities = {}
|
capabilities = {}
|
||||||
state = XMPP_STATE_NEW
|
state = XMPP_STATE_NEW
|
||||||
stream_info = None
|
streaminfo = None
|
||||||
error_instance = None
|
|
||||||
|
|
||||||
in_elem = []
|
inelem = []
|
||||||
level = 0
|
level = 0
|
||||||
|
|
||||||
def __init__(self, expect_starttls):
|
def __init__(self, expect_starttls):
|
||||||
|
@ -101,35 +97,35 @@ class XmppResponseHandler(ContentHandler):
|
||||||
super(XmppResponseHandler, self).__init__()
|
super(XmppResponseHandler, self).__init__()
|
||||||
|
|
||||||
def startElementNS(self, name, qname, attrs):
|
def startElementNS(self, name, qname, attrs):
|
||||||
self.in_elem.append(name)
|
self.inelem.append(name)
|
||||||
self.seen_elements.add(name)
|
self.seen_elements.add(name)
|
||||||
if name == (NS_ETHERX_STREAMS, 'stream'):
|
if name == (NS_ETHERX_STREAMS, 'stream'):
|
||||||
self.state = XMPP_STATE_STREAM_START
|
self.state = XMPP_STATE_STREAM_START
|
||||||
self.stream_info = dict([
|
self.streaminfo = dict([
|
||||||
(qname, attrs.getValueByQName(qname)) for
|
(qname, attrs.getValueByQName(qname)) for
|
||||||
qname in attrs.getQNames()])
|
qname in attrs.getQNames()])
|
||||||
elif name == (NS_IETF_XMPP_TLS, 'starttls'):
|
elif name == (NS_IETF_XMPP_TLS, 'starttls'):
|
||||||
self.starttls = True
|
self.starttls = True
|
||||||
elif (
|
elif (
|
||||||
self.in_elem[-2] == (NS_IETF_XMPP_TLS, 'starttls') and
|
self.inelem[-2] == (NS_IETF_XMPP_TLS, 'starttls') and
|
||||||
name == (NS_IETF_XMPP_TLS, 'required')
|
name == (NS_IETF_XMPP_TLS, 'required')
|
||||||
):
|
):
|
||||||
self.tls_required = True
|
self.tlsrequired = True
|
||||||
_LOG.info("info other side requires TLS")
|
_LOG.info("info other side requires TLS")
|
||||||
elif name == (NS_JABBER_CAPS, 'c'):
|
elif name == (NS_JABBER_CAPS, 'c'):
|
||||||
for qname in attrs.getQNames():
|
for qname in attrs.getQNames():
|
||||||
self.capabilities[qname] = attrs.getValueByQName(qname)
|
self.capabilities[qname] = attrs.getValueByQName(qname)
|
||||||
elif name == (NS_ETHERX_STREAMS, 'error'):
|
elif name == (NS_ETHERX_STREAMS, 'error'):
|
||||||
self.state = XMPP_STATE_ERROR
|
self.state = XMPP_STATE_ERROR
|
||||||
self.error_instance = XmppStreamError()
|
self.errorinstance = XmppStreamError()
|
||||||
elif (
|
elif (
|
||||||
self.state == XMPP_STATE_ERROR and
|
self.state == XMPP_STATE_ERROR and
|
||||||
name != (NS_IETF_XMPP_STREAMS, 'text')
|
name != (NS_IETF_XMPP_STREAMS, 'text')
|
||||||
):
|
):
|
||||||
if name[0] == NS_IETF_XMPP_STREAMS:
|
if name[0] == NS_IETF_XMPP_STREAMS:
|
||||||
self.error_instance.condition = name[1]
|
self.errorinstance.condition = name[1]
|
||||||
else:
|
else:
|
||||||
self.error_instance.other_elements[name] = {'attrs': dict([
|
self.errorinstance.other_elements[name] = {'attrs': dict([
|
||||||
(qname, attrs.getValueByQName(qname)) for
|
(qname, attrs.getValueByQName(qname)) for
|
||||||
qname in attrs.getQNames()
|
qname in attrs.getQNames()
|
||||||
])}
|
])}
|
||||||
|
@ -142,25 +138,25 @@ class XmppResponseHandler(ContentHandler):
|
||||||
self.state = XMPP_STATE_FINISHED
|
self.state = XMPP_STATE_FINISHED
|
||||||
elif name == (NS_ETHERX_STREAMS, 'error'):
|
elif name == (NS_ETHERX_STREAMS, 'error'):
|
||||||
raise XmppException("XMPP stream error: {error}".format(
|
raise XmppException("XMPP stream error: {error}".format(
|
||||||
error=self.error_instance))
|
error=self.errorinstance))
|
||||||
elif name == (NS_IETF_XMPP_TLS, 'proceed'):
|
elif name == (NS_IETF_XMPP_TLS, 'proceed'):
|
||||||
self.state = XMPP_STATE_PROCEED_STARTTLS
|
self.state = XMPP_STATE_PROCEED_STARTTLS
|
||||||
elif name == (NS_IETF_XMPP_TLS, 'failure'):
|
elif name == (NS_IETF_XMPP_TLS, 'failure'):
|
||||||
raise XmppException("starttls initiation failed")
|
raise XmppException("starttls initiation failed")
|
||||||
_LOG.debug('end %s', name)
|
_LOG.debug('end %s', name)
|
||||||
del self.in_elem[-1]
|
del self.inelem[-1]
|
||||||
|
|
||||||
def characters(self, content):
|
def characters(self, content):
|
||||||
elem = self.in_elem[-1]
|
elem = self.inelem[-1]
|
||||||
if elem == (NS_IETF_XMPP_SASL, 'mechanism'):
|
if elem == (NS_IETF_XMPP_SASL, 'mechanism'):
|
||||||
self.mechanisms.append(content)
|
self.mechanisms.append(content)
|
||||||
elif self.state == XMPP_STATE_ERROR:
|
elif self.state == XMPP_STATE_ERROR:
|
||||||
if elem == (NS_IETF_XMPP_STREAMS, 'text'):
|
if elem == (NS_IETF_XMPP_STREAMS, 'text'):
|
||||||
self.error_instance.text = content
|
self.errorinstance.text = content
|
||||||
else:
|
else:
|
||||||
self.error_instance.other_elements[elem]['text'] = content
|
self.errorinstance.other_elements[elem]['text'] = content
|
||||||
else:
|
else:
|
||||||
_LOG.warning('ignored content in %s: %s', self.in_elem, content)
|
_LOG.warning('ignored content in %s: %s', self.inelem, content)
|
||||||
|
|
||||||
def is_valid_start(self):
|
def is_valid_start(self):
|
||||||
if not self.state == XMPP_STATE_RECEIVED_FEATURES:
|
if not self.state == XMPP_STATE_RECEIVED_FEATURES:
|
||||||
|
@ -168,43 +164,14 @@ class XmppResponseHandler(ContentHandler):
|
||||||
if self.expect_starttls is True and self.starttls is False:
|
if self.expect_starttls is True and self.starttls is False:
|
||||||
raise XmppException('expected STARTTLS capable service')
|
raise XmppException('expected STARTTLS capable service')
|
||||||
if (
|
if (
|
||||||
'version' not in self.stream_info or
|
'version' not in self.streaminfo or
|
||||||
self.stream_info['version'] != '1.0'
|
self.streaminfo['version'] != '1.0'
|
||||||
):
|
):
|
||||||
_LOG.warning(
|
_LOG.warning(
|
||||||
'unknown stream version %s', self.stream_info['version'])
|
'unknown stream version %s', self.streaminfo['version'])
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def open_socket(addrinfo):
|
|
||||||
"""
|
|
||||||
Open a client socket based on information in the addrinfo list of
|
|
||||||
tuples.
|
|
||||||
|
|
||||||
"""
|
|
||||||
new_socket = None
|
|
||||||
|
|
||||||
for res in addrinfo:
|
|
||||||
af, socktype, proto, canonname, sa = res
|
|
||||||
try:
|
|
||||||
new_socket = socket.socket(af, socktype, proto)
|
|
||||||
except socket.error:
|
|
||||||
new_socket = None
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
new_socket.connect(sa)
|
|
||||||
except socket.error:
|
|
||||||
new_socket.close()
|
|
||||||
new_socket = None
|
|
||||||
continue
|
|
||||||
break
|
|
||||||
|
|
||||||
if new_socket is None:
|
|
||||||
raise XmppException("could not open socket")
|
|
||||||
|
|
||||||
return new_socket
|
|
||||||
|
|
||||||
|
|
||||||
class Xmpp(nagiosplugin.Resource):
|
class Xmpp(nagiosplugin.Resource):
|
||||||
"""
|
"""
|
||||||
Xmpp resource.
|
Xmpp resource.
|
||||||
|
@ -213,13 +180,11 @@ class Xmpp(nagiosplugin.Resource):
|
||||||
state = nagiosplugin.Unknown
|
state = nagiosplugin.Unknown
|
||||||
cause = None
|
cause = None
|
||||||
socket = None
|
socket = None
|
||||||
days_left = None
|
daysleft = None
|
||||||
parser = None
|
|
||||||
content_handler = None
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, host_address, port, ipv6, is_server, starttls,
|
self, host_address, port, ipv6, is_server, starttls,
|
||||||
servername, checkcerts, caroots
|
servername, checkcerts, caroots
|
||||||
):
|
):
|
||||||
self.address = host_address
|
self.address = host_address
|
||||||
self.port = port
|
self.port = port
|
||||||
|
@ -227,8 +192,8 @@ class Xmpp(nagiosplugin.Resource):
|
||||||
self.is_server = is_server
|
self.is_server = is_server
|
||||||
self.starttls = starttls
|
self.starttls = starttls
|
||||||
self.servername = servername
|
self.servername = servername
|
||||||
self.check_certs = checkcerts
|
self.checkcerts = checkcerts
|
||||||
self.ca_roots = caroots
|
self.caroots = caroots
|
||||||
self.make_parser()
|
self.make_parser()
|
||||||
self.set_content_handler()
|
self.set_content_handler()
|
||||||
|
|
||||||
|
@ -245,28 +210,53 @@ class Xmpp(nagiosplugin.Resource):
|
||||||
Set the XMPP SAX content handler.
|
Set the XMPP SAX content handler.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.content_handler = XmppResponseHandler(
|
self.contenthandler = XmppResponseHandler(
|
||||||
expect_starttls=self.starttls)
|
expect_starttls=self.starttls)
|
||||||
self.parser.setContentHandler(self.content_handler)
|
self.parser.setContentHandler(self.contenthandler)
|
||||||
|
|
||||||
def get_addr_info(self):
|
def get_addrinfo(self):
|
||||||
"""
|
"""
|
||||||
Perform the DNS lookup and return a list of potential socket address
|
Perform the DNS lookup and return a list of potential socket address
|
||||||
tuples as returned by :py:method:`socket.getaddrinfo`.
|
tuples as returned by :py:method:`socket.getaddrinfo`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if self.ipv6 is None:
|
if self.ipv6 is None:
|
||||||
addr_family = 0
|
addrfamily = 0
|
||||||
elif self.ipv6 is True:
|
elif self.ipv6 is True:
|
||||||
addr_family = socket.AF_INET6
|
addrfamily = socket.AF_INET6
|
||||||
else:
|
else:
|
||||||
addr_family = socket.AF_INET
|
addrfamily = socket.AF_INET
|
||||||
return socket.getaddrinfo(
|
return socket.getaddrinfo(
|
||||||
self.address, self.port, addr_family, socket.SOCK_STREAM,
|
self.address, self.port, addrfamily, socket.SOCK_STREAM,
|
||||||
socket.IPPROTO_TCP)
|
socket.IPPROTO_TCP)
|
||||||
|
self.result = nagiosplugin.Critical
|
||||||
|
|
||||||
|
def open_socket(self, addrinfo):
|
||||||
|
"""
|
||||||
|
Open a client socket based on information in the addrinfo list of
|
||||||
|
tuples.
|
||||||
|
|
||||||
|
"""
|
||||||
|
for res in addrinfo:
|
||||||
|
af, socktype, proto, canonname, sa = res
|
||||||
|
try:
|
||||||
|
s = socket.socket(af, socktype, proto)
|
||||||
|
except socket.error:
|
||||||
|
s = None
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
s.connect(sa)
|
||||||
|
except socket.error:
|
||||||
|
s.close()
|
||||||
|
s = None
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
if s is None:
|
||||||
|
raise XmppException("could not open socket")
|
||||||
|
return s
|
||||||
|
|
||||||
def handle_xmpp_stanza(
|
def handle_xmpp_stanza(
|
||||||
self, message_str, timeout=0.1, expected_state=None
|
self, message_str, timeout=0.1, expected_state=None
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Handle a single XMPP message.
|
Handle a single XMPP message.
|
||||||
|
@ -293,28 +283,37 @@ class Xmpp(nagiosplugin.Resource):
|
||||||
chunks.append(data)
|
chunks.append(data)
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
xml_text = b''.join(chunks).decode('utf-8')
|
xmltext = b''.join(chunks).decode('utf-8')
|
||||||
_LOG.debug("read %s", xml_text)
|
_LOG.debug("read %s", xmltext)
|
||||||
self.parser.feed(xml_text)
|
self.parser.feed(xmltext)
|
||||||
if (
|
if (
|
||||||
expected_state is not None and
|
expected_state is not None and
|
||||||
self.content_handler.state != expected_state
|
self.contenthandler.state != expected_state
|
||||||
):
|
):
|
||||||
raise XmppException(
|
raise XmppException(
|
||||||
"unexpected state %s" % self.content_handler.state)
|
"unexpected state %s" % self.contenthandler.state)
|
||||||
|
|
||||||
def start_stream(self):
|
def start_stream(self):
|
||||||
"""
|
"""
|
||||||
Start a XMPP conversation with the server.
|
Start a XMPP conversation with the server.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
namespace = "jabber:server" if self.is_server else "jabber:client"
|
if self.is_server:
|
||||||
|
self.handle_xmpp_stanza((
|
||||||
self.handle_xmpp_stanza(
|
"<?xml version='1.0' ?><stream:stream to='{servername}' "
|
||||||
f"<stream:stream xmlns:stream='{NS_ETHERX_STREAMS}' xmlns='{namespace}' to='{self.servername}'"
|
"xmlns='jabber:server' "
|
||||||
f" version='1.0'>",
|
"xmlns:stream='http://etherx.jabber.org/streams' "
|
||||||
expected_state=XMPP_STATE_RECEIVED_FEATURES
|
"version='1.0'>"
|
||||||
)
|
).format(servername=self.servername),
|
||||||
|
expected_state=XMPP_STATE_RECEIVED_FEATURES)
|
||||||
|
else:
|
||||||
|
self.handle_xmpp_stanza((
|
||||||
|
"<?xml version='1.0' ?><stream:stream to='{servername}' "
|
||||||
|
"xmlns='jabber:client' "
|
||||||
|
"xmlns:stream='http://etherx.jabber.org/streams' "
|
||||||
|
"version='1.0'>"
|
||||||
|
).format(servername=self.servername),
|
||||||
|
expected_state=XMPP_STATE_RECEIVED_FEATURES)
|
||||||
|
|
||||||
def setup_ssl_context(self):
|
def setup_ssl_context(self):
|
||||||
"""
|
"""
|
||||||
|
@ -322,19 +321,28 @@ class Xmpp(nagiosplugin.Resource):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
context = ssl.create_default_context()
|
context = ssl.create_default_context()
|
||||||
if not self.check_certs:
|
context.options = ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3
|
||||||
|
if not self.checkcerts:
|
||||||
context.check_hostname = False
|
context.check_hostname = False
|
||||||
context.verify_mode = ssl.CERT_NONE
|
context.verify_mode = ssl.CERT_NONE
|
||||||
else:
|
else:
|
||||||
context.verify_mode = ssl.CERT_REQUIRED
|
context.verify_mode = ssl.CERT_REQUIRED
|
||||||
if self.ca_roots:
|
if self.caroots:
|
||||||
if os.path.isfile(self.ca_roots):
|
if os.path.isfile(self.caroots):
|
||||||
kwargs = {'cafile': self.ca_roots}
|
kwargs = {'cafile': self.caroots}
|
||||||
else:
|
else:
|
||||||
kwargs = {'capath': self.ca_roots}
|
kwargs = {'capath': self.caroots}
|
||||||
context.load_verify_locations(**kwargs)
|
context.load_verify_locations(**kwargs)
|
||||||
else:
|
else:
|
||||||
context.load_default_certs()
|
context.load_default_certs()
|
||||||
|
stats = context.cert_store_stats()
|
||||||
|
if stats['x509_ca'] == 0:
|
||||||
|
_LOG.info(
|
||||||
|
"tried to load CA certificates from default locations, but"
|
||||||
|
" could not find any CA certificates.")
|
||||||
|
raise XmppException('no CA certificates found')
|
||||||
|
else:
|
||||||
|
_LOG.debug('certificate store statistics: %s', stats)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def initiate_tls(self):
|
def initiate_tls(self):
|
||||||
|
@ -346,22 +354,21 @@ class Xmpp(nagiosplugin.Resource):
|
||||||
"""
|
"""
|
||||||
_LOG.debug("start initiate_tls()")
|
_LOG.debug("start initiate_tls()")
|
||||||
self.handle_xmpp_stanza(
|
self.handle_xmpp_stanza(
|
||||||
f"<starttls xmlns='{NS_IETF_XMPP_TLS}'/>",
|
"<starttls xmlns='{xmlns}'/>".format(xmlns=NS_IETF_XMPP_TLS),
|
||||||
timeout=0.5,
|
|
||||||
expected_state=XMPP_STATE_PROCEED_STARTTLS)
|
expected_state=XMPP_STATE_PROCEED_STARTTLS)
|
||||||
sslcontext = self.setup_ssl_context()
|
sslcontext = self.setup_ssl_context()
|
||||||
try:
|
try:
|
||||||
self.socket = sslcontext.wrap_socket(
|
self.socket = sslcontext.wrap_socket(
|
||||||
self.socket, server_hostname=self.servername)
|
self.socket, server_hostname=self.servername)
|
||||||
_LOG.info("TLS socket setup successful")
|
_LOG.info("TLS socket setup successful")
|
||||||
except ssl.CertificateError as certificate_error:
|
except ssl.SSLError as ssle:
|
||||||
raise XmppException("Certificate error %s" % certificate_error)
|
raise XmppException("SSL error %s" % ssle.strerror)
|
||||||
except ssl.SSLError as ssl_error:
|
except ssl.CertificateError as certerr:
|
||||||
raise XmppException("SSL error %s" % ssl_error.strerror)
|
raise XmppException("Certificate error %s" % certerr)
|
||||||
self.starttls = False
|
self.starttls = False
|
||||||
# reset infos retrieved previously as written in RFC 3920 sec. 5.
|
# reset infos retrieved previously as written in RFC 3920 sec. 5.
|
||||||
self.parser.reset()
|
self.parser.reset()
|
||||||
if self.check_certs:
|
if self.checkcerts:
|
||||||
certinfo = self.socket.getpeercert()
|
certinfo = self.socket.getpeercert()
|
||||||
_LOG.debug("got the following certificate info: %s", certinfo)
|
_LOG.debug("got the following certificate info: %s", certinfo)
|
||||||
_LOG.info(
|
_LOG.info(
|
||||||
|
@ -369,12 +376,12 @@ class Xmpp(nagiosplugin.Resource):
|
||||||
certinfo['notBefore'], certinfo['notAfter'])
|
certinfo['notBefore'], certinfo['notAfter'])
|
||||||
enddate = ssl.cert_time_to_seconds(certinfo['notAfter'])
|
enddate = ssl.cert_time_to_seconds(certinfo['notAfter'])
|
||||||
remaining = datetime.fromtimestamp(enddate) - datetime.now()
|
remaining = datetime.fromtimestamp(enddate) - datetime.now()
|
||||||
self.days_left = remaining.days
|
self.daysleft = remaining.days
|
||||||
# start new parsing
|
# start new parsing
|
||||||
self.make_parser()
|
self.make_parser()
|
||||||
self.set_content_handler()
|
self.set_content_handler()
|
||||||
self.start_stream()
|
self.start_stream()
|
||||||
if not self.content_handler.is_valid_start():
|
if not self.contenthandler.is_valid_start():
|
||||||
raise XmppException("no valid response to XMPP client request")
|
raise XmppException("no valid response to XMPP client request")
|
||||||
_LOG.debug("end initiate_tls()")
|
_LOG.debug("end initiate_tls()")
|
||||||
|
|
||||||
|
@ -385,9 +392,9 @@ class Xmpp(nagiosplugin.Resource):
|
||||||
"""
|
"""
|
||||||
_LOG.debug("start handle_xmpp()")
|
_LOG.debug("start handle_xmpp()")
|
||||||
self.start_stream()
|
self.start_stream()
|
||||||
if not self.content_handler.is_valid_start():
|
if not self.contenthandler.is_valid_start():
|
||||||
raise XmppException("no valid response to XMPP client request")
|
raise XmppException("no valid response to XMPP client request")
|
||||||
if self.starttls is True or self.content_handler.tls_required:
|
if self.starttls is True or self.contenthandler.tlsrequired:
|
||||||
self.initiate_tls()
|
self.initiate_tls()
|
||||||
self.handle_xmpp_stanza("</stream:stream>")
|
self.handle_xmpp_stanza("</stream:stream>")
|
||||||
_LOG.debug("end handle_xmpp()")
|
_LOG.debug("end handle_xmpp()")
|
||||||
|
@ -401,8 +408,8 @@ class Xmpp(nagiosplugin.Resource):
|
||||||
start = datetime.now()
|
start = datetime.now()
|
||||||
_LOG.debug("start probe() at %s", start)
|
_LOG.debug("start probe() at %s", start)
|
||||||
try:
|
try:
|
||||||
addrinfo = self.get_addr_info()
|
addrinfo = self.get_addrinfo()
|
||||||
self.socket = open_socket(addrinfo)
|
self.socket = self.open_socket(addrinfo)
|
||||||
try:
|
try:
|
||||||
self.handle_xmpp()
|
self.handle_xmpp()
|
||||||
finally:
|
finally:
|
||||||
|
@ -411,7 +418,6 @@ class Xmpp(nagiosplugin.Resource):
|
||||||
except socket.gaierror as e:
|
except socket.gaierror as e:
|
||||||
self.state = nagiosplugin.Critical
|
self.state = nagiosplugin.Critical
|
||||||
self.cause = str(e)
|
self.cause = str(e)
|
||||||
_LOG.debug("got an gaierror %s", e)
|
|
||||||
return nagiosplugin.Metric("time", "unknown")
|
return nagiosplugin.Metric("time", "unknown")
|
||||||
except XmppException as e:
|
except XmppException as e:
|
||||||
self.state = nagiosplugin.Critical
|
self.state = nagiosplugin.Critical
|
||||||
|
@ -423,7 +429,7 @@ class Xmpp(nagiosplugin.Resource):
|
||||||
_LOG.debug("end probe() at %s", end)
|
_LOG.debug("end probe() at %s", end)
|
||||||
yield nagiosplugin.Metric(
|
yield nagiosplugin.Metric(
|
||||||
'time', (end - start).total_seconds(), 's', min=0)
|
'time', (end - start).total_seconds(), 's', min=0)
|
||||||
yield nagiosplugin.Metric('daysleft', self.days_left, 'd')
|
yield nagiosplugin.Metric('daysleft', self.daysleft, 'd')
|
||||||
|
|
||||||
|
|
||||||
class XmppContext(nagiosplugin.ScalarContext):
|
class XmppContext(nagiosplugin.ScalarContext):
|
||||||
|
@ -449,50 +455,31 @@ class DaysValidContext(nagiosplugin.Context):
|
||||||
Context for checking the certificate expiry date.
|
Context for checking the certificate expiry date.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
fmt_hint = "less than {value} days"
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, name, warndays=0, critdays=0,
|
self, name, warndays=0, critdays=0,
|
||||||
fmt_metric='certificate valid for {value} days'
|
fmt_metric='certificate expires in {value} days'
|
||||||
):
|
):
|
||||||
super(DaysValidContext, self).__init__(name, fmt_metric=fmt_metric)
|
super(DaysValidContext, self).__init__(name, fmt_metric=fmt_metric)
|
||||||
self.warning = nagiosplugin.Range('@%d:' % warndays)
|
self.warning = nagiosplugin.Range('@%d:' % warndays)
|
||||||
self.critical = nagiosplugin.Range('@%d:' % critdays)
|
self.critical = nagiosplugin.Range('@%d:' % critdays)
|
||||||
self.warndays = warndays
|
|
||||||
self.critdays = critdays
|
|
||||||
|
|
||||||
def evaluate(self, metric, resource):
|
def evaluate(self, metric, resource):
|
||||||
if resource.check_certs and metric.value is not None:
|
if resource.checkcerts and metric.value is not None:
|
||||||
|
hint = self.describe(metric)
|
||||||
if self.critical.match(metric.value):
|
if self.critical.match(metric.value):
|
||||||
return nagiosplugin.Result(
|
return nagiosplugin.Result(nagiosplugin.Critical, hint, metric)
|
||||||
nagiosplugin.Critical,
|
|
||||||
hint=self.fmt_hint.format(value=self.critdays),
|
|
||||||
metric=metric)
|
|
||||||
if self.warning.match(metric.value):
|
if self.warning.match(metric.value):
|
||||||
return nagiosplugin.Result(
|
return nagiosplugin.Result(nagiosplugin.Warn, hint, metric)
|
||||||
nagiosplugin.Warn,
|
return nagiosplugin.Result(nagiosplugin.Ok, hint, metric)
|
||||||
hint=self.fmt_hint.format(value=self.warndays),
|
|
||||||
metric=metric)
|
|
||||||
return nagiosplugin.Result(
|
|
||||||
nagiosplugin.Ok, "", metric)
|
|
||||||
return nagiosplugin.Result(nagiosplugin.Ok)
|
return nagiosplugin.Result(nagiosplugin.Ok)
|
||||||
|
|
||||||
def performance(self, metric, resource):
|
def performance(self, metric, resource):
|
||||||
if resource.check_certs and metric.value is not None:
|
if resource.checkcerts and metric.value is not None:
|
||||||
return nagiosplugin.Performance('daysvalid', metric.value, '')
|
return nagiosplugin.Performance('daysvalid', metric.value, 'd')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class XmppSummary(nagiosplugin.Summary):
|
|
||||||
"""
|
|
||||||
Summary instance that outputs all metrics if the check results are ok.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def ok(self, results):
|
|
||||||
return ", ".join([str(res) for res in results])
|
|
||||||
|
|
||||||
|
|
||||||
@nagiosplugin.guarded
|
@nagiosplugin.guarded
|
||||||
def main():
|
def main():
|
||||||
"""
|
"""
|
||||||
|
@ -501,11 +488,8 @@ def main():
|
||||||
"""
|
"""
|
||||||
import argparse
|
import argparse
|
||||||
parser = argparse.ArgumentParser(description="Check XMPP services")
|
parser = argparse.ArgumentParser(description="Check XMPP services")
|
||||||
host_address = parser.add_mutually_exclusive_group(required=True)
|
parser.add_argument(
|
||||||
host_address.add_argument("-H", "--host-address", help="host address")
|
"-H", "--host-address", help="host address", required=True)
|
||||||
host_address.add_argument(
|
|
||||||
"--hostname", help="host name, alternative for host-address",
|
|
||||||
dest="host_address")
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-p", "--port", help="port", type=int)
|
"-p", "--port", help="port", type=int)
|
||||||
is_server = parser.add_mutually_exclusive_group()
|
is_server = parser.add_mutually_exclusive_group()
|
||||||
|
@ -567,10 +551,8 @@ def main():
|
||||||
]
|
]
|
||||||
check = nagiosplugin.Check(
|
check = nagiosplugin.Check(
|
||||||
Xmpp(**kwargs),
|
Xmpp(**kwargs),
|
||||||
XmppContext(
|
XmppContext('time', warning, critical),
|
||||||
'time', warning, critical, fmt_metric="request took {value}{uom}"),
|
DaysValidContext('daysleft', warndays, critdays)
|
||||||
DaysValidContext('daysleft', warndays, critdays),
|
|
||||||
XmppSummary(),
|
|
||||||
)
|
)
|
||||||
check.main(verbose=verbose, timeout=0)
|
check.main(verbose=verbose, timeout=0)
|
||||||
|
|
||||||
|
|
12
debian/changelog
vendored
Normal file
12
debian/changelog
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
nagios-check-xmppng (0.1.2-1~bpo8+1) jessie-backports; urgency=medium
|
||||||
|
|
||||||
|
* Rebuild for jessie-backports.
|
||||||
|
* set jessie-backports as debian branch in debian/gbp.conf
|
||||||
|
|
||||||
|
-- Jan Dittberner <jandd@debian.org> Sun, 20 Sep 2015 18:38:33 +0200
|
||||||
|
|
||||||
|
nagios-check-xmppng (0.1.2-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Initial release. (Closes: #777732)
|
||||||
|
|
||||||
|
-- Jan Dittberner <jandd@debian.org> Thu, 12 Feb 2015 00:49:30 +0100
|
1
debian/compat
vendored
Normal file
1
debian/compat
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
9
|
30
debian/control
vendored
Normal file
30
debian/control
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
Source: nagios-check-xmppng
|
||||||
|
Section: net
|
||||||
|
Priority: extra
|
||||||
|
Maintainer: Jan Dittberner <jandd@debian.org>
|
||||||
|
Homepage: https://exchange.icinga.org/jandd/check_xmppng
|
||||||
|
Build-Depends: debhelper (>= 9)
|
||||||
|
Standards-Version: 3.9.6
|
||||||
|
Vcs-Browser: https://git.dittberner.info/?p=check_xmpp.git
|
||||||
|
Vcs-Git: https://git.dittberner.info/check_xmpp.git -b debian
|
||||||
|
|
||||||
|
Package: nagios-check-xmppng
|
||||||
|
Architecture: all
|
||||||
|
Depends: python3 (>= 3.4),
|
||||||
|
python3-defusedxml (>= 0.4.1),
|
||||||
|
python3-nagiosplugin (>= 1.2.2),
|
||||||
|
${misc:Depends}
|
||||||
|
Suggests: icinga | icinga2 | nagios3
|
||||||
|
Description: monitoring plugin to check XMPP servers
|
||||||
|
a nagios compatible check plugin for XMPP servers implementing the XMPP
|
||||||
|
protocol as specified in RFC 6120.
|
||||||
|
.
|
||||||
|
The plugin provides the following features:
|
||||||
|
.
|
||||||
|
- check client to server (C2S) as well as server to server (S2S) ports
|
||||||
|
- check XMPP servers on IPv6 and IPv4 addresses
|
||||||
|
- support STARTTLS as specified in RFC 6120 section 5.
|
||||||
|
- check the validity of the server certificate presented by the XMPP server
|
||||||
|
.
|
||||||
|
The plugin has been implemented because of insufficiencies in the existing
|
||||||
|
check_ssl_cert and check_xmpp plugins.
|
24
debian/copyright
vendored
Normal file
24
debian/copyright
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
|
Upstream-Name: check_xmppng
|
||||||
|
Upstream-Contact: Jan Dittberner <jan@dittberner.info>
|
||||||
|
Source: https://git.dittberner.info/?p=check_xmpp.git
|
||||||
|
|
||||||
|
Files: *
|
||||||
|
Copyright: 2015 Jan Dittberner
|
||||||
|
License: GPL-3
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
.
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
.
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
.
|
||||||
|
On Debian systems, the full text of the GNU General Public
|
||||||
|
License version 3 can be found in the file
|
||||||
|
`/usr/share/common-licenses/GPL-3'.
|
1
debian/docs
vendored
Normal file
1
debian/docs
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
README.md
|
8
debian/gbp.conf
vendored
Normal file
8
debian/gbp.conf
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[DEFAULT]
|
||||||
|
debian-branch = jessie-backports
|
||||||
|
upstream-branch = master
|
||||||
|
upstream-tag = %(version)s
|
||||||
|
no-create-orig = True
|
||||||
|
|
||||||
|
[import-orig]
|
||||||
|
pristine-tar = False
|
1
debian/install
vendored
Normal file
1
debian/install
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
check_xmppng /usr/lib/nagios/plugins/
|
10
debian/rules
vendored
Executable file
10
debian/rules
vendored
Executable file
|
@ -0,0 +1,10 @@
|
||||||
|
#!/usr/bin/make -f
|
||||||
|
# -*- makefile -*-
|
||||||
|
|
||||||
|
#export DH_VERBOSE=1
|
||||||
|
|
||||||
|
override_dh_installchangelogs:
|
||||||
|
dh_installchangelogs changes.md
|
||||||
|
|
||||||
|
%:
|
||||||
|
dh $@
|
1
debian/source/format
vendored
Normal file
1
debian/source/format
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
3.0 (quilt)
|
|
@ -1,150 +1,12 @@
|
||||||
name: check_xmppng
|
name: check_xmppng
|
||||||
description: "file:///README.md"
|
description: "file:///README.md"
|
||||||
url: "https://git.dittberner.info/jan/check_xmppng"
|
url: "https://git.dittberner.info/?p=check_xmpp.git"
|
||||||
tags: XMPP, X.509
|
tags: XMPP, X.509
|
||||||
vendor:
|
vendor:
|
||||||
target: Messaging
|
target: Messaging
|
||||||
type: Plugin
|
type: Plugin
|
||||||
license: gplv3
|
license: gplv3
|
||||||
releases:
|
releases:
|
||||||
- name: 0.3.3
|
|
||||||
description: "fix CA certificate check"
|
|
||||||
files:
|
|
||||||
-
|
|
||||||
name: check_xmppng
|
|
||||||
url: "file:///check_xmppng"
|
|
||||||
description: "Check command"
|
|
||||||
checksum: fdf942cb5c778aaa395a0ed1eba6dcda
|
|
||||||
-
|
|
||||||
name: COPYING
|
|
||||||
url: "file:///COPYING"
|
|
||||||
description: "GPL 3.0 license text"
|
|
||||||
checksum: d32239bcb673463ab874e80d47fae504
|
|
||||||
-
|
|
||||||
name: README.md
|
|
||||||
url: "file:///README.md"
|
|
||||||
description: "documentation"
|
|
||||||
checksum: 701ad7a882406a1f552a118d471a0b45
|
|
||||||
-
|
|
||||||
name: changes.md
|
|
||||||
url: "file:///changes.md"
|
|
||||||
description: "change log"
|
|
||||||
checksum: 0e23c919b413a4214c323b1953909c14
|
|
||||||
- name: 0.3.2
|
|
||||||
description: "fix CA certificate check"
|
|
||||||
files:
|
|
||||||
-
|
|
||||||
name: check_xmppng
|
|
||||||
url: "file:///check_xmppng"
|
|
||||||
description: "Check command"
|
|
||||||
checksum: e0ded038e79a2538d3b0c99cdc599810
|
|
||||||
-
|
|
||||||
name: COPYING
|
|
||||||
url: "file:///COPYING"
|
|
||||||
description: "GPL 3.0 license text"
|
|
||||||
checksum: d32239bcb673463ab874e80d47fae504
|
|
||||||
-
|
|
||||||
name: README.md
|
|
||||||
url: "file:///README.md"
|
|
||||||
description: "documentation"
|
|
||||||
checksum: 701ad7a882406a1f552a118d471a0b45
|
|
||||||
-
|
|
||||||
name: changes.md
|
|
||||||
url: "file:///changes.md"
|
|
||||||
description: "change log"
|
|
||||||
checksum: 60adfa1bf31c5a5fefb57ca55d186c7b
|
|
||||||
- name: 0.3.1
|
|
||||||
description: "new project home"
|
|
||||||
files:
|
|
||||||
-
|
|
||||||
name: check_xmppng
|
|
||||||
url: "file:///check_xmppng"
|
|
||||||
description: "Check command"
|
|
||||||
checksum: 3ba088ad712e7eff3897b0be70dc5437
|
|
||||||
-
|
|
||||||
name: COPYING
|
|
||||||
url: "file:///COPYING"
|
|
||||||
description: "GPL 3.0 license text"
|
|
||||||
checksum: d32239bcb673463ab874e80d47fae504
|
|
||||||
-
|
|
||||||
name: README.md
|
|
||||||
url: "file:///README.md"
|
|
||||||
description: "documentation"
|
|
||||||
checksum: 1e6f6632b12e4ef5fc4f02c3ea65da8a
|
|
||||||
-
|
|
||||||
name: changes.md
|
|
||||||
url: "file:///changes.md"
|
|
||||||
description: "change log"
|
|
||||||
checksum: df32115e17a931a083c2c9065c998147
|
|
||||||
- name: 0.3.0
|
|
||||||
description: "add --hostname option"
|
|
||||||
files:
|
|
||||||
-
|
|
||||||
name: check_xmppng
|
|
||||||
url: "file:///check_xmppng"
|
|
||||||
description: "Check command"
|
|
||||||
checksum: 9774a26db6c54af4a51902b90ffe13bc
|
|
||||||
-
|
|
||||||
name: COPYING
|
|
||||||
url: "file:///COPYING"
|
|
||||||
description: "GPL 3.0 license text"
|
|
||||||
checksum: d32239bcb673463ab874e80d47fae504
|
|
||||||
-
|
|
||||||
name: README.md
|
|
||||||
url: "file:///README.md"
|
|
||||||
description: "documentation"
|
|
||||||
checksum: 1e6f6632b12e4ef5fc4f02c3ea65da8a
|
|
||||||
-
|
|
||||||
name: changes.md
|
|
||||||
url: "file:///changes.md"
|
|
||||||
description: "change log"
|
|
||||||
checksum: f2c1f311817cbb51eac4c62b9c097579
|
|
||||||
- name: 0.2.1
|
|
||||||
description: "fixed perfdata output"
|
|
||||||
files:
|
|
||||||
-
|
|
||||||
name: check_xmppng
|
|
||||||
url: "file:///check_xmppng"
|
|
||||||
description: "Check command"
|
|
||||||
checksum: 992ee1b3209bba6ddb29b46c939b2978
|
|
||||||
-
|
|
||||||
name: COPYING
|
|
||||||
url: "file:///COPYING"
|
|
||||||
description: "GPL 3.0 license text"
|
|
||||||
checksum: d32239bcb673463ab874e80d47fae504
|
|
||||||
-
|
|
||||||
name: README.md
|
|
||||||
url: "file:///README.md"
|
|
||||||
description: "documentation"
|
|
||||||
checksum: 1e6f6632b12e4ef5fc4f02c3ea65da8a
|
|
||||||
-
|
|
||||||
name: changes.md
|
|
||||||
url: "file:///changes.md"
|
|
||||||
description: "change log"
|
|
||||||
checksum: a7a17e05c732e24f7b0529fb868a172f
|
|
||||||
- name: 0.2
|
|
||||||
description: "better human readable output"
|
|
||||||
files:
|
|
||||||
-
|
|
||||||
name: check_xmppng
|
|
||||||
url: "file:///check_xmppng"
|
|
||||||
description: "Check command"
|
|
||||||
checksum: 7369095c7daad89e04e0fbdd81ef0e00
|
|
||||||
-
|
|
||||||
name: COPYING
|
|
||||||
url: "file:///COPYING"
|
|
||||||
description: "GPL 3.0 license text"
|
|
||||||
checksum: d32239bcb673463ab874e80d47fae504
|
|
||||||
-
|
|
||||||
name: README.md
|
|
||||||
url: "file:///README.md"
|
|
||||||
description: "documentation"
|
|
||||||
checksum: 1e6f6632b12e4ef5fc4f02c3ea65da8a
|
|
||||||
-
|
|
||||||
name: changes.md
|
|
||||||
url: "file:///changes.md"
|
|
||||||
description: "change log"
|
|
||||||
checksum: f09a79d4762efc8c5a97e6d9f301b398
|
|
||||||
-
|
-
|
||||||
name: 0.1.2
|
name: 0.1.2
|
||||||
description: "first icingaexchange release"
|
description: "first icingaexchange release"
|
||||||
|
|
Loading…
Reference in a new issue