31
28
from dbus.mainloop.glib import DBusGMainLoop
 
35
 
import logging.handlers
 
37
 
logger = logging.Logger('mandos')
 
38
 
syslogger = logging.handlers.SysLogHandler\
 
39
 
            (facility = logging.handlers.SysLogHandler.LOG_DAEMON)
 
40
 
syslogger.setFormatter(logging.Formatter\
 
41
 
                        ('%(levelname)s: %(message)s'))
 
42
 
logger.addHandler(syslogger)
 
45
31
# This variable is used to optionally bind to a specified interface.
 
46
32
# It is a global variable to fit in with the other variables from the
 
47
33
# Avahi server example code.
 
 
114
100
    del _set_interval
 
115
101
    def __init__(self, name=None, options=None, stop_hook=None,
 
116
 
                 fingerprint=None, secret=None, secfile=None,
 
117
 
                 fqdn=None, timeout=None, interval=-1, checker=None):
 
 
102
                 fingerprint=None, secret=None, secfile=None, fqdn=None,
 
 
103
                 timeout=None, interval=-1, checker=None):
 
119
105
        # Uppercase and remove spaces from fingerprint
 
120
106
        # for later comparison purposes with return value of
 
 
133
119
        self.created = datetime.datetime.now()
 
134
120
        self.last_seen = None
 
135
121
        if timeout is None:
 
136
 
            self.timeout = options.timeout
 
138
 
            self.timeout = string_to_delta(timeout)
 
 
122
            timeout = options.timeout
 
 
123
        self.timeout = timeout
 
139
124
        if interval == -1:
 
140
 
            self.interval = options.interval
 
 
125
            interval = options.interval
 
142
 
            self.interval = string_to_delta(interval)
 
 
127
            interval = string_to_delta(interval)
 
 
128
        self.interval = interval
 
143
129
        self.stop_hook = stop_hook
 
144
130
        self.checker = None
 
145
131
        self.checker_initiator_tag = None
 
 
163
149
        """Stop this client.
 
164
150
        The possibility that this client might be restarted is left
 
165
151
        open, but not currently used."""
 
166
 
        logger.debug(u"Stopping client %s", self.name)
 
 
153
            sys.stderr.write(u"Stopping client %s\n" % self.name)
 
167
154
        self.secret = None
 
168
155
        if self.stop_initiator_tag:
 
169
156
            gobject.source_remove(self.stop_initiator_tag)
 
 
192
179
        now = datetime.datetime.now()
 
193
180
        if os.WIFEXITED(condition) \
 
194
181
               and (os.WEXITSTATUS(condition) == 0):
 
195
 
            logger.debug(u"Checker for %(name)s succeeded",
 
 
183
                sys.stderr.write(u"Checker for %(name)s succeeded\n"
 
197
185
            self.last_seen = now
 
198
186
            gobject.source_remove(self.stop_initiator_tag)
 
199
187
            self.stop_initiator_tag = gobject.timeout_add\
 
200
188
                                      (self._timeout_milliseconds,
 
202
 
        elif not os.WIFEXITED(condition):
 
203
 
            logger.warning(u"Checker for %(name)s crashed?",
 
206
 
            logger.debug(u"Checker for %(name)s failed",
 
 
191
            if not os.WIFEXITED(condition):
 
 
192
                sys.stderr.write(u"Checker for %(name)s crashed?\n"
 
 
195
                sys.stderr.write(u"Checker for %(name)s failed\n"
 
209
198
        self.checker_callback_tag = None
 
210
199
    def start_checker(self):
 
211
200
        """Start a new checker subprocess if one is not running.
 
212
201
        If a checker already exists, leave it running and do
 
214
203
        if self.checker is None:
 
 
205
                sys.stderr.write(u"Starting checker for %s\n"
 
216
208
                command = self.check_command % self.fqdn
 
217
209
            except TypeError:
 
218
210
                escaped_attrs = dict((key, re.escape(str(val)))
 
220
212
                                     vars(self).iteritems())
 
222
 
                    command = self.check_command % escaped_attrs
 
223
 
                except TypeError, error:
 
224
 
                    logger.critical(u'Could not format string "%s":'
 
225
 
                                    u' %s', self.check_command, error)
 
226
 
                    return True # Try again later
 
 
213
                command = self.check_command % escaped_attrs
 
228
 
                logger.debug(u"Starting checker %r for %s",
 
230
215
                self.checker = subprocess.\
 
 
217
                                     stdout=subprocess.PIPE,
 
232
218
                                     close_fds=True, shell=True,
 
234
 
                self.checker_callback_tag = gobject.child_watch_add\
 
236
 
                                             self.checker_callback)
 
 
220
                self.checker_callback_tag = gobject.\
 
 
221
                                            child_watch_add(self.checker.pid,
 
237
224
            except subprocess.OSError, error:
 
238
 
                logger.error(u"Failed to start subprocess: %s",
 
 
225
                sys.stderr.write(u"Failed to start subprocess: %s\n"
 
240
227
        # Re-run this periodically if run by gobject.timeout_add
 
242
229
    def stop_checker(self):
 
 
279
265
def fingerprint(openpgp):
 
280
 
    "Convert an OpenPGP data string to a hexdigit fingerprint string"
 
281
266
    # New empty GnuTLS certificate
 
282
267
    crt = gnutls.library.types.gnutls_openpgp_crt_t()
 
283
268
    gnutls.library.functions.gnutls_openpgp_crt_init\
 
 
313
298
    Note: This will run in its own forked process."""
 
315
300
    def handle(self):
 
316
 
        logger.debug(u"TCP connection from: %s",
 
317
 
                     unicode(self.client_address))
 
 
302
            sys.stderr.write(u"TCP request came\n")
 
 
303
            sys.stderr.write(u"Request: %s\n" % self.request)
 
 
304
            sys.stderr.write(u"Client Address: %s\n"
 
 
305
                             % unicode(self.client_address))
 
 
306
            sys.stderr.write(u"Server: %s\n" % self.server)
 
318
307
        session = gnutls.connection.ClientSession(self.request,
 
319
308
                                                  gnutls.connection.\
 
320
309
                                                  X509Credentials())
 
 
331
320
            session.handshake()
 
332
321
        except gnutls.errors.GNUTLSError, error:
 
333
 
            logger.debug(u"Handshake failed: %s", error)
 
 
323
                sys.stderr.write(u"Handshake failed: %s\n" % error)
 
334
324
            # Do not run session.bye() here: the session is not
 
335
325
            # established.  Just abandon the request.
 
338
328
            fpr = fingerprint(peer_certificate(session))
 
339
329
        except (TypeError, gnutls.errors.GNUTLSError), error:
 
340
 
            logger.debug(u"Bad certificate: %s", error)
 
 
331
                sys.stderr.write(u"Bad certificate: %s\n" % error)
 
343
 
        logger.debug(u"Fingerprint: %s", fpr)
 
 
335
            sys.stderr.write(u"Fingerprint: %s\n" % fpr)
 
345
337
        for c in clients:
 
346
338
            if c.fingerprint == fpr:
 
 
350
342
        # that the client timed out while establishing the GnuTLS
 
352
344
        if (not client) or (not client.still_valid()):
 
354
 
                logger.debug(u"Client %(name)s is invalid",
 
357
 
                logger.debug(u"Client not found for fingerprint: %s",
 
 
347
                    sys.stderr.write(u"Client %(name)s is invalid\n"
 
 
350
                    sys.stderr.write(u"Client not found for "
 
 
351
                                     u"fingerprint: %s\n" % fpr)
 
362
355
        while sent_size < len(client.secret):
 
363
356
            sent = session.send(client.secret[sent_size:])
 
364
 
            logger.debug(u"Sent: %d, remaining: %d",
 
365
 
                         sent, len(client.secret)
 
366
 
                         - (sent_size + sent))
 
 
358
                sys.stderr.write(u"Sent: %d, remaining: %d\n"
 
 
359
                                 % (sent, len(client.secret)
 
 
360
                                    - (sent_size + sent)))
 
367
361
            sent_size += sent
 
 
397
391
                                       self.options.interface)
 
398
392
            except socket.error, error:
 
399
393
                if error[0] == errno.EPERM:
 
400
 
                    logger.warning(u"No permission to"
 
401
 
                                   u" bind to interface %s",
 
402
 
                                   self.options.interface)
 
 
394
                    sys.stderr.write(u"Warning: No permission to" \
 
 
395
                                     u" bind to interface %s\n"
 
 
396
                                     % self.options.interface)
 
405
399
        # Only bind(2) the socket if we really need to.
 
 
459
453
                avahi.DBUS_INTERFACE_ENTRY_GROUP)
 
460
454
        group.connect_to_signal('StateChanged',
 
461
455
                                entry_group_state_changed)
 
462
 
    logger.debug(u"Adding service '%s' of type '%s' ...",
 
463
 
                 serviceName, serviceType)
 
 
457
        sys.stderr.write(u"Adding service '%s' of type '%s' ...\n"
 
 
458
                         % (serviceName, serviceType))
 
465
460
    group.AddService(
 
466
461
            serviceInterface,           # interface
 
 
484
479
def server_state_changed(state):
 
485
480
    """From the Avahi server example code"""
 
486
481
    if state == avahi.SERVER_COLLISION:
 
487
 
        logger.warning(u"Server name collision")
 
 
482
        sys.stderr.write(u"WARNING: Server name collision\n")
 
489
484
    elif state == avahi.SERVER_RUNNING:
 
 
494
489
    """From the Avahi server example code"""
 
495
490
    global serviceName, server, rename_count
 
497
 
    logger.debug(u"state change: %i", state)
 
 
493
        sys.stderr.write(u"state change: %i\n" % state)
 
499
495
    if state == avahi.ENTRY_GROUP_ESTABLISHED:
 
500
 
        logger.debug(u"Service established.")
 
 
497
            sys.stderr.write(u"Service established.\n")
 
501
498
    elif state == avahi.ENTRY_GROUP_COLLISION:
 
503
500
        rename_count = rename_count - 1
 
504
501
        if rename_count > 0:
 
505
502
            name = server.GetAlternativeServiceName(name)
 
506
 
            logger.warning(u"Service name collision, "
 
507
 
                           u"changing name to '%s' ...", name)
 
 
503
            sys.stderr.write(u"WARNING: Service name collision, "
 
 
504
                             u"changing name to '%s' ...\n" % name)
 
512
 
            logger.error(u"No suitable service name found after %i"
 
513
 
                         u" retries, exiting.", n_rename)
 
 
509
            sys.stderr.write(u"ERROR: No suitable service name found "
 
 
510
                             u"after %i retries, exiting.\n"
 
515
513
    elif state == avahi.ENTRY_GROUP_FAILURE:
 
516
 
        logger.error(u"Error in group state changed %s",
 
 
514
        sys.stderr.write(u"Error in group state changed %s\n"
 
521
520
def if_nametoindex(interface):
 
 
537
536
        return interface_index
 
540
 
def daemon(nochdir, noclose):
 
541
 
    """See daemon(3).  Standard BSD Unix function.
 
542
 
    This should really exist as os.daemon, but it doesn't (yet)."""
 
549
 
        # Close all standard open file descriptors
 
550
 
        null = os.open("/dev/null", os.O_NOCTTY | os.O_RDWR)
 
551
 
        if not stat.S_ISCHR(os.fstat(null).st_mode):
 
552
 
            raise OSError(errno.ENODEV,
 
553
 
                          "/dev/null not a character device")
 
554
 
        os.dup2(null, sys.stdin.fileno())
 
555
 
        os.dup2(null, sys.stdout.fileno())
 
556
 
        os.dup2(null, sys.stderr.fileno())
 
561
 
def killme(status = 0):
 
562
 
    logger.debug("Stopping server with exit status %d", status)
 
564
 
    if main_loop_started:
 
570
539
if __name__ == '__main__':
 
572
 
    main_loop_started = False
 
573
540
    parser = OptionParser()
 
574
541
    parser.add_option("-i", "--interface", type="string",
 
575
542
                      default=None, metavar="IF",
 
576
543
                      help="Bind to interface IF")
 
 
544
    parser.add_option("--cert", type="string", default="cert.pem",
 
 
546
                      help="Public key certificate PEM file to use")
 
 
547
    parser.add_option("--key", type="string", default="key.pem",
 
 
549
                      help="Private key PEM file to use")
 
 
550
    parser.add_option("--ca", type="string", default="ca.pem",
 
 
552
                      help="Certificate Authority certificate PEM file to use")
 
 
553
    parser.add_option("--crl", type="string", default="crl.pem",
 
 
555
                      help="Certificate Revokation List PEM file to use")
 
577
556
    parser.add_option("-p", "--port", type="int", default=None,
 
578
557
                      help="Port number to receive requests on")
 
579
558
    parser.add_option("--timeout", type="string", # Parsed later
 
 
604
583
        parser.error("option --interval: Unparseable time")
 
606
585
    # Parse config file
 
607
 
    defaults = { "checker": "fping -q -- %%(fqdn)s" }
 
 
586
    defaults = { "checker": "sleep 1; fping -q -- %%(fqdn)s" }
 
608
587
    client_config = ConfigParser.SafeConfigParser(defaults)
 
609
588
    #client_config.readfp(open("secrets.conf"), "secrets.conf")
 
610
589
    client_config.read("mandos-clients.conf")
 
 
621
600
    debug = options.debug
 
624
 
        console = logging.StreamHandler()
 
625
 
        # console.setLevel(logging.DEBUG)
 
626
 
        console.setFormatter(logging.Formatter\
 
627
 
                             ('%(levelname)s: %(message)s'))
 
628
 
        logger.addHandler(console)
 
632
603
    def remove_from_clients(client):
 
633
604
        clients.remove(client)
 
635
 
            logger.debug(u"No clients left, exiting")
 
 
607
                sys.stderr.write(u"No clients left, exiting\n")
 
638
610
    clients.update(Set(Client(name=section, options=options,
 
639
611
                              stop_hook = remove_from_clients,
 
640
612
                              **(dict(client_config\
 
641
613
                                      .items(section))))
 
642
614
                       for section in client_config.sections()))
 
648
 
        "Cleanup function; run on exit"
 
650
 
        # From the Avahi server example code
 
651
 
        if not group is None:
 
654
 
        # End of Avahi example code
 
656
 
        for client in clients:
 
657
 
            client.stop_hook = None
 
660
 
    atexit.register(cleanup)
 
663
 
        signal.signal(signal.SIGINT, signal.SIG_IGN)
 
664
 
    signal.signal(signal.SIGHUP, lambda signum, frame: killme())
 
665
 
    signal.signal(signal.SIGTERM, lambda signum, frame: killme())
 
667
615
    for client in clients:
 
 
674
622
    # Find out what random port we got
 
675
623
    servicePort = tcp_server.socket.getsockname()[1]
 
676
 
    logger.debug(u"Now listening on port %d", servicePort)
 
 
625
        sys.stderr.write(u"Now listening on port %d\n" % servicePort)
 
678
627
    if options.interface is not None:
 
679
628
        serviceInterface = if_nametoindex(options.interface)
 
681
630
    # From the Avahi server example code
 
682
631
    server.connect_to_signal("StateChanged", server_state_changed)
 
684
 
        server_state_changed(server.GetState())
 
685
 
    except dbus.exceptions.DBusException, error:
 
686
 
        logger.critical(u"DBusException: %s", error)
 
 
632
    server_state_changed(server.GetState())
 
688
633
    # End of Avahi example code
 
690
635
    gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,