Compare commits

..

No commits in common. "master" and "0.2" have entirely different histories.
master ... 0.2

4 changed files with 134 additions and 268 deletions

View file

@ -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,7 +54,7 @@ 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]
@ -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.

View file

@ -1,26 +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 ## version 0.2 2015-12-29
* improve human readable output by returning both response time as well as * improve human readable output by returning both response time as well as

View file

@ -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.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,9 +180,7 @@ 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,
@ -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,25 +210,50 @@ 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
@ -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:
@ -423,7 +430,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):
@ -462,7 +469,7 @@ class DaysValidContext(nagiosplugin.Context):
self.critdays = critdays 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:
if self.critical.match(metric.value): if self.critical.match(metric.value):
return nagiosplugin.Result( return nagiosplugin.Result(
nagiosplugin.Critical, nagiosplugin.Critical,
@ -478,8 +485,8 @@ class DaysValidContext(nagiosplugin.Context):
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
@ -501,11 +508,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()

View file

@ -1,127 +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 - name: 0.2
description: "better human readable output" description: "better human readable output"
files: files: