]> git.siccegge.de Git - dane-monitoring-plugins.git/blob - check_dane_xmpp
Rework https checker
[dane-monitoring-plugins.git] / check_dane_xmpp
1 #!/usr/bin/python3
2 #
3 #
4
5 from __future__ import print_function
6
7 import sys
8 import argparse
9 import logging
10
11 from socket import socket, AF_INET6, AF_INET, create_connection
12 from ssl import SSLError, CertificateError, SSLContext
13 from ssl import PROTOCOL_TLSv1_2, CERT_REQUIRED
14
15 from unbound import ub_ctx
16
17 from check_dane.resolve import Resolver, ResolverException, srv_lookup
18 from check_dane.tlsa import verify_tlsa_record
19 from check_dane.cert import verify_certificate, add_certificate_options
20
21 XMPP_OPEN = ("<stream:stream xmlns='jabber:{0}' xmlns:stream='"
22 "http://etherx.jabber.org/streams' xmlns:tls='http://www.ietf.org/rfc/"
23 "rfc2595.txt' to='{1}' xml:lang='en' version='1.0'>")
24 XMPP_CLOSE = "</stream:stream>"
25 XMPP_STARTTLS = "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"
26
27
28 def init_connection(sslcontext, args, family, endpoint, metadata):
29 host, _ = endpoint
30 logging.debug("Connecting to %s", endpoint)
31
32 connection = socket(family=family)
33 connection.connect(endpoint)
34
35 connection.sendall(XMPP_OPEN.format(metadata['type'], args.Host).encode())
36 answer = connection.recv(4096)
37 logging.debug(answer)
38
39 if not '</stream:features>' in answer:
40 answer = connection.recv(4096)
41 logging.debug(answer)
42
43 connection.sendall(XMPP_STARTTLS.encode())
44 answer = connection.recv(4096)
45 logging.debug(answer)
46
47 connection = sslcontext.wrap_socket(connection, server_hostname=host)
48 connection.do_handshake()
49
50 connection.sendall(XMPP_OPEN.format(metadata['type'], args.Host).encode())
51 answer = connection.recv(4096)
52 logging.debug(answer)
53
54 if not '</stream:features>' in answer:
55 answer = connection.recv(4096)
56 logging.debug(answer)
57
58 return connection
59
60
61 def close_connection(connection):
62 connection.send(XMPP_CLOSE.encode())
63 answer = connection.recv(512)
64 logging.debug(answer)
65
66
67 def init(args):
68 sslcontext = SSLContext(PROTOCOL_TLSv1_2)
69 sslcontext.verify_mode = CERT_REQUIRED
70 sslcontext.load_verify_locations(args.castore)
71
72 resolver = ub_ctx()
73 resolver.add_ta_file(args.ancor)
74
75 return sslcontext, resolver
76
77
78 def main():
79 logging.basicConfig(format='%(levelname)5s %(message)s')
80 parser = argparse.ArgumentParser()
81 parser.add_argument("Host")
82
83 parser.add_argument("--verbose", action="store_true")
84 parser.add_argument("--quiet", action="store_true")
85 # parser.add_argument("-p", "--port",
86 # action="store", type=int, default=0,
87 # help="XMPP port")
88 group = parser.add_mutually_exclusive_group()
89 group.add_argument("--s2s", action="store_true",
90 help="Only check server-to-server connections")
91 group.add_argument("--c2s", action="store_true",
92 help="Only check client-to-server connections")
93 # parser.add_argument("--ssl",
94 # action="store_true",
95 # help="Use direct TLS connection instead of starttls (default: disabled)")
96 parser.add_argument("--check-dane",
97 action="store_false",
98 help="Verify presented certificate via DANE (default: enabled)")
99 parser.add_argument("--check-ca",
100 action="store_false",
101 help="Verify presented certificate via the CA system (default: enabled)")
102 parser.add_argument("--check-expire",
103 action="store_false",
104 help="Verify presented certificate for expiration (default: enabled)")
105
106 parser.add_argument("-a", "--ancor",
107 action="store", type=str, default="/etc/unbound/root.key",
108 help="DNSSEC root ancor")
109 parser.add_argument("--castore", action="store", type=str,
110 default="/etc/ssl/certs/ca-certificates.crt",
111 help="ca certificate bundle")
112
113 group = parser.add_mutually_exclusive_group()
114 group.add_argument("-6", "--6", action="store_true", dest="use6", help="check via IPv6 only")
115 group.add_argument("-4", "--4", action="store_true", dest="use4", help="check via IPv4 only")
116 group.add_argument("--64", action="store_false", dest="use64", help="check via IPv4 and IPv6 (default)")
117
118 add_certificate_options(parser)
119
120 args = parser.parse_args()
121
122 if args.verbose:
123 logging.getLogger().setLevel(logging.DEBUG)
124 elif args.quiet:
125 logging.getLogger().setLevel(logging.WARNING)
126 else:
127 logging.getLogger().setLevel(logging.INFO)
128
129 host = args.Host.encode('idna').decode()
130
131 sslcontext, resolver = init(args)
132
133 if args.use6:
134 afamilies = [AF_INET6]
135 elif args.use4:
136 afamilies = [AF_INET6]
137 else:
138 afamilies = [AF_INET, AF_INET6]
139
140 xresolver = Resolver(args.ancor)
141 endpoints = []
142 if not args.s2s:
143 for endpoint, meta in srv_lookup("_xmpp-client._tcp.%s" % host, xresolver):
144 meta['type'] = 'client'
145 endpoints.append((endpoint, meta))
146 if not args.c2s:
147 for endpoint, meta in srv_lookup("_xmpp-server._tcp.%s" % host, xresolver):
148 meta['type'] = 'server'
149 endpoints.append((endpoint, meta))
150
151 retval = 0
152 for afamily in afamilies:
153 for endpoint, metadata in endpoints:
154 host, port = endpoint
155 try:
156 connection = init_connection(sslcontext, args, afamily, endpoint, metadata)
157 except:
158 logging.exception("Connection refused")
159 return 2
160
161 nretval = verify_certificate(connection.getpeercert(), args)
162 retval = max(retval, nretval)
163 nretval = verify_tlsa_record(resolver, "_%d._tcp.%s" % (port, host),
164 connection.getpeercert(binary_form=True))
165 retval = max(retval, nretval)
166
167 close_connection(connection)
168
169 return retval
170
171
172 if __name__ == '__main__':
173 sys.exit(main())