bzr branch
http://bzr.recompile.se/loggerhead/mandos/trunk
| 3
by Björn Påhlsson Python based server | 1 | #!/usr/bin/python
 | 
| 2 | ||
| 10
by Teddy Hogeborn * server.py: Bug fix: Do "from __future__ import division". | 3 | from __future__ import division | 
| 4 | ||
| 3
by Björn Påhlsson Python based server | 5 | import SocketServer | 
| 6 | import socket | |
| 7 | import select | |
| 8 | from optparse import OptionParser | |
| 9 | import datetime | |
| 10 | import errno | |
| 11 | import gnutls.crypto | |
| 12 | import gnutls.connection | |
| 13 | import gnutls.errors | |
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 14 | import gnutls.library.functions | 
| 15 | import gnutls.library.constants | |
| 16 | import gnutls.library.types | |
| 3
by Björn Påhlsson Python based server | 17 | import ConfigParser | 
| 4
by Teddy Hogeborn * server.py (Client.created, Client.next_check): New. | 18 | import sys | 
| 9
by Teddy Hogeborn * client.cpp (main): Get t_old early since it is used on error exits. | 19 | import re | 
| 20 | import os | |
| 21 | import signal | |
| 10
by Teddy Hogeborn * server.py: Bug fix: Do "from __future__ import division". | 22 | from sets import Set | 
| 23 | import subprocess | |
| 5
by Teddy Hogeborn * server.py (server_metaclass): New. | 24 | |
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 25 | import dbus | 
| 26 | import gobject | |
| 27 | import avahi | |
| 28 | from dbus.mainloop.glib import DBusGMainLoop | |
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 29 | import ctypes | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 30 | |
| 13
by Björn Påhlsson Added following support: | 31 | import logging | 
| 32 | import logging.handlers | |
| 33 | ||
| 34 | # logghandler.setFormatter(logging.Formatter('%(levelname)s %(message)s')
 | |
| 35 | ||
| 36 | logger = logging.Logger('mandos') | |
| 37 | logger.addHandler(logging.handlers.SysLogHandler(facility = logging.handlers.SysLogHandler.LOG_DAEMON)) | |
| 38 | ||
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 39 | # This variable is used to optionally bind to a specified interface.
 | 
| 40 | # It is a global variable to fit in with the other variables from the
 | |
| 41 | # Avahi server example code.
 | |
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 42 | serviceInterface = avahi.IF_UNSPEC | 
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 43 | # From the Avahi server example code:
 | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 44 | serviceName = "Mandos" | 
| 45 | serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html | |
| 46 | servicePort = None # Not known at startup | |
| 47 | serviceTXT = [] # TXT record for the service | |
| 48 | domain = "" # Domain to publish on, default to .local | |
| 49 | host = "" # Host to publish records for, default to localhost | |
| 50 | group = None #our entry group | |
| 51 | rename_count = 12 # Counter so we only rename after collisions a | |
| 52 |                         # sensible number of times
 | |
| 53 | # End of Avahi example code
 | |
| 54 | ||
| 55 | ||
| 3
by Björn Påhlsson Python based server | 56 | class Client(object): | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 57 | """A representation of a client host served by this server. | 
| 58 |     Attributes:
 | |
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 59 |     name:      string; from the config file, used in log messages
 | 
| 60 |     fingerprint: string (40 or 32 hexadecimal digits); used to
 | |
| 61 |                  uniquely identify the client
 | |
| 62 |     secret:    bytestring; sent verbatim (over TLS) to client
 | |
| 63 |     fqdn:      string (FQDN); available for use by the checker command
 | |
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 64 |     created:   datetime.datetime()
 | 
| 65 |     last_seen: datetime.datetime() or None if not yet seen
 | |
| 66 |     timeout:   datetime.timedelta(); How long from last_seen until
 | |
| 67 |                                      this client is invalid
 | |
| 68 |     interval:  datetime.timedelta(); How often to start a new checker
 | |
| 69 |     stop_hook: If set, called by stop() as stop_hook(self)
 | |
| 70 |     checker:   subprocess.Popen(); a running checker process used
 | |
| 71 |                                    to see if the client lives.
 | |
| 72 |                                    Is None if no process is running.
 | |
| 73 |     checker_initiator_tag: a gobject event source tag, or None
 | |
| 74 |     stop_initiator_tag:    - '' -
 | |
| 75 |     checker_callback_tag:  - '' -
 | |
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 76 |     checker_command: string; External command which is run to check if
 | 
| 77 |                      client lives.  %()s expansions are done at
 | |
| 78 |                      runtime with vars(self) as dict, so that for
 | |
| 79 |                      instance %(name)s can be used in the command.
 | |
| 80 |     Private attibutes:
 | |
| 81 |     _timeout: Real variable for 'timeout'
 | |
| 82 |     _interval: Real variable for 'interval'
 | |
| 83 |     _timeout_milliseconds: Used by gobject.timeout_add()
 | |
| 84 |     _interval_milliseconds: - '' -
 | |
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 85 |     """
 | 
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 86 | def _set_timeout(self, timeout): | 
| 87 |         "Setter function for 'timeout' attribute"
 | |
| 88 | self._timeout = timeout | |
| 89 | self._timeout_milliseconds = ((self.timeout.days | |
| 90 | * 24 * 60 * 60 * 1000) | |
| 91 | + (self.timeout.seconds * 1000) | |
| 92 | + (self.timeout.microseconds | |
| 93 | // 1000)) | |
| 94 | timeout = property(lambda self: self._timeout, | |
| 95 | _set_timeout) | |
| 96 | del _set_timeout | |
| 97 | def _set_interval(self, interval): | |
| 98 |         "Setter function for 'interval' attribute"
 | |
| 99 | self._interval = interval | |
| 100 | self._interval_milliseconds = ((self.interval.days | |
| 101 | * 24 * 60 * 60 * 1000) | |
| 102 | + (self.interval.seconds | |
| 103 | * 1000) | |
| 104 | + (self.interval.microseconds | |
| 105 | // 1000)) | |
| 106 | interval = property(lambda self: self._interval, | |
| 107 | _set_interval) | |
| 108 | del _set_interval | |
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 109 | def __init__(self, name=None, options=None, stop_hook=None, | 
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 110 | fingerprint=None, secret=None, secfile=None, fqdn=None, | 
| 111 | timeout=None, interval=-1, checker=None): | |
| 3
by Björn Påhlsson Python based server | 112 | self.name = name | 
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 113 |         # Uppercase and remove spaces from fingerprint
 | 
| 114 |         # for later comparison purposes with return value of
 | |
| 115 |         # the fingerprint() function
 | |
| 116 | self.fingerprint = fingerprint.upper().replace(u" ", u"") | |
| 117 | if secret: | |
| 118 | self.secret = secret.decode(u"base64") | |
| 119 | elif secfile: | |
| 120 | sf = open(secfile) | |
| 121 | self.secret = sf.read() | |
| 122 | sf.close() | |
| 3
by Björn Påhlsson Python based server | 123 | else: | 
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 124 | raise RuntimeError(u"No secret or secfile for client %s" | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 125 | % self.name) | 
| 9
by Teddy Hogeborn * client.cpp (main): Get t_old early since it is used on error exits. | 126 | self.fqdn = fqdn # string | 
| 4
by Teddy Hogeborn * server.py (Client.created, Client.next_check): New. | 127 | self.created = datetime.datetime.now() | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 128 | self.last_seen = None | 
| 3
by Björn Påhlsson Python based server | 129 | if timeout is None: | 
| 5
by Teddy Hogeborn * server.py (server_metaclass): New. | 130 | timeout = options.timeout | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 131 | self.timeout = timeout | 
| 3
by Björn Påhlsson Python based server | 132 | if interval == -1: | 
| 5
by Teddy Hogeborn * server.py (server_metaclass): New. | 133 | interval = options.interval | 
| 10
by Teddy Hogeborn * server.py: Bug fix: Do "from __future__ import division". | 134 | else: | 
| 135 | interval = string_to_delta(interval) | |
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 136 | self.interval = interval | 
| 137 | self.stop_hook = stop_hook | |
| 138 | self.checker = None | |
| 139 | self.checker_initiator_tag = None | |
| 140 | self.stop_initiator_tag = None | |
| 141 | self.checker_callback_tag = None | |
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 142 | self.check_command = checker | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 143 | def start(self): | 
| 144 | """Start this clients checker and timeout hooks""" | |
| 145 |         # Schedule a new checker to be started an 'interval' from now,
 | |
| 146 |         # and every interval from then on.
 | |
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 147 | self.checker_initiator_tag = gobject.timeout_add\ | 
| 148 | (self._interval_milliseconds, | |
| 149 | self.start_checker) | |
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 150 |         # Also start a new checker *right now*.
 | 
| 151 | self.start_checker() | |
| 152 |         # Schedule a stop() when 'timeout' has passed
 | |
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 153 | self.stop_initiator_tag = gobject.timeout_add\ | 
| 154 | (self._timeout_milliseconds, | |
| 155 | self.stop) | |
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 156 | def stop(self): | 
| 157 | """Stop this client. | |
| 158 |         The possibility that this client might be restarted is left
 | |
| 159 |         open, but not currently used."""
 | |
| 13
by Björn Påhlsson Added following support: | 160 | logger.debug(u"Stopping client %s", self.name) | 
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 161 | self.secret = None | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 162 | if self.stop_initiator_tag: | 
| 163 | gobject.source_remove(self.stop_initiator_tag) | |
| 164 | self.stop_initiator_tag = None | |
| 165 | if self.checker_initiator_tag: | |
| 166 | gobject.source_remove(self.checker_initiator_tag) | |
| 167 | self.checker_initiator_tag = None | |
| 168 | self.stop_checker() | |
| 169 | if self.stop_hook: | |
| 170 | self.stop_hook(self) | |
| 171 |         # Do not run this again if called by a gobject.timeout_add
 | |
| 172 | return False | |
| 173 | def __del__(self): | |
| 174 |         # Some code duplication here and in stop()
 | |
| 175 | if hasattr(self, "stop_initiator_tag") \ | |
| 176 | and self.stop_initiator_tag: | |
| 177 | gobject.source_remove(self.stop_initiator_tag) | |
| 178 | self.stop_initiator_tag = None | |
| 179 | if hasattr(self, "checker_initiator_tag") \ | |
| 180 | and self.checker_initiator_tag: | |
| 181 | gobject.source_remove(self.checker_initiator_tag) | |
| 182 | self.checker_initiator_tag = None | |
| 183 | self.stop_checker() | |
| 184 | def checker_callback(self, pid, condition): | |
| 185 | """The checker has completed, so take appropriate actions.""" | |
| 10
by Teddy Hogeborn * server.py: Bug fix: Do "from __future__ import division". | 186 | now = datetime.datetime.now() | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 187 | if os.WIFEXITED(condition) \ | 
| 188 | and (os.WEXITSTATUS(condition) == 0): | |
| 13
by Björn Påhlsson Added following support: | 189 | logger.debug(u"Checker for %(name)s succeeded", | 
| 190 | vars(self)) | |
| 9
by Teddy Hogeborn * client.cpp (main): Get t_old early since it is used on error exits. | 191 | self.last_seen = now | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 192 | gobject.source_remove(self.stop_initiator_tag) | 
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 193 | self.stop_initiator_tag = gobject.timeout_add\ | 
| 194 | (self._timeout_milliseconds, | |
| 195 | self.stop) | |
| 13
by Björn Påhlsson Added following support: | 196 | if not os.WIFEXITED(condition): | 
| 197 | logger.warning(u"Checker for %(name)s crashed?", | |
| 198 | vars(self)) | |
| 199 | else: | |
| 200 | logger.debug(u"Checker for %(name)s failed", | |
| 201 | vars(self)) | |
| 202 | self.checker = None | |
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 203 | self.checker_callback_tag = None | 
| 9
by Teddy Hogeborn * client.cpp (main): Get t_old early since it is used on error exits. | 204 | def start_checker(self): | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 205 | """Start a new checker subprocess if one is not running. | 
| 206 |         If a checker already exists, leave it running and do
 | |
| 207 |         nothing."""
 | |
| 208 | if self.checker is None: | |
| 13
by Björn Påhlsson Added following support: | 209 | logger.debug(u"Starting checker for %s", | 
| 210 | self.name) | |
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 211 | try: | 
| 212 | command = self.check_command % self.fqdn | |
| 213 | except TypeError: | |
| 214 | escaped_attrs = dict((key, re.escape(str(val))) | |
| 215 | for key, val in | |
| 216 | vars(self).iteritems()) | |
| 13
by Björn Påhlsson Added following support: | 217 | try: | 
| 218 | command = self.check_command % escaped_attrs | |
| 219 | except TypeError, error: | |
| 220 | logger.critical(u'Could not format string "%s": %s', | |
| 221 | self.check_command, error) | |
| 222 | return True # Try again later | |
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 223 | try: | 
| 224 | self.checker = subprocess.\ | |
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 225 | Popen(command, | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 226 | stdout=subprocess.PIPE, | 
| 227 | close_fds=True, shell=True, | |
| 228 | cwd="/") | |
| 229 | self.checker_callback_tag = gobject.\ | |
| 230 | child_watch_add(self.checker.pid, | |
| 231 | self.\ | |
| 232 | checker_callback) | |
| 233 | except subprocess.OSError, error: | |
| 13
by Björn Påhlsson Added following support: | 234 | logger.error(u"Failed to start subprocess: %s", | 
| 235 | error) | |
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 236 |         # Re-run this periodically if run by gobject.timeout_add
 | 
| 237 | return True | |
| 9
by Teddy Hogeborn * client.cpp (main): Get t_old early since it is used on error exits. | 238 | def stop_checker(self): | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 239 | """Force the checker process, if any, to stop.""" | 
| 240 | if not hasattr(self, "checker") or self.checker is None: | |
| 9
by Teddy Hogeborn * client.cpp (main): Get t_old early since it is used on error exits. | 241 |             return
 | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 242 | gobject.source_remove(self.checker_callback_tag) | 
| 243 | self.checker_callback_tag = None | |
| 9
by Teddy Hogeborn * client.cpp (main): Get t_old early since it is used on error exits. | 244 | os.kill(self.checker.pid, signal.SIGTERM) | 
| 245 | if self.checker.poll() is None: | |
| 246 | os.kill(self.checker.pid, signal.SIGKILL) | |
| 247 | self.checker = None | |
| 248 | def still_valid(self, now=None): | |
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 249 | """Has the timeout not yet passed for this client?""" | 
| 9
by Teddy Hogeborn * client.cpp (main): Get t_old early since it is used on error exits. | 250 | if now is None: | 
| 251 | now = datetime.datetime.now() | |
| 10
by Teddy Hogeborn * server.py: Bug fix: Do "from __future__ import division". | 252 | if self.last_seen is None: | 
| 253 | return now < (self.created + self.timeout) | |
| 254 | else: | |
| 255 | return now < (self.last_seen + self.timeout) | |
| 3
by Björn Påhlsson Python based server | 256 | |
| 257 | ||
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 258 | def peer_certificate(session): | 
| 259 |     # If not an OpenPGP certificate...
 | |
| 260 | if gnutls.library.functions.gnutls_certificate_type_get\ | |
| 261 | (session._c_object) \ | |
| 262 | != gnutls.library.constants.GNUTLS_CRT_OPENPGP: | |
| 263 |         # ...do the normal thing
 | |
| 264 | return session.peer_certificate | |
| 265 | list_size = ctypes.c_uint() | |
| 266 | cert_list = gnutls.library.functions.gnutls_certificate_get_peers\ | |
| 267 | (session._c_object, ctypes.byref(list_size)) | |
| 268 | if list_size.value == 0: | |
| 269 | return None | |
| 270 | cert = cert_list[0] | |
| 271 | return ctypes.string_at(cert.data, cert.size) | |
| 272 | ||
| 273 | ||
| 274 | def fingerprint(openpgp): | |
| 275 |     # New empty GnuTLS certificate
 | |
| 276 | crt = gnutls.library.types.gnutls_openpgp_crt_t() | |
| 277 | gnutls.library.functions.gnutls_openpgp_crt_init\ | |
| 278 | (ctypes.byref(crt)) | |
| 279 |     # New GnuTLS "datum" with the OpenPGP public key
 | |
| 280 | datum = gnutls.library.types.gnutls_datum_t\ | |
| 281 | (ctypes.cast(ctypes.c_char_p(openpgp), | |
| 282 | ctypes.POINTER(ctypes.c_ubyte)), | |
| 283 | ctypes.c_uint(len(openpgp))) | |
| 284 |     # Import the OpenPGP public key into the certificate
 | |
| 285 | ret = gnutls.library.functions.gnutls_openpgp_crt_import\ | |
| 286 | (crt, | |
| 287 | ctypes.byref(datum), | |
| 288 | gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW) | |
| 289 |     # New buffer for the fingerprint
 | |
| 290 | buffer = ctypes.create_string_buffer(20) | |
| 291 | buffer_length = ctypes.c_size_t() | |
| 292 |     # Get the fingerprint from the certificate into the buffer
 | |
| 293 | gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\ | |
| 294 | (crt, ctypes.byref(buffer), ctypes.byref(buffer_length)) | |
| 295 |     # Deinit the certificate
 | |
| 296 | gnutls.library.functions.gnutls_openpgp_crt_deinit(crt) | |
| 297 |     # Convert the buffer to a Python bytestring
 | |
| 298 | fpr = ctypes.string_at(buffer, buffer_length.value) | |
| 299 |     # Convert the bytestring to hexadecimal notation
 | |
| 300 | hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr) | |
| 301 | return hex_fpr | |
| 302 | ||
| 303 | ||
| 3
by Björn Påhlsson Python based server | 304 | class tcp_handler(SocketServer.BaseRequestHandler, object): | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 305 | """A TCP request handler class. | 
| 306 |     Instantiated by IPv6_TCPServer for each request to handle it.
 | |
| 307 |     Note: This will run in its own forked process."""
 | |
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 308 | |
| 3
by Björn Påhlsson Python based server | 309 | def handle(self): | 
| 13
by Björn Påhlsson Added following support: | 310 | logger.debug(u"TCP connection from: %s", | 
| 311 | unicode(self.client_address)) | |
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 312 | session = gnutls.connection.ClientSession(self.request, | 
| 313 | gnutls.connection.\ | |
| 314 | X509Credentials()) | |
| 315 | ||
| 316 |         #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
 | |
| 317 |         #                "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
 | |
| 318 |         #                "+DHE-DSS"))
 | |
| 319 | priority = "SECURE256" | |
| 320 | ||
| 321 | gnutls.library.functions.gnutls_priority_set_direct\ | |
| 322 | (session._c_object, priority, None); | |
| 323 | ||
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 324 | try: | 
| 325 | session.handshake() | |
| 326 | except gnutls.errors.GNUTLSError, error: | |
| 13
by Björn Påhlsson Added following support: | 327 | logger.debug(u"Handshake failed: %s", error) | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 328 |             # Do not run session.bye() here: the session is not
 | 
| 329 |             # established.  Just abandon the request.
 | |
| 330 |             return
 | |
| 3
by Björn Påhlsson Python based server | 331 | try: | 
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 332 | fpr = fingerprint(peer_certificate(session)) | 
| 333 | except (TypeError, gnutls.errors.GNUTLSError), error: | |
| 13
by Björn Påhlsson Added following support: | 334 | logger.debug(u"Bad certificate: %s", error) | 
| 3
by Björn Påhlsson Python based server | 335 | session.bye() | 
| 336 |             return
 | |
| 13
by Björn Påhlsson Added following support: | 337 | logger.debug(u"Fingerprint: %s", fpr) | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 338 | client = None | 
| 339 | for c in clients: | |
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 340 | if c.fingerprint == fpr: | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 341 | client = c | 
| 342 |                 break
 | |
| 343 |         # Have to check if client.still_valid(), since it is possible
 | |
| 344 |         # that the client timed out while establishing the GnuTLS
 | |
| 345 |         # session.
 | |
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 346 | if (not client) or (not client.still_valid()): | 
| 13
by Björn Påhlsson Added following support: | 347 | if client: | 
| 348 | logger.debug(u"Client %(name)s is invalid", | |
| 349 | vars(client)) | |
| 350 | else: | |
| 351 | logger.debug(u"Client not found for fingerprint: %s", | |
| 352 | fpr) | |
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 353 | session.bye() | 
| 354 |             return
 | |
| 355 | sent_size = 0 | |
| 356 | while sent_size < len(client.secret): | |
| 357 | sent = session.send(client.secret[sent_size:]) | |
| 13
by Björn Påhlsson Added following support: | 358 | logger.debug(u"Sent: %d, remaining: %d", | 
| 359 | sent, len(client.secret) | |
| 360 | - (sent_size + sent)) | |
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 361 | sent_size += sent | 
| 3
by Björn Påhlsson Python based server | 362 | session.bye() | 
| 363 | ||
| 5
by Teddy Hogeborn * server.py (server_metaclass): New. | 364 | |
| 3
by Björn Påhlsson Python based server | 365 | class IPv6_TCPServer(SocketServer.ForkingTCPServer, object): | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 366 | """IPv6 TCP server. Accepts 'None' as address and/or port. | 
| 367 |     Attributes:
 | |
| 368 |         options:        Command line options
 | |
| 369 |         clients:        Set() of Client objects
 | |
| 370 |     """
 | |
| 371 | address_family = socket.AF_INET6 | |
| 372 | def __init__(self, *args, **kwargs): | |
| 373 | if "options" in kwargs: | |
| 374 | self.options = kwargs["options"] | |
| 375 | del kwargs["options"] | |
| 376 | if "clients" in kwargs: | |
| 377 | self.clients = kwargs["clients"] | |
| 378 | del kwargs["clients"] | |
| 379 | return super(type(self), self).__init__(*args, **kwargs) | |
| 380 | def server_bind(self): | |
| 381 | """This overrides the normal server_bind() function | |
| 382 |         to bind to an interface if one was specified, and also NOT to
 | |
| 383 |         bind to an address or port if they were not specified."""
 | |
| 384 | if self.options.interface: | |
| 385 | if not hasattr(socket, "SO_BINDTODEVICE"): | |
| 386 |                 # From /usr/include/asm-i486/socket.h
 | |
| 387 | socket.SO_BINDTODEVICE = 25 | |
| 388 | try: | |
| 389 | self.socket.setsockopt(socket.SOL_SOCKET, | |
| 390 | socket.SO_BINDTODEVICE, | |
| 391 | self.options.interface) | |
| 392 | except socket.error, error: | |
| 393 | if error[0] == errno.EPERM: | |
| 13
by Björn Påhlsson Added following support: | 394 | logger.warning(u"No permission to" | 
| 395 | u" bind to interface %s", | |
| 396 | self.options.interface) | |
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 397 | else: | 
| 398 | raise error | |
| 399 |         # Only bind(2) the socket if we really need to.
 | |
| 400 | if self.server_address[0] or self.server_address[1]: | |
| 401 | if not self.server_address[0]: | |
| 402 | in6addr_any = "::" | |
| 403 | self.server_address = (in6addr_any, | |
| 404 | self.server_address[1]) | |
| 405 | elif self.server_address[1] is None: | |
| 406 | self.server_address = (self.server_address[0], | |
| 407 | 0) | |
| 408 | return super(type(self), self).server_bind() | |
| 10
by Teddy Hogeborn * server.py: Bug fix: Do "from __future__ import division". | 409 | |
| 3
by Björn Påhlsson Python based server | 410 | |
| 4
by Teddy Hogeborn * server.py (Client.created, Client.next_check): New. | 411 | def string_to_delta(interval): | 
| 412 | """Parse a string and return a datetime.timedelta | |
| 413 | ||
| 414 |     >>> string_to_delta('7d')
 | |
| 415 |     datetime.timedelta(7)
 | |
| 416 |     >>> string_to_delta('60s')
 | |
| 417 |     datetime.timedelta(0, 60)
 | |
| 418 |     >>> string_to_delta('60m')
 | |
| 419 |     datetime.timedelta(0, 3600)
 | |
| 420 |     >>> string_to_delta('24h')
 | |
| 421 |     datetime.timedelta(1)
 | |
| 422 |     >>> string_to_delta(u'1w')
 | |
| 423 |     datetime.timedelta(7)
 | |
| 424 |     """
 | |
| 425 | try: | |
| 426 | suffix=unicode(interval[-1]) | |
| 427 | value=int(interval[:-1]) | |
| 428 | if suffix == u"d": | |
| 429 | delta = datetime.timedelta(value) | |
| 430 | elif suffix == u"s": | |
| 431 | delta = datetime.timedelta(0, value) | |
| 432 | elif suffix == u"m": | |
| 433 | delta = datetime.timedelta(0, 0, 0, 0, value) | |
| 434 | elif suffix == u"h": | |
| 435 | delta = datetime.timedelta(0, 0, 0, 0, 0, value) | |
| 436 | elif suffix == u"w": | |
| 437 | delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value) | |
| 438 | else: | |
| 439 | raise ValueError | |
| 440 | except (ValueError, IndexError): | |
| 441 | raise ValueError | |
| 442 | return delta | |
| 443 | ||
| 8
by Teddy Hogeborn * Makefile (client_debug): Bug fix; add quotes and / to CERT_ROOT. | 444 | |
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 445 | def add_service(): | 
| 446 | """From the Avahi server example code""" | |
| 447 | global group, serviceName, serviceType, servicePort, serviceTXT, \ | |
| 448 | domain, host | |
| 449 | if group is None: | |
| 450 | group = dbus.Interface( | |
| 451 | bus.get_object( avahi.DBUS_NAME, | |
| 452 | server.EntryGroupNew()), | |
| 453 | avahi.DBUS_INTERFACE_ENTRY_GROUP) | |
| 454 | group.connect_to_signal('StateChanged', | |
| 455 | entry_group_state_changed) | |
| 13
by Björn Påhlsson Added following support: | 456 | logger.debug(u"Adding service '%s' of type '%s' ...", | 
| 457 | serviceName, serviceType) | |
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 458 | |
| 459 | group.AddService( | |
| 460 | serviceInterface, # interface | |
| 461 | avahi.PROTO_INET6, # protocol | |
| 462 | dbus.UInt32(0), # flags | |
| 463 | serviceName, serviceType, | |
| 464 | domain, host, | |
| 465 | dbus.UInt16(servicePort), | |
| 466 | avahi.string_array_to_txt_array(serviceTXT)) | |
| 467 | group.Commit() | |
| 468 | ||
| 469 | ||
| 470 | def remove_service(): | |
| 471 | """From the Avahi server example code""" | |
| 472 | global group | |
| 473 | ||
| 474 | if not group is None: | |
| 475 | group.Reset() | |
| 476 | ||
| 477 | ||
| 478 | def server_state_changed(state): | |
| 479 | """From the Avahi server example code""" | |
| 480 | if state == avahi.SERVER_COLLISION: | |
| 13
by Björn Påhlsson Added following support: | 481 | logger.warning(u"Server name collision") | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 482 | remove_service() | 
| 483 | elif state == avahi.SERVER_RUNNING: | |
| 484 | add_service() | |
| 485 | ||
| 486 | ||
| 487 | def entry_group_state_changed(state, error): | |
| 488 | """From the Avahi server example code""" | |
| 489 | global serviceName, server, rename_count | |
| 490 | ||
| 13
by Björn Påhlsson Added following support: | 491 | logger.debug(u"state change: %i", state) | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 492 | |
| 493 | if state == avahi.ENTRY_GROUP_ESTABLISHED: | |
| 13
by Björn Påhlsson Added following support: | 494 | logger.debug(u"Service established.") | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 495 | elif state == avahi.ENTRY_GROUP_COLLISION: | 
| 496 | ||
| 497 | rename_count = rename_count - 1 | |
| 498 | if rename_count > 0: | |
| 499 | name = server.GetAlternativeServiceName(name) | |
| 13
by Björn Påhlsson Added following support: | 500 | logger.warning(u"Service name collision, " | 
| 501 | u"changing name to '%s' ...", name) | |
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 502 | remove_service() | 
| 503 | add_service() | |
| 504 | ||
| 505 | else: | |
| 13
by Björn Påhlsson Added following support: | 506 | logger.error(u"No suitable service name found " | 
| 507 | u"after %i retries, exiting.", | |
| 508 | n_rename) | |
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 509 | main_loop.quit() | 
| 510 | elif state == avahi.ENTRY_GROUP_FAILURE: | |
| 13
by Björn Påhlsson Added following support: | 511 | logger.error(u"Error in group state changed %s", | 
| 512 | unicode(error)) | |
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 513 | main_loop.quit() | 
| 514 |         return
 | |
| 515 | ||
| 516 | ||
| 517 | def if_nametoindex(interface): | |
| 518 | """Call the C function if_nametoindex()""" | |
| 519 | try: | |
| 520 | libc = ctypes.cdll.LoadLibrary("libc.so.6") | |
| 521 | return libc.if_nametoindex(interface) | |
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 522 | except (OSError, AttributeError): | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 523 | if "struct" not in sys.modules: | 
| 524 | import struct | |
| 525 | if "fcntl" not in sys.modules: | |
| 526 | import fcntl | |
| 527 | SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h | |
| 528 | s = socket.socket() | |
| 529 | ifreq = fcntl.ioctl(s, SIOCGIFINDEX, | |
| 530 | struct.pack("16s16x", interface)) | |
| 531 | s.close() | |
| 532 | interface_index = struct.unpack("I", ifreq[16:20])[0] | |
| 533 | return interface_index | |
| 534 | ||
| 535 | ||
| 536 | if __name__ == '__main__': | |
| 3
by Björn Påhlsson Python based server | 537 | parser = OptionParser() | 
| 538 | parser.add_option("-i", "--interface", type="string", | |
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 539 | default=None, metavar="IF", | 
| 540 | help="Bind to interface IF") | |
| 3
by Björn Påhlsson Python based server | 541 | parser.add_option("--cert", type="string", default="cert.pem", | 
| 542 | metavar="FILE", | |
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 543 | help="Public key certificate PEM file to use") | 
| 3
by Björn Påhlsson Python based server | 544 | parser.add_option("--key", type="string", default="key.pem", | 
| 545 | metavar="FILE", | |
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 546 | help="Private key PEM file to use") | 
| 3
by Björn Påhlsson Python based server | 547 | parser.add_option("--ca", type="string", default="ca.pem", | 
| 548 | metavar="FILE", | |
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 549 | help="Certificate Authority certificate PEM file to use") | 
| 3
by Björn Påhlsson Python based server | 550 | parser.add_option("--crl", type="string", default="crl.pem", | 
| 551 | metavar="FILE", | |
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 552 | help="Certificate Revokation List PEM file to use") | 
| 553 | parser.add_option("-p", "--port", type="int", default=None, | |
| 3
by Björn Påhlsson Python based server | 554 | help="Port number to receive requests on") | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 555 | parser.add_option("--timeout", type="string", # Parsed later | 
| 556 | default="1h", | |
| 3
by Björn Påhlsson Python based server | 557 | help="Amount of downtime allowed for clients") | 
| 4
by Teddy Hogeborn * server.py (Client.created, Client.next_check): New. | 558 | parser.add_option("--interval", type="string", # Parsed later | 
| 559 | default="5m", | |
| 560 | help="How often to check that a client is up") | |
| 561 | parser.add_option("--check", action="store_true", default=False, | |
| 562 | help="Run self-test") | |
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 563 | parser.add_option("--debug", action="store_true", default=False, | 
| 564 | help="Debug mode") | |
| 3
by Björn Påhlsson Python based server | 565 | (options, args) = parser.parse_args() | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 566 | |
| 4
by Teddy Hogeborn * server.py (Client.created, Client.next_check): New. | 567 | if options.check: | 
| 568 | import doctest | |
| 569 | doctest.testmod() | |
| 570 | sys.exit() | |
| 3
by Björn Påhlsson Python based server | 571 | |
| 4
by Teddy Hogeborn * server.py (Client.created, Client.next_check): New. | 572 |     # Parse the time arguments
 | 
| 3
by Björn Påhlsson Python based server | 573 | try: | 
| 4
by Teddy Hogeborn * server.py (Client.created, Client.next_check): New. | 574 | options.timeout = string_to_delta(options.timeout) | 
| 575 | except ValueError: | |
| 3
by Björn Påhlsson Python based server | 576 | parser.error("option --timeout: Unparseable time") | 
| 4
by Teddy Hogeborn * server.py (Client.created, Client.next_check): New. | 577 | try: | 
| 578 | options.interval = string_to_delta(options.interval) | |
| 579 | except ValueError: | |
| 580 | parser.error("option --interval: Unparseable time") | |
| 581 | ||
| 3
by Björn Påhlsson Python based server | 582 |     # Parse config file
 | 
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 583 | defaults = { "checker": "sleep 1; fping -q -- %%(fqdn)s" } | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 584 | client_config = ConfigParser.SafeConfigParser(defaults) | 
| 585 |     #client_config.readfp(open("secrets.conf"), "secrets.conf")
 | |
| 586 | client_config.read("mandos-clients.conf") | |
| 587 | ||
| 588 |     # From the Avahi server example code
 | |
| 589 | DBusGMainLoop(set_as_default=True ) | |
| 590 | main_loop = gobject.MainLoop() | |
| 591 | bus = dbus.SystemBus() | |
| 592 | server = dbus.Interface( | |
| 593 | bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ), | |
| 594 | avahi.DBUS_INTERFACE_SERVER ) | |
| 595 |     # End of Avahi example code
 | |
| 596 | ||
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 597 | debug = options.debug | 
| 598 | ||
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 599 | clients = Set() | 
| 600 | def remove_from_clients(client): | |
| 601 | clients.remove(client) | |
| 602 | if not clients: | |
| 13
by Björn Påhlsson Added following support: | 603 | logger.debug(u"No clients left, exiting") | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 604 | main_loop.quit() | 
| 605 | ||
| 606 | clients.update(Set(Client(name=section, options=options, | |
| 607 | stop_hook = remove_from_clients, | |
| 608 | **(dict(client_config\ | |
| 609 | .items(section)))) | |
| 610 | for section in client_config.sections())) | |
| 611 | for client in clients: | |
| 612 | client.start() | |
| 613 | ||
| 614 | tcp_server = IPv6_TCPServer((None, options.port), | |
| 3
by Björn Påhlsson Python based server | 615 | tcp_handler, | 
| 616 | options=options, | |
| 12
by Teddy Hogeborn * mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn, | 617 | clients=clients) | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 618 |     # Find out what random port we got
 | 
| 619 | servicePort = tcp_server.socket.getsockname()[1] | |
| 13
by Björn Påhlsson Added following support: | 620 | logger.debug(u"Now listening on port %d", servicePort) | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 621 | |
| 622 | if options.interface is not None: | |
| 623 | serviceInterface = if_nametoindex(options.interface) | |
| 624 | ||
| 625 |     # From the Avahi server example code
 | |
| 626 | server.connect_to_signal("StateChanged", server_state_changed) | |
| 627 | server_state_changed(server.GetState()) | |
| 628 |     # End of Avahi example code
 | |
| 629 | ||
| 630 | gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN, | |
| 631 | lambda *args, **kwargs: | |
| 632 | tcp_server.handle_request(*args[2:], | |
| 633 | **kwargs) or True) | |
| 634 | try: | |
| 635 | main_loop.run() | |
| 636 | except KeyboardInterrupt: | |
| 637 |         print
 | |
| 9
by Teddy Hogeborn * client.cpp (main): Get t_old early since it is used on error exits. | 638 | |
| 639 |     # Cleanup here
 | |
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 640 | |
| 641 |     # From the Avahi server example code
 | |
| 642 | if not group is None: | |
| 643 | group.Free() | |
| 644 |     # End of Avahi example code
 | |
| 645 | ||
| 9
by Teddy Hogeborn * client.cpp (main): Get t_old early since it is used on error exits. | 646 | for client in clients: | 
| 11
by Teddy Hogeborn * server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the | 647 | client.stop_hook = None | 
| 648 | client.stop() |