Compare commits
	
		
			4 commits
		
	
	
		
			c7ae8cf76e
			...
			c8443a53c3
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| c8443a53c3 | |||
| 7b4b4e75da | |||
| 6f077e51bc | |||
| ea68a363ca | 
					 4 changed files with 142 additions and 108 deletions
				
			
		|  | @ -1,8 +1,12 @@ | |||
| # change log | ||||
| 
 | ||||
| ## version 0.3.3 2023-08-04 | ||||
| 
 | ||||
| * fix starttls behaviour with Python 3.11 | ||||
| 
 | ||||
| ## version 0.3.2 2021-03-07 | ||||
| 
 | ||||
| * remove brokebn CA certificate statistics | ||||
| * remove broken CA certificate statistics | ||||
| 
 | ||||
| ## version 0.3.1 2019-06-23 | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										197
									
								
								check_xmppng
									
										
									
									
									
								
							
							
						
						
									
										197
									
								
								check_xmppng
									
										
									
									
									
								
							|  | @ -33,7 +33,6 @@ import nagiosplugin | |||
| __author__ = "Jan Dittberner" | ||||
| __version__ = "0.3.2" | ||||
| 
 | ||||
| 
 | ||||
| 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_STREAMS = 'urn:ietf:params:xml:ns:xmpp-streams' | ||||
|  | @ -55,10 +54,14 @@ class XmppException(Exception): | |||
|     Custom exception class. | ||||
| 
 | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, message): | ||||
|         self.message = message | ||||
|         super(XmppException, self).__init__() | ||||
| 
 | ||||
|     def __str__(self): | ||||
|         return self.message | ||||
| 
 | ||||
| 
 | ||||
| class XmppStreamError(object): | ||||
|     """ | ||||
|  | @ -69,7 +72,7 @@ class XmppStreamError(object): | |||
|     text = None | ||||
|     other_elements = {} | ||||
| 
 | ||||
|     def str(self): | ||||
|     def __str__(self): | ||||
|         if self.text: | ||||
|             return "{condition}: {text}".format( | ||||
|                 condition=self.condition, text=self.text) | ||||
|  | @ -84,12 +87,13 @@ class XmppResponseHandler(ContentHandler): | |||
|     seen_elements = set() | ||||
|     mechanisms = [] | ||||
|     starttls = False | ||||
|     tlsrequired = False | ||||
|     tls_required = False | ||||
|     capabilities = {} | ||||
|     state = XMPP_STATE_NEW | ||||
|     streaminfo = None | ||||
|     stream_info = None | ||||
|     error_instance = None | ||||
| 
 | ||||
|     inelem = [] | ||||
|     in_elem = [] | ||||
|     level = 0 | ||||
| 
 | ||||
|     def __init__(self, expect_starttls): | ||||
|  | @ -97,35 +101,35 @@ class XmppResponseHandler(ContentHandler): | |||
|         super(XmppResponseHandler, self).__init__() | ||||
| 
 | ||||
|     def startElementNS(self, name, qname, attrs): | ||||
|         self.inelem.append(name) | ||||
|         self.in_elem.append(name) | ||||
|         self.seen_elements.add(name) | ||||
|         if name == (NS_ETHERX_STREAMS, 'stream'): | ||||
|             self.state = XMPP_STATE_STREAM_START | ||||
|             self.streaminfo = dict([ | ||||
|             self.stream_info = dict([ | ||||
|                 (qname, attrs.getValueByQName(qname)) for | ||||
|                 qname in attrs.getQNames()]) | ||||
|         elif name == (NS_IETF_XMPP_TLS, 'starttls'): | ||||
|             self.starttls = True | ||||
|         elif ( | ||||
|             self.inelem[-2] == (NS_IETF_XMPP_TLS, 'starttls') and | ||||
|                 self.in_elem[-2] == (NS_IETF_XMPP_TLS, 'starttls') and | ||||
|                 name == (NS_IETF_XMPP_TLS, 'required') | ||||
|         ): | ||||
|             self.tlsrequired = True | ||||
|             self.tls_required = True | ||||
|             _LOG.info("info other side requires TLS") | ||||
|         elif name == (NS_JABBER_CAPS, 'c'): | ||||
|             for qname in attrs.getQNames(): | ||||
|                 self.capabilities[qname] = attrs.getValueByQName(qname) | ||||
|         elif name == (NS_ETHERX_STREAMS, 'error'): | ||||
|             self.state = XMPP_STATE_ERROR | ||||
|             self.errorinstance = XmppStreamError() | ||||
|             self.error_instance = XmppStreamError() | ||||
|         elif ( | ||||
|                 self.state == XMPP_STATE_ERROR and | ||||
|                 name != (NS_IETF_XMPP_STREAMS, 'text') | ||||
|         ): | ||||
|             if name[0] == NS_IETF_XMPP_STREAMS: | ||||
|                 self.errorinstance.condition = name[1] | ||||
|                 self.error_instance.condition = name[1] | ||||
|             else: | ||||
|                 self.errorinstance.other_elements[name] = {'attrs': dict([ | ||||
|                 self.error_instance.other_elements[name] = {'attrs': dict([ | ||||
|                     (qname, attrs.getValueByQName(qname)) for | ||||
|                     qname in attrs.getQNames() | ||||
|                 ])} | ||||
|  | @ -138,25 +142,25 @@ class XmppResponseHandler(ContentHandler): | |||
|             self.state = XMPP_STATE_FINISHED | ||||
|         elif name == (NS_ETHERX_STREAMS, 'error'): | ||||
|             raise XmppException("XMPP stream error: {error}".format( | ||||
|                 error=self.errorinstance)) | ||||
|                 error=self.error_instance)) | ||||
|         elif name == (NS_IETF_XMPP_TLS, 'proceed'): | ||||
|             self.state = XMPP_STATE_PROCEED_STARTTLS | ||||
|         elif name == (NS_IETF_XMPP_TLS, 'failure'): | ||||
|             raise XmppException("starttls initiation failed") | ||||
|         _LOG.debug('end %s', name) | ||||
|         del self.inelem[-1] | ||||
|         del self.in_elem[-1] | ||||
| 
 | ||||
|     def characters(self, content): | ||||
|         elem = self.inelem[-1] | ||||
|         elem = self.in_elem[-1] | ||||
|         if elem == (NS_IETF_XMPP_SASL, 'mechanism'): | ||||
|             self.mechanisms.append(content) | ||||
|         elif self.state == XMPP_STATE_ERROR: | ||||
|             if elem == (NS_IETF_XMPP_STREAMS, 'text'): | ||||
|                 self.errorinstance.text = content | ||||
|                 self.error_instance.text = content | ||||
|             else: | ||||
|                 self.errorinstance.other_elements[elem]['text'] = content | ||||
|                 self.error_instance.other_elements[elem]['text'] = content | ||||
|         else: | ||||
|             _LOG.warning('ignored content in %s: %s', self.inelem, content) | ||||
|             _LOG.warning('ignored content in %s: %s', self.in_elem, content) | ||||
| 
 | ||||
|     def is_valid_start(self): | ||||
|         if not self.state == XMPP_STATE_RECEIVED_FEATURES: | ||||
|  | @ -164,14 +168,43 @@ class XmppResponseHandler(ContentHandler): | |||
|         if self.expect_starttls is True and self.starttls is False: | ||||
|             raise XmppException('expected STARTTLS capable service') | ||||
|         if ( | ||||
|             'version' not in self.streaminfo or | ||||
|             self.streaminfo['version'] != '1.0' | ||||
|                 'version' not in self.stream_info or | ||||
|                 self.stream_info['version'] != '1.0' | ||||
|         ): | ||||
|             _LOG.warning( | ||||
|                 'unknown stream version %s', self.streaminfo['version']) | ||||
|                 'unknown stream version %s', self.stream_info['version']) | ||||
|         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): | ||||
|     """ | ||||
|     Xmpp resource. | ||||
|  | @ -180,7 +213,9 @@ class Xmpp(nagiosplugin.Resource): | |||
|     state = nagiosplugin.Unknown | ||||
|     cause = None | ||||
|     socket = None | ||||
|     daysleft = None | ||||
|     days_left = None | ||||
|     parser = None | ||||
|     content_handler = None | ||||
| 
 | ||||
|     def __init__( | ||||
|             self, host_address, port, ipv6, is_server, starttls, | ||||
|  | @ -192,8 +227,8 @@ class Xmpp(nagiosplugin.Resource): | |||
|         self.is_server = is_server | ||||
|         self.starttls = starttls | ||||
|         self.servername = servername | ||||
|         self.checkcerts = checkcerts | ||||
|         self.caroots = caroots | ||||
|         self.check_certs = checkcerts | ||||
|         self.ca_roots = caroots | ||||
|         self.make_parser() | ||||
|         self.set_content_handler() | ||||
| 
 | ||||
|  | @ -210,50 +245,25 @@ class Xmpp(nagiosplugin.Resource): | |||
|         Set the XMPP SAX content handler. | ||||
| 
 | ||||
|         """ | ||||
|         self.contenthandler = XmppResponseHandler( | ||||
|         self.content_handler = XmppResponseHandler( | ||||
|             expect_starttls=self.starttls) | ||||
|         self.parser.setContentHandler(self.contenthandler) | ||||
|         self.parser.setContentHandler(self.content_handler) | ||||
| 
 | ||||
|     def get_addrinfo(self): | ||||
|     def get_addr_info(self): | ||||
|         """ | ||||
|         Perform the DNS lookup and return a list of potential socket address | ||||
|         tuples as returned by :py:method:`socket.getaddrinfo`. | ||||
| 
 | ||||
|         """ | ||||
|         if self.ipv6 is None: | ||||
|             addrfamily = 0 | ||||
|             addr_family = 0 | ||||
|         elif self.ipv6 is True: | ||||
|             addrfamily = socket.AF_INET6 | ||||
|             addr_family = socket.AF_INET6 | ||||
|         else: | ||||
|             addrfamily = socket.AF_INET | ||||
|             addr_family = socket.AF_INET | ||||
|         return socket.getaddrinfo( | ||||
|             self.address, self.port, addrfamily, socket.SOCK_STREAM, | ||||
|             self.address, self.port, addr_family, socket.SOCK_STREAM, | ||||
|             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( | ||||
|             self, message_str, timeout=0.1, expected_state=None | ||||
|  | @ -283,37 +293,28 @@ class Xmpp(nagiosplugin.Resource): | |||
|                 chunks.append(data) | ||||
|             else: | ||||
|                 break | ||||
|         xmltext = b''.join(chunks).decode('utf-8') | ||||
|         _LOG.debug("read %s", xmltext) | ||||
|         self.parser.feed(xmltext) | ||||
|         xml_text = b''.join(chunks).decode('utf-8') | ||||
|         _LOG.debug("read %s", xml_text) | ||||
|         self.parser.feed(xml_text) | ||||
|         if ( | ||||
|                 expected_state is not None and | ||||
|             self.contenthandler.state != expected_state | ||||
|                 self.content_handler.state != expected_state | ||||
|         ): | ||||
|             raise XmppException( | ||||
|                 "unexpected state %s" % self.contenthandler.state) | ||||
|                 "unexpected state %s" % self.content_handler.state) | ||||
| 
 | ||||
|     def start_stream(self): | ||||
|         """ | ||||
|         Start a XMPP conversation with the server. | ||||
| 
 | ||||
|         """ | ||||
|         if self.is_server: | ||||
|             self.handle_xmpp_stanza(( | ||||
|                 "<?xml version='1.0' ?><stream:stream to='{servername}' " | ||||
|                 "xmlns='jabber:server' " | ||||
|                 "xmlns:stream='http://etherx.jabber.org/streams' " | ||||
|                 "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) | ||||
|         namespace = "jabber:server" if self.is_server else "jabber:client" | ||||
| 
 | ||||
|         self.handle_xmpp_stanza( | ||||
|             f"<stream:stream xmlns:stream='{NS_ETHERX_STREAMS}' xmlns='{namespace}' to='{self.servername}'" | ||||
|             f" version='1.0'>", | ||||
|             expected_state=XMPP_STATE_RECEIVED_FEATURES | ||||
|         ) | ||||
| 
 | ||||
|     def setup_ssl_context(self): | ||||
|         """ | ||||
|  | @ -321,17 +322,16 @@ class Xmpp(nagiosplugin.Resource): | |||
| 
 | ||||
|         """ | ||||
|         context = ssl.create_default_context() | ||||
|         context.options = ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ||||
|         if not self.checkcerts: | ||||
|         if not self.check_certs: | ||||
|             context.check_hostname = False | ||||
|             context.verify_mode = ssl.CERT_NONE | ||||
|         else: | ||||
|             context.verify_mode = ssl.CERT_REQUIRED | ||||
|             if self.caroots: | ||||
|                 if os.path.isfile(self.caroots): | ||||
|                     kwargs = {'cafile': self.caroots} | ||||
|             if self.ca_roots: | ||||
|                 if os.path.isfile(self.ca_roots): | ||||
|                     kwargs = {'cafile': self.ca_roots} | ||||
|                 else: | ||||
|                     kwargs = {'capath': self.caroots} | ||||
|                     kwargs = {'capath': self.ca_roots} | ||||
|                 context.load_verify_locations(**kwargs) | ||||
|             else: | ||||
|                 context.load_default_certs() | ||||
|  | @ -346,21 +346,22 @@ class Xmpp(nagiosplugin.Resource): | |||
|         """ | ||||
|         _LOG.debug("start initiate_tls()") | ||||
|         self.handle_xmpp_stanza( | ||||
|             "<starttls xmlns='{xmlns}'/>".format(xmlns=NS_IETF_XMPP_TLS), | ||||
|             f"<starttls xmlns='{NS_IETF_XMPP_TLS}'/>", | ||||
|             timeout=0.5, | ||||
|             expected_state=XMPP_STATE_PROCEED_STARTTLS) | ||||
|         sslcontext = self.setup_ssl_context() | ||||
|         try: | ||||
|             self.socket = sslcontext.wrap_socket( | ||||
|                 self.socket, server_hostname=self.servername) | ||||
|             _LOG.info("TLS socket setup successful") | ||||
|         except ssl.SSLError as ssle: | ||||
|             raise XmppException("SSL error %s" % ssle.strerror) | ||||
|         except ssl.CertificateError as certerr: | ||||
|             raise XmppException("Certificate error %s" % certerr) | ||||
|         except ssl.CertificateError as certificate_error: | ||||
|             raise XmppException("Certificate error %s" % certificate_error) | ||||
|         except ssl.SSLError as ssl_error: | ||||
|             raise XmppException("SSL error %s" % ssl_error.strerror) | ||||
|         self.starttls = False | ||||
|         # reset infos retrieved previously as written in RFC 3920 sec. 5. | ||||
|         self.parser.reset() | ||||
|         if self.checkcerts: | ||||
|         if self.check_certs: | ||||
|             certinfo = self.socket.getpeercert() | ||||
|             _LOG.debug("got the following certificate info: %s", certinfo) | ||||
|             _LOG.info( | ||||
|  | @ -368,12 +369,12 @@ class Xmpp(nagiosplugin.Resource): | |||
|                 certinfo['notBefore'], certinfo['notAfter']) | ||||
|             enddate = ssl.cert_time_to_seconds(certinfo['notAfter']) | ||||
|             remaining = datetime.fromtimestamp(enddate) - datetime.now() | ||||
|             self.daysleft = remaining.days | ||||
|             self.days_left = remaining.days | ||||
|         # start new parsing | ||||
|         self.make_parser() | ||||
|         self.set_content_handler() | ||||
|         self.start_stream() | ||||
|         if not self.contenthandler.is_valid_start(): | ||||
|         if not self.content_handler.is_valid_start(): | ||||
|             raise XmppException("no valid response to XMPP client request") | ||||
|         _LOG.debug("end initiate_tls()") | ||||
| 
 | ||||
|  | @ -384,9 +385,9 @@ class Xmpp(nagiosplugin.Resource): | |||
|         """ | ||||
|         _LOG.debug("start handle_xmpp()") | ||||
|         self.start_stream() | ||||
|         if not self.contenthandler.is_valid_start(): | ||||
|         if not self.content_handler.is_valid_start(): | ||||
|             raise XmppException("no valid response to XMPP client request") | ||||
|         if self.starttls is True or self.contenthandler.tlsrequired: | ||||
|         if self.starttls is True or self.content_handler.tls_required: | ||||
|             self.initiate_tls() | ||||
|         self.handle_xmpp_stanza("</stream:stream>") | ||||
|         _LOG.debug("end handle_xmpp()") | ||||
|  | @ -400,8 +401,8 @@ class Xmpp(nagiosplugin.Resource): | |||
|         start = datetime.now() | ||||
|         _LOG.debug("start probe() at %s", start) | ||||
|         try: | ||||
|             addrinfo = self.get_addrinfo() | ||||
|             self.socket = self.open_socket(addrinfo) | ||||
|             addrinfo = self.get_addr_info() | ||||
|             self.socket = open_socket(addrinfo) | ||||
|             try: | ||||
|                 self.handle_xmpp() | ||||
|             finally: | ||||
|  | @ -422,7 +423,7 @@ class Xmpp(nagiosplugin.Resource): | |||
|         _LOG.debug("end probe() at %s", end) | ||||
|         yield nagiosplugin.Metric( | ||||
|             'time', (end - start).total_seconds(), 's', min=0) | ||||
|         yield nagiosplugin.Metric('daysleft', self.daysleft, 'd') | ||||
|         yield nagiosplugin.Metric('daysleft', self.days_left, 'd') | ||||
| 
 | ||||
| 
 | ||||
| class XmppContext(nagiosplugin.ScalarContext): | ||||
|  | @ -461,7 +462,7 @@ class DaysValidContext(nagiosplugin.Context): | |||
|         self.critdays = critdays | ||||
| 
 | ||||
|     def evaluate(self, metric, resource): | ||||
|         if resource.checkcerts and metric.value is not None: | ||||
|         if resource.check_certs and metric.value is not None: | ||||
|             if self.critical.match(metric.value): | ||||
|                 return nagiosplugin.Result( | ||||
|                     nagiosplugin.Critical, | ||||
|  | @ -477,7 +478,7 @@ class DaysValidContext(nagiosplugin.Context): | |||
|         return nagiosplugin.Result(nagiosplugin.Ok) | ||||
| 
 | ||||
|     def performance(self, metric, resource): | ||||
|         if resource.checkcerts and metric.value is not None: | ||||
|         if resource.check_certs and metric.value is not None: | ||||
|             return nagiosplugin.Performance('daysvalid', metric.value, '') | ||||
|         return None | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										6
									
								
								debian/changelog
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								debian/changelog
									
										
									
									
										vendored
									
									
								
							|  | @ -1,3 +1,9 @@ | |||
| nagios-check-xmppng (0.3.3-1) unstable; urgency=medium | ||||
| 
 | ||||
|   * New upstream version | ||||
| 
 | ||||
|  -- Jan Dittberner <jandd@debian.org>  Fri, 04 Aug 2023 20:14:49 +0200 | ||||
| 
 | ||||
| nagios-check-xmppng (0.3.2-3) unstable; urgency=medium | ||||
| 
 | ||||
|   * Source only upload for migration to testing | ||||
|  |  | |||
|  | @ -7,6 +7,29 @@ target: Messaging | |||
| type: Plugin | ||||
| license: gplv3 | ||||
| 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: | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue