/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2009-04-16 06:47:28 UTC
  • Revision ID: teddy@fukt.bsnet.se-20090416064728-c3d36mvgxo5q9aoh
* mandos: Import "SocketServer" as "socketserver" and "ConfigParser"
          as "configparser".  All users changed.

Show diffs side-by-side

added added

removed removed

Lines of Context:
33
33
 
34
34
from __future__ import division, with_statement, absolute_import
35
35
 
36
 
import SocketServer
 
36
import SocketServer as socketserver
37
37
import socket
38
38
import optparse
39
39
import datetime
44
44
import gnutls.library.functions
45
45
import gnutls.library.constants
46
46
import gnutls.library.types
47
 
import ConfigParser
 
47
import ConfigParser as configparser
48
48
import sys
49
49
import re
50
50
import os
51
51
import signal
52
 
from sets import Set
53
52
import subprocess
54
53
import atexit
55
54
import stat
57
56
import logging.handlers
58
57
import pwd
59
58
from contextlib import closing
 
59
import struct
 
60
import fcntl
60
61
 
61
62
import dbus
62
63
import dbus.service
78
79
 
79
80
version = "1.0.8"
80
81
 
81
 
logger = logging.Logger('mandos')
 
82
logger = logging.Logger(u'mandos')
82
83
syslogger = (logging.handlers.SysLogHandler
83
84
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
84
85
              address = "/dev/log"))
85
86
syslogger.setFormatter(logging.Formatter
86
 
                       ('Mandos [%(process)d]: %(levelname)s:'
87
 
                        ' %(message)s'))
 
87
                       (u'Mandos [%(process)d]: %(levelname)s:'
 
88
                        u' %(message)s'))
88
89
logger.addHandler(syslogger)
89
90
 
90
91
console = logging.StreamHandler()
91
 
console.setFormatter(logging.Formatter('%(name)s [%(process)d]:'
92
 
                                       ' %(levelname)s: %(message)s'))
 
92
console.setFormatter(logging.Formatter(u'%(name)s [%(process)d]:'
 
93
                                       u' %(levelname)s:'
 
94
                                       u' %(message)s'))
93
95
logger.addHandler(console)
94
96
 
95
97
class AvahiError(Exception):
112
114
    Attributes:
113
115
    interface: integer; avahi.IF_UNSPEC or an interface index.
114
116
               Used to optionally bind to the specified interface.
115
 
    name: string; Example: 'Mandos'
116
 
    type: string; Example: '_mandos._tcp'.
 
117
    name: string; Example: u'Mandos'
 
118
    type: string; Example: u'_mandos._tcp'.
117
119
                  See <http://www.dns-sd.org/ServiceTypes.html>
118
120
    port: integer; what port to announce
119
121
    TXT: list of strings; TXT record for the service
125
127
    """
126
128
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
127
129
                 servicetype = None, port = None, TXT = None,
128
 
                 domain = "", host = "", max_renames = 32768,
 
130
                 domain = u"", host = u"", max_renames = 32768,
129
131
                 protocol = avahi.PROTO_UNSPEC):
130
132
        self.interface = interface
131
133
        self.name = name
146
148
            raise AvahiServiceError(u"Too many renames")
147
149
        self.name = server.GetAlternativeServiceName(self.name)
148
150
        logger.info(u"Changing Zeroconf service name to %r ...",
149
 
                    str(self.name))
 
151
                    self.name)
150
152
        syslogger.setFormatter(logging.Formatter
151
 
                               ('Mandos (%s) [%%(process)d]:'
152
 
                                ' %%(levelname)s: %%(message)s'
 
153
                               (u'Mandos (%s) [%%(process)d]:'
 
154
                                u' %%(levelname)s: %%(message)s'
153
155
                                % self.name))
154
156
        self.remove()
155
157
        self.add()
220
222
                     instance %(name)s can be used in the command.
221
223
    current_checker_command: string; current running checker_command
222
224
    """
 
225
    
 
226
    @staticmethod
 
227
    def _datetime_to_milliseconds(dt):
 
228
        "Convert a datetime.datetime() to milliseconds"
 
229
        return ((dt.days * 24 * 60 * 60 * 1000)
 
230
                + (dt.seconds * 1000)
 
231
                + (dt.microseconds // 1000))
 
232
    
223
233
    def timeout_milliseconds(self):
224
234
        "Return the 'timeout' attribute in milliseconds"
225
 
        return ((self.timeout.days * 24 * 60 * 60 * 1000)
226
 
                + (self.timeout.seconds * 1000)
227
 
                + (self.timeout.microseconds // 1000))
 
235
        return self._datetime_to_milliseconds(self.timeout)
228
236
    
229
237
    def interval_milliseconds(self):
230
238
        "Return the 'interval' attribute in milliseconds"
231
 
        return ((self.interval.days * 24 * 60 * 60 * 1000)
232
 
                + (self.interval.seconds * 1000)
233
 
                + (self.interval.microseconds // 1000))
 
239
        return self._datetime_to_milliseconds(self.interval)
234
240
    
235
241
    def __init__(self, name = None, disable_hook=None, config=None):
236
242
        """Note: the 'checker' key in 'config' sets the
243
249
        # Uppercase and remove spaces from fingerprint for later
244
250
        # comparison purposes with return value from the fingerprint()
245
251
        # function
246
 
        self.fingerprint = (config["fingerprint"].upper()
 
252
        self.fingerprint = (config[u"fingerprint"].upper()
247
253
                            .replace(u" ", u""))
248
254
        logger.debug(u"  Fingerprint: %s", self.fingerprint)
249
 
        if "secret" in config:
250
 
            self.secret = config["secret"].decode(u"base64")
251
 
        elif "secfile" in config:
 
255
        if u"secret" in config:
 
256
            self.secret = config[u"secret"].decode(u"base64")
 
257
        elif u"secfile" in config:
252
258
            with closing(open(os.path.expanduser
253
259
                              (os.path.expandvars
254
 
                               (config["secfile"])))) as secfile:
 
260
                               (config[u"secfile"])))) as secfile:
255
261
                self.secret = secfile.read()
256
262
        else:
257
263
            raise TypeError(u"No secret or secfile for client %s"
258
264
                            % self.name)
259
 
        self.host = config.get("host", "")
 
265
        self.host = config.get(u"host", u"")
260
266
        self.created = datetime.datetime.utcnow()
261
267
        self.enabled = False
262
268
        self.last_enabled = None
263
269
        self.last_checked_ok = None
264
 
        self.timeout = string_to_delta(config["timeout"])
265
 
        self.interval = string_to_delta(config["interval"])
 
270
        self.timeout = string_to_delta(config[u"timeout"])
 
271
        self.interval = string_to_delta(config[u"interval"])
266
272
        self.disable_hook = disable_hook
267
273
        self.checker = None
268
274
        self.checker_initiator_tag = None
269
275
        self.disable_initiator_tag = None
270
276
        self.checker_callback_tag = None
271
 
        self.checker_command = config["checker"]
 
277
        self.checker_command = config[u"checker"]
272
278
        self.current_checker_command = None
273
279
        self.last_connect = None
274
280
    
293
299
        if not getattr(self, "enabled", False):
294
300
            return False
295
301
        logger.info(u"Disabling client %s", self.name)
296
 
        if getattr(self, "disable_initiator_tag", False):
 
302
        if getattr(self, u"disable_initiator_tag", False):
297
303
            gobject.source_remove(self.disable_initiator_tag)
298
304
            self.disable_initiator_tag = None
299
 
        if getattr(self, "checker_initiator_tag", False):
 
305
        if getattr(self, u"checker_initiator_tag", False):
300
306
            gobject.source_remove(self.checker_initiator_tag)
301
307
            self.checker_initiator_tag = None
302
308
        self.stop_checker()
357
363
        if self.checker is not None:
358
364
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
359
365
            if pid:
360
 
                logger.warning("Checker was a zombie")
 
366
                logger.warning(u"Checker was a zombie")
361
367
                gobject.source_remove(self.checker_callback_tag)
362
368
                self.checker_callback(pid, status,
363
369
                                      self.current_checker_command)
368
374
                command = self.checker_command % self.host
369
375
            except TypeError:
370
376
                # Escape attributes for the shell
371
 
                escaped_attrs = dict((key, re.escape(str(val)))
 
377
                escaped_attrs = dict((key,
 
378
                                      re.escape(unicode(str(val),
 
379
                                                        errors=
 
380
                                                        u'replace')))
372
381
                                     for key, val in
373
382
                                     vars(self).iteritems())
374
383
                try:
387
396
                # always replaced by /dev/null.)
388
397
                self.checker = subprocess.Popen(command,
389
398
                                                close_fds=True,
390
 
                                                shell=True, cwd="/")
 
399
                                                shell=True, cwd=u"/")
391
400
                self.checker_callback_tag = (gobject.child_watch_add
392
401
                                             (self.checker.pid,
393
402
                                              self.checker_callback,
409
418
        if self.checker_callback_tag:
410
419
            gobject.source_remove(self.checker_callback_tag)
411
420
            self.checker_callback_tag = None
412
 
        if getattr(self, "checker", None) is None:
 
421
        if getattr(self, u"checker", None) is None:
413
422
            return
414
423
        logger.debug(u"Stopping checker for %(name)s", vars(self))
415
424
        try:
424
433
    
425
434
    def still_valid(self):
426
435
        """Has the timeout not yet passed for this client?"""
427
 
        if not getattr(self, "enabled", False):
 
436
        if not getattr(self, u"enabled", False):
428
437
            return False
429
438
        now = datetime.datetime.utcnow()
430
439
        if self.last_checked_ok is None:
446
455
        # Only now, when this client is initialized, can it show up on
447
456
        # the D-Bus
448
457
        self.dbus_object_path = (dbus.ObjectPath
449
 
                                 ("/clients/"
450
 
                                  + self.name.replace(".", "_")))
 
458
                                 (u"/clients/"
 
459
                                  + self.name.replace(u".", u"_")))
451
460
        dbus.service.Object.__init__(self, bus,
452
461
                                     self.dbus_object_path)
453
462
    def enable(self):
454
 
        oldstate = getattr(self, "enabled", False)
 
463
        oldstate = getattr(self, u"enabled", False)
455
464
        r = Client.enable(self)
456
465
        if oldstate != self.enabled:
457
466
            # Emit D-Bus signals
463
472
        return r
464
473
    
465
474
    def disable(self, signal = True):
466
 
        oldstate = getattr(self, "enabled", False)
 
475
        oldstate = getattr(self, u"enabled", False)
467
476
        r = Client.disable(self)
468
477
        if signal and oldstate != self.enabled:
469
478
            # Emit D-Bus signal
476
485
            self.remove_from_connection()
477
486
        except LookupError:
478
487
            pass
479
 
        if hasattr(dbus.service.Object, "__del__"):
 
488
        if hasattr(dbus.service.Object, u"__del__"):
480
489
            dbus.service.Object.__del__(self, *args, **kwargs)
481
490
        Client.__del__(self, *args, **kwargs)
482
491
    
524
533
            # Emit D-Bus signal
525
534
            self.CheckerStarted(self.current_checker_command)
526
535
            self.PropertyChanged(
527
 
                dbus.String("checker_running"),
 
536
                dbus.String(u"checker_running"),
528
537
                dbus.Boolean(True, variant_level=1))
529
538
        return r
530
539
    
531
540
    def stop_checker(self, *args, **kwargs):
532
 
        old_checker = getattr(self, "checker", None)
 
541
        old_checker = getattr(self, u"checker", None)
533
542
        r = Client.stop_checker(self, *args, **kwargs)
534
543
        if (old_checker is not None
535
 
            and getattr(self, "checker", None) is None):
 
544
            and getattr(self, u"checker", None) is None):
536
545
            self.PropertyChanged(dbus.String(u"checker_running"),
537
546
                                 dbus.Boolean(False, variant_level=1))
538
547
        return r
541
550
    _interface = u"se.bsnet.fukt.Mandos.Client"
542
551
    
543
552
    # CheckedOK - method
544
 
    CheckedOK = dbus.service.method(_interface)(checked_ok)
545
 
    CheckedOK.__name__ = "CheckedOK"
 
553
    @dbus.service.method(_interface)
 
554
    def CheckedOK(self):
 
555
        return self.checked_ok()
546
556
    
547
557
    # CheckerCompleted - signal
548
 
    @dbus.service.signal(_interface, signature="nxs")
 
558
    @dbus.service.signal(_interface, signature=u"nxs")
549
559
    def CheckerCompleted(self, exitcode, waitstatus, command):
550
560
        "D-Bus signal"
551
561
        pass
552
562
    
553
563
    # CheckerStarted - signal
554
 
    @dbus.service.signal(_interface, signature="s")
 
564
    @dbus.service.signal(_interface, signature=u"s")
555
565
    def CheckerStarted(self, command):
556
566
        "D-Bus signal"
557
567
        pass
558
568
    
559
569
    # GetAllProperties - method
560
 
    @dbus.service.method(_interface, out_signature="a{sv}")
 
570
    @dbus.service.method(_interface, out_signature=u"a{sv}")
561
571
    def GetAllProperties(self):
562
572
        "D-Bus method"
563
573
        return dbus.Dictionary({
564
 
                dbus.String("name"):
 
574
                dbus.String(u"name"):
565
575
                    dbus.String(self.name, variant_level=1),
566
 
                dbus.String("fingerprint"):
 
576
                dbus.String(u"fingerprint"):
567
577
                    dbus.String(self.fingerprint, variant_level=1),
568
 
                dbus.String("host"):
 
578
                dbus.String(u"host"):
569
579
                    dbus.String(self.host, variant_level=1),
570
 
                dbus.String("created"):
 
580
                dbus.String(u"created"):
571
581
                    _datetime_to_dbus(self.created, variant_level=1),
572
 
                dbus.String("last_enabled"):
 
582
                dbus.String(u"last_enabled"):
573
583
                    (_datetime_to_dbus(self.last_enabled,
574
584
                                       variant_level=1)
575
585
                     if self.last_enabled is not None
576
586
                     else dbus.Boolean(False, variant_level=1)),
577
 
                dbus.String("enabled"):
 
587
                dbus.String(u"enabled"):
578
588
                    dbus.Boolean(self.enabled, variant_level=1),
579
 
                dbus.String("last_checked_ok"):
 
589
                dbus.String(u"last_checked_ok"):
580
590
                    (_datetime_to_dbus(self.last_checked_ok,
581
591
                                       variant_level=1)
582
592
                     if self.last_checked_ok is not None
583
593
                     else dbus.Boolean (False, variant_level=1)),
584
 
                dbus.String("timeout"):
 
594
                dbus.String(u"timeout"):
585
595
                    dbus.UInt64(self.timeout_milliseconds(),
586
596
                                variant_level=1),
587
 
                dbus.String("interval"):
 
597
                dbus.String(u"interval"):
588
598
                    dbus.UInt64(self.interval_milliseconds(),
589
599
                                variant_level=1),
590
 
                dbus.String("checker"):
 
600
                dbus.String(u"checker"):
591
601
                    dbus.String(self.checker_command,
592
602
                                variant_level=1),
593
 
                dbus.String("checker_running"):
 
603
                dbus.String(u"checker_running"):
594
604
                    dbus.Boolean(self.checker is not None,
595
605
                                 variant_level=1),
596
 
                dbus.String("object_path"):
 
606
                dbus.String(u"object_path"):
597
607
                    dbus.ObjectPath(self.dbus_object_path,
598
608
                                    variant_level=1)
599
 
                }, signature="sv")
 
609
                }, signature=u"sv")
600
610
    
601
611
    # IsStillValid - method
602
 
    @dbus.service.method(_interface, out_signature="b")
 
612
    @dbus.service.method(_interface, out_signature=u"b")
603
613
    def IsStillValid(self):
604
614
        return self.still_valid()
605
615
    
606
616
    # PropertyChanged - signal
607
 
    @dbus.service.signal(_interface, signature="sv")
 
617
    @dbus.service.signal(_interface, signature=u"sv")
608
618
    def PropertyChanged(self, property, value):
609
619
        "D-Bus signal"
610
620
        pass
622
632
        pass
623
633
    
624
634
    # SetChecker - method
625
 
    @dbus.service.method(_interface, in_signature="s")
 
635
    @dbus.service.method(_interface, in_signature=u"s")
626
636
    def SetChecker(self, checker):
627
637
        "D-Bus setter method"
628
638
        self.checker_command = checker
632
642
                                         variant_level=1))
633
643
    
634
644
    # SetHost - method
635
 
    @dbus.service.method(_interface, in_signature="s")
 
645
    @dbus.service.method(_interface, in_signature=u"s")
636
646
    def SetHost(self, host):
637
647
        "D-Bus setter method"
638
648
        self.host = host
641
651
                             dbus.String(self.host, variant_level=1))
642
652
    
643
653
    # SetInterval - method
644
 
    @dbus.service.method(_interface, in_signature="t")
 
654
    @dbus.service.method(_interface, in_signature=u"t")
645
655
    def SetInterval(self, milliseconds):
646
656
        self.interval = datetime.timedelta(0, 0, 0, milliseconds)
647
657
        # Emit D-Bus signal
650
660
                                          variant_level=1)))
651
661
    
652
662
    # SetSecret - method
653
 
    @dbus.service.method(_interface, in_signature="ay",
 
663
    @dbus.service.method(_interface, in_signature=u"ay",
654
664
                         byte_arrays=True)
655
665
    def SetSecret(self, secret):
656
666
        "D-Bus setter method"
657
667
        self.secret = str(secret)
658
668
    
659
669
    # SetTimeout - method
660
 
    @dbus.service.method(_interface, in_signature="t")
 
670
    @dbus.service.method(_interface, in_signature=u"t")
661
671
    def SetTimeout(self, milliseconds):
662
672
        self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
663
673
        # Emit D-Bus signal
666
676
                                          variant_level=1)))
667
677
    
668
678
    # Enable - method
669
 
    Enable = dbus.service.method(_interface)(enable)
670
 
    Enable.__name__ = "Enable"
 
679
    @dbus.service.method(_interface)
 
680
    def Enable(self):
 
681
        "D-Bus method"
 
682
        self.enable()
671
683
    
672
684
    # StartChecker - method
673
685
    @dbus.service.method(_interface)
682
694
        self.disable()
683
695
    
684
696
    # StopChecker - method
685
 
    StopChecker = dbus.service.method(_interface)(stop_checker)
686
 
    StopChecker.__name__ = "StopChecker"
 
697
    @dbus.service.method(_interface)
 
698
    def StopChecker(self):
 
699
        self.stop_checker()
687
700
    
688
701
    del _interface
689
702
 
690
703
 
691
 
class ClientHandler(SocketServer.BaseRequestHandler, object):
 
704
class ClientHandler(socketserver.BaseRequestHandler, object):
692
705
    """A class to handle client connections.
693
706
    
694
707
    Instantiated once for each connection to handle it.
699
712
                    unicode(self.client_address))
700
713
        logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
701
714
        # Open IPC pipe to parent process
702
 
        with closing(os.fdopen(self.server.pipe[1], "w", 1)) as ipc:
 
715
        with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
703
716
            session = (gnutls.connection
704
717
                       .ClientSession(self.request,
705
718
                                      gnutls.connection
719
732
            # no X.509 keys are added to it.  Therefore, we can use it
720
733
            # here despite using OpenPGP certificates.
721
734
            
722
 
            #priority = ':'.join(("NONE", "+VERS-TLS1.1",
723
 
            #                     "+AES-256-CBC", "+SHA1",
724
 
            #                     "+COMP-NULL", "+CTYPE-OPENPGP",
725
 
            #                     "+DHE-DSS"))
 
735
            #priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
 
736
            #                      u"+AES-256-CBC", u"+SHA1",
 
737
            #                      u"+COMP-NULL", u"+CTYPE-OPENPGP",
 
738
            #                      u"+DHE-DSS"))
726
739
            # Use a fallback default, since this MUST be set.
727
740
            priority = self.server.gnutls_priority
728
741
            if priority is None:
729
 
                priority = "NORMAL"
 
742
                priority = u"NORMAL"
730
743
            (gnutls.library.functions
731
744
             .gnutls_priority_set_direct(session._c_object,
732
745
                                         priority, None))
752
765
                    client = c
753
766
                    break
754
767
            else:
755
 
                ipc.write("NOTFOUND %s\n" % fpr)
 
768
                ipc.write(u"NOTFOUND %s\n" % fpr)
756
769
                session.bye()
757
770
                return
758
771
            # Have to check if client.still_valid(), since it is
759
772
            # possible that the client timed out while establishing
760
773
            # the GnuTLS session.
761
774
            if not client.still_valid():
762
 
                ipc.write("INVALID %s\n" % client.name)
 
775
                ipc.write(u"INVALID %s\n" % client.name)
763
776
                session.bye()
764
777
                return
765
 
            ipc.write("SENDING %s\n" % client.name)
 
778
            ipc.write(u"SENDING %s\n" % client.name)
766
779
            sent_size = 0
767
780
            while sent_size < len(client.secret):
768
781
                sent = session.send(client.secret[sent_size:])
786
799
                     .gnutls_certificate_get_peers
787
800
                     (session._c_object, ctypes.byref(list_size)))
788
801
        if not bool(cert_list) and list_size.value != 0:
789
 
            raise gnutls.errors.GNUTLSError("error getting peer"
790
 
                                            " certificate")
 
802
            raise gnutls.errors.GNUTLSError(u"error getting peer"
 
803
                                            u" certificate")
791
804
        if list_size.value == 0:
792
805
            return None
793
806
        cert = cert_list[0]
819
832
        if crtverify.value != 0:
820
833
            gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
821
834
            raise (gnutls.errors.CertificateSecurityError
822
 
                   ("Verify failed"))
 
835
                   (u"Verify failed"))
823
836
        # New buffer for the fingerprint
824
837
        buf = ctypes.create_string_buffer(20)
825
838
        buf_len = ctypes.c_size_t()
836
849
        return hex_fpr
837
850
 
838
851
 
839
 
class ForkingMixInWithPipe(SocketServer.ForkingMixIn, object):
840
 
    """Like SocketServer.ForkingMixIn, but also pass a pipe.
 
852
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
 
853
    """Like socketserver.ForkingMixIn, but also pass a pipe.
841
854
    
842
855
    Assumes a gobject.MainLoop event loop.
843
856
    """
861
874
 
862
875
 
863
876
class IPv6_TCPServer(ForkingMixInWithPipe,
864
 
                     SocketServer.TCPServer, object):
 
877
                     socketserver.TCPServer, object):
865
878
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
866
879
    
867
880
    Attributes:
869
882
        interface:      None or a network interface name (string)
870
883
        use_ipv6:       Boolean; to use IPv6 or not
871
884
        ----
872
 
        clients:        Set() of Client objects
 
885
        clients:        set of Client objects
873
886
        gnutls_priority GnuTLS priority string
874
887
        use_dbus:       Boolean; to emit D-Bus signals or not
875
888
    """
883
896
        self.clients = clients
884
897
        self.use_dbus = use_dbus
885
898
        self.gnutls_priority = gnutls_priority
886
 
        SocketServer.TCPServer.__init__(self, server_address,
 
899
        socketserver.TCPServer.__init__(self, server_address,
887
900
                                        RequestHandlerClass)
888
901
    def server_bind(self):
889
902
        """This overrides the normal server_bind() function
893
906
            try:
894
907
                self.socket.setsockopt(socket.SOL_SOCKET,
895
908
                                       SO_BINDTODEVICE,
896
 
                                       self.interface + '\0')
 
909
                                       str(self.interface + u'\0'))
897
910
            except socket.error, error:
898
911
                if error[0] == errno.EPERM:
899
912
                    logger.error(u"No permission to"
905
918
        if self.server_address[0] or self.server_address[1]:
906
919
            if not self.server_address[0]:
907
920
                if self.address_family == socket.AF_INET6:
908
 
                    any_address = "::" # in6addr_any
 
921
                    any_address = u"::" # in6addr_any
909
922
                else:
910
923
                    any_address = socket.INADDR_ANY
911
924
                self.server_address = (any_address,
919
932
#                                            0, # flowinfo
920
933
#                                            if_nametoindex
921
934
#                                            (self.interface))
922
 
            return SocketServer.TCPServer.server_bind(self)
 
935
            return socketserver.TCPServer.server_bind(self)
923
936
    def server_activate(self):
924
937
        if self.enabled:
925
 
            return SocketServer.TCPServer.server_activate(self)
 
938
            return socketserver.TCPServer.server_activate(self)
926
939
    def enable(self):
927
940
        self.enabled = True
928
941
    def handle_ipc(self, source, condition, file_objects={}):
929
942
        condition_names = {
930
 
            gobject.IO_IN: "IN", # There is data to read.
931
 
            gobject.IO_OUT: "OUT", # Data can be written (without
932
 
                                   # blocking).
933
 
            gobject.IO_PRI: "PRI", # There is urgent data to read.
934
 
            gobject.IO_ERR: "ERR", # Error condition.
935
 
            gobject.IO_HUP: "HUP"  # Hung up (the connection has been
936
 
                                   # broken, usually for pipes and
937
 
                                   # sockets).
 
943
            gobject.IO_IN: u"IN",   # There is data to read.
 
944
            gobject.IO_OUT: u"OUT", # Data can be written (without
 
945
                                    # blocking).
 
946
            gobject.IO_PRI: u"PRI", # There is urgent data to read.
 
947
            gobject.IO_ERR: u"ERR", # Error condition.
 
948
            gobject.IO_HUP: u"HUP"  # Hung up (the connection has been
 
949
                                    # broken, usually for pipes and
 
950
                                    # sockets).
938
951
            }
939
952
        conditions_string = ' | '.join(name
940
953
                                       for cond, name in
941
954
                                       condition_names.iteritems()
942
955
                                       if cond & condition)
943
 
        logger.debug("Handling IPC: FD = %d, condition = %s", source,
 
956
        logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
944
957
                     conditions_string)
945
958
        
946
959
        # Turn the pipe file descriptor into a Python file object
947
960
        if source not in file_objects:
948
 
            file_objects[source] = os.fdopen(source, "r", 1)
 
961
            file_objects[source] = os.fdopen(source, u"r", 1)
949
962
        
950
963
        # Read a line from the file object
951
964
        cmdline = file_objects[source].readline()
957
970
            # Stop calling this function
958
971
            return False
959
972
        
960
 
        logger.debug("IPC command: %r", cmdline)
 
973
        logger.debug(u"IPC command: %r", cmdline)
961
974
        
962
975
        # Parse and act on command
963
 
        cmd, args = cmdline.rstrip("\r\n").split(None, 1)
 
976
        cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
964
977
        
965
 
        if cmd == "NOTFOUND":
 
978
        if cmd == u"NOTFOUND":
966
979
            logger.warning(u"Client not found for fingerprint: %s",
967
980
                           args)
968
981
            if self.use_dbus:
969
982
                # Emit D-Bus signal
970
983
                mandos_dbus_service.ClientNotFound(args)
971
 
        elif cmd == "INVALID":
 
984
        elif cmd == u"INVALID":
972
985
            for client in self.clients:
973
986
                if client.name == args:
974
987
                    logger.warning(u"Client %s is invalid", args)
978
991
                    break
979
992
            else:
980
993
                logger.error(u"Unknown client %s is invalid", args)
981
 
        elif cmd == "SENDING":
 
994
        elif cmd == u"SENDING":
982
995
            for client in self.clients:
983
996
                if client.name == args:
984
997
                    logger.info(u"Sending secret to %s", client.name)
991
1004
                logger.error(u"Sending secret to unknown client %s",
992
1005
                             args)
993
1006
        else:
994
 
            logger.error("Unknown IPC command: %r", cmdline)
 
1007
            logger.error(u"Unknown IPC command: %r", cmdline)
995
1008
        
996
1009
        # Keep calling this function
997
1010
        return True
1000
1013
def string_to_delta(interval):
1001
1014
    """Parse a string and return a datetime.timedelta
1002
1015
    
1003
 
    >>> string_to_delta('7d')
 
1016
    >>> string_to_delta(u'7d')
1004
1017
    datetime.timedelta(7)
1005
 
    >>> string_to_delta('60s')
 
1018
    >>> string_to_delta(u'60s')
1006
1019
    datetime.timedelta(0, 60)
1007
 
    >>> string_to_delta('60m')
 
1020
    >>> string_to_delta(u'60m')
1008
1021
    datetime.timedelta(0, 3600)
1009
 
    >>> string_to_delta('24h')
 
1022
    >>> string_to_delta(u'24h')
1010
1023
    datetime.timedelta(1)
1011
1024
    >>> string_to_delta(u'1w')
1012
1025
    datetime.timedelta(7)
1013
 
    >>> string_to_delta('5m 30s')
 
1026
    >>> string_to_delta(u'5m 30s')
1014
1027
    datetime.timedelta(0, 330)
1015
1028
    """
1016
1029
    timevalue = datetime.timedelta(0)
1060
1073
        raise AvahiGroupError(u"State changed: %s" % unicode(error))
1061
1074
 
1062
1075
def if_nametoindex(interface):
1063
 
    """Call the C function if_nametoindex(), or equivalent"""
 
1076
    """Call the C function if_nametoindex(), or equivalent
 
1077
    
 
1078
    Note: This function cannot accept a unicode string."""
1064
1079
    global if_nametoindex
1065
1080
    try:
1066
1081
        if_nametoindex = (ctypes.cdll.LoadLibrary
1067
 
                          (ctypes.util.find_library("c"))
 
1082
                          (ctypes.util.find_library(u"c"))
1068
1083
                          .if_nametoindex)
1069
1084
    except (OSError, AttributeError):
1070
 
        if "struct" not in sys.modules:
1071
 
            import struct
1072
 
        if "fcntl" not in sys.modules:
1073
 
            import fcntl
 
1085
        logger.warning(u"Doing if_nametoindex the hard way")
1074
1086
        def if_nametoindex(interface):
1075
1087
            "Get an interface index the hard way, i.e. using fcntl()"
1076
1088
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
1077
1089
            with closing(socket.socket()) as s:
1078
1090
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1079
 
                                    struct.pack("16s16x", interface))
1080
 
            interface_index = struct.unpack("I", ifreq[16:20])[0]
 
1091
                                    struct.pack(str(u"16s16x"),
 
1092
                                                interface))
 
1093
            interface_index = struct.unpack(str(u"I"),
 
1094
                                            ifreq[16:20])[0]
1081
1095
            return interface_index
1082
1096
    return if_nametoindex(interface)
1083
1097
 
1090
1104
        sys.exit()
1091
1105
    os.setsid()
1092
1106
    if not nochdir:
1093
 
        os.chdir("/")
 
1107
        os.chdir(u"/")
1094
1108
    if os.fork():
1095
1109
        sys.exit()
1096
1110
    if not noclose:
1098
1112
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1099
1113
        if not stat.S_ISCHR(os.fstat(null).st_mode):
1100
1114
            raise OSError(errno.ENODEV,
1101
 
                          "/dev/null not a character device")
 
1115
                          u"/dev/null not a character device")
1102
1116
        os.dup2(null, sys.stdin.fileno())
1103
1117
        os.dup2(null, sys.stdout.fileno())
1104
1118
        os.dup2(null, sys.stderr.fileno())
1112
1126
    # Parsing of options, both command line and config file
1113
1127
    
1114
1128
    parser = optparse.OptionParser(version = "%%prog %s" % version)
1115
 
    parser.add_option("-i", "--interface", type="string",
1116
 
                      metavar="IF", help="Bind to interface IF")
1117
 
    parser.add_option("-a", "--address", type="string",
1118
 
                      help="Address to listen for requests on")
1119
 
    parser.add_option("-p", "--port", type="int",
1120
 
                      help="Port number to receive requests on")
1121
 
    parser.add_option("--check", action="store_true",
1122
 
                      help="Run self-test")
1123
 
    parser.add_option("--debug", action="store_true",
1124
 
                      help="Debug mode; run in foreground and log to"
1125
 
                      " terminal")
1126
 
    parser.add_option("--priority", type="string", help="GnuTLS"
1127
 
                      " priority string (see GnuTLS documentation)")
1128
 
    parser.add_option("--servicename", type="string", metavar="NAME",
1129
 
                      help="Zeroconf service name")
1130
 
    parser.add_option("--configdir", type="string",
1131
 
                      default="/etc/mandos", metavar="DIR",
1132
 
                      help="Directory to search for configuration"
1133
 
                      " files")
1134
 
    parser.add_option("--no-dbus", action="store_false",
1135
 
                      dest="use_dbus",
1136
 
                      help="Do not provide D-Bus system bus"
1137
 
                      " interface")
1138
 
    parser.add_option("--no-ipv6", action="store_false",
1139
 
                      dest="use_ipv6", help="Do not use IPv6")
 
1129
    parser.add_option("-i", u"--interface", type=u"string",
 
1130
                      metavar="IF", help=u"Bind to interface IF")
 
1131
    parser.add_option("-a", u"--address", type=u"string",
 
1132
                      help=u"Address to listen for requests on")
 
1133
    parser.add_option("-p", u"--port", type=u"int",
 
1134
                      help=u"Port number to receive requests on")
 
1135
    parser.add_option("--check", action=u"store_true",
 
1136
                      help=u"Run self-test")
 
1137
    parser.add_option("--debug", action=u"store_true",
 
1138
                      help=u"Debug mode; run in foreground and log to"
 
1139
                      u" terminal")
 
1140
    parser.add_option("--priority", type=u"string", help=u"GnuTLS"
 
1141
                      u" priority string (see GnuTLS documentation)")
 
1142
    parser.add_option("--servicename", type=u"string",
 
1143
                      metavar=u"NAME", help=u"Zeroconf service name")
 
1144
    parser.add_option("--configdir", type=u"string",
 
1145
                      default=u"/etc/mandos", metavar=u"DIR",
 
1146
                      help=u"Directory to search for configuration"
 
1147
                      u" files")
 
1148
    parser.add_option("--no-dbus", action=u"store_false",
 
1149
                      dest=u"use_dbus", help=u"Do not provide D-Bus"
 
1150
                      u" system bus interface")
 
1151
    parser.add_option("--no-ipv6", action=u"store_false",
 
1152
                      dest=u"use_ipv6", help=u"Do not use IPv6")
1140
1153
    options = parser.parse_args()[0]
1141
1154
    
1142
1155
    if options.check:
1145
1158
        sys.exit()
1146
1159
    
1147
1160
    # Default values for config file for server-global settings
1148
 
    server_defaults = { "interface": "",
1149
 
                        "address": "",
1150
 
                        "port": "",
1151
 
                        "debug": "False",
1152
 
                        "priority":
1153
 
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1154
 
                        "servicename": "Mandos",
1155
 
                        "use_dbus": "True",
1156
 
                        "use_ipv6": "True",
 
1161
    server_defaults = { u"interface": u"",
 
1162
                        u"address": u"",
 
1163
                        u"port": u"",
 
1164
                        u"debug": u"False",
 
1165
                        u"priority":
 
1166
                        u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
 
1167
                        u"servicename": u"Mandos",
 
1168
                        u"use_dbus": u"True",
 
1169
                        u"use_ipv6": u"True",
1157
1170
                        }
1158
1171
    
1159
1172
    # Parse config file for server-global settings
1160
 
    server_config = ConfigParser.SafeConfigParser(server_defaults)
 
1173
    server_config = configparser.SafeConfigParser(server_defaults)
1161
1174
    del server_defaults
1162
 
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
 
1175
    server_config.read(os.path.join(options.configdir,
 
1176
                                    u"mandos.conf"))
1163
1177
    # Convert the SafeConfigParser object to a dict
1164
1178
    server_settings = server_config.defaults()
1165
1179
    # Use the appropriate methods on the non-string config options
1166
 
    server_settings["debug"] = server_config.getboolean("DEFAULT",
1167
 
                                                        "debug")
1168
 
    server_settings["use_dbus"] = server_config.getboolean("DEFAULT",
1169
 
                                                           "use_dbus")
1170
 
    server_settings["use_ipv6"] = server_config.getboolean("DEFAULT",
1171
 
                                                           "use_ipv6")
 
1180
    for option in (u"debug", u"use_dbus", u"use_ipv6"):
 
1181
        server_settings[option] = server_config.getboolean(u"DEFAULT",
 
1182
                                                           option)
1172
1183
    if server_settings["port"]:
1173
 
        server_settings["port"] = server_config.getint("DEFAULT",
1174
 
                                                       "port")
 
1184
        server_settings["port"] = server_config.getint(u"DEFAULT",
 
1185
                                                       u"port")
1175
1186
    del server_config
1176
1187
    
1177
1188
    # Override the settings from the config file with command line
1178
1189
    # options, if set.
1179
 
    for option in ("interface", "address", "port", "debug",
1180
 
                   "priority", "servicename", "configdir",
1181
 
                   "use_dbus", "use_ipv6"):
 
1190
    for option in (u"interface", u"address", u"port", u"debug",
 
1191
                   u"priority", u"servicename", u"configdir",
 
1192
                   u"use_dbus", u"use_ipv6"):
1182
1193
        value = getattr(options, option)
1183
1194
        if value is not None:
1184
1195
            server_settings[option] = value
1185
1196
    del options
 
1197
    # Force all strings to be unicode
 
1198
    for option in server_settings.keys():
 
1199
        if type(server_settings[option]) is str:
 
1200
            server_settings[option] = unicode(server_settings[option])
1186
1201
    # Now we have our good server settings in "server_settings"
1187
1202
    
1188
1203
    ##################################################################
1189
1204
    
1190
1205
    # For convenience
1191
 
    debug = server_settings["debug"]
1192
 
    use_dbus = server_settings["use_dbus"]
1193
 
    use_ipv6 = server_settings["use_ipv6"]
 
1206
    debug = server_settings[u"debug"]
 
1207
    use_dbus = server_settings[u"use_dbus"]
 
1208
    use_ipv6 = server_settings[u"use_ipv6"]
1194
1209
    
1195
1210
    if not debug:
1196
1211
        syslogger.setLevel(logging.WARNING)
1197
1212
        console.setLevel(logging.WARNING)
1198
1213
    
1199
 
    if server_settings["servicename"] != "Mandos":
 
1214
    if server_settings[u"servicename"] != u"Mandos":
1200
1215
        syslogger.setFormatter(logging.Formatter
1201
 
                               ('Mandos (%s) [%%(process)d]:'
1202
 
                                ' %%(levelname)s: %%(message)s'
1203
 
                                % server_settings["servicename"]))
 
1216
                               (u'Mandos (%s) [%%(process)d]:'
 
1217
                                u' %%(levelname)s: %%(message)s'
 
1218
                                % server_settings[u"servicename"]))
1204
1219
    
1205
1220
    # Parse config file with clients
1206
 
    client_defaults = { "timeout": "1h",
1207
 
                        "interval": "5m",
1208
 
                        "checker": "fping -q -- %%(host)s",
1209
 
                        "host": "",
 
1221
    client_defaults = { u"timeout": u"1h",
 
1222
                        u"interval": u"5m",
 
1223
                        u"checker": u"fping -q -- %%(host)s",
 
1224
                        u"host": u"",
1210
1225
                        }
1211
 
    client_config = ConfigParser.SafeConfigParser(client_defaults)
1212
 
    client_config.read(os.path.join(server_settings["configdir"],
1213
 
                                    "clients.conf"))
1214
 
 
 
1226
    client_config = configparser.SafeConfigParser(client_defaults)
 
1227
    client_config.read(os.path.join(server_settings[u"configdir"],
 
1228
                                    u"clients.conf"))
 
1229
    
1215
1230
    global mandos_dbus_service
1216
1231
    mandos_dbus_service = None
1217
1232
    
1218
 
    clients = Set()
1219
 
    tcp_server = IPv6_TCPServer((server_settings["address"],
1220
 
                                 server_settings["port"]),
 
1233
    clients = set()
 
1234
    tcp_server = IPv6_TCPServer((server_settings[u"address"],
 
1235
                                 server_settings[u"port"]),
1221
1236
                                ClientHandler,
1222
1237
                                interface=
1223
 
                                server_settings["interface"],
 
1238
                                server_settings[u"interface"],
1224
1239
                                use_ipv6=use_ipv6,
1225
1240
                                clients=clients,
1226
1241
                                gnutls_priority=
1227
 
                                server_settings["priority"],
 
1242
                                server_settings[u"priority"],
1228
1243
                                use_dbus=use_dbus)
1229
 
    pidfilename = "/var/run/mandos.pid"
 
1244
    pidfilename = u"/var/run/mandos.pid"
1230
1245
    try:
1231
 
        pidfile = open(pidfilename, "w")
 
1246
        pidfile = open(pidfilename, u"w")
1232
1247
    except IOError:
1233
 
        logger.error("Could not open file %r", pidfilename)
 
1248
        logger.error(u"Could not open file %r", pidfilename)
1234
1249
    
1235
1250
    try:
1236
 
        uid = pwd.getpwnam("_mandos").pw_uid
1237
 
        gid = pwd.getpwnam("_mandos").pw_gid
 
1251
        uid = pwd.getpwnam(u"_mandos").pw_uid
 
1252
        gid = pwd.getpwnam(u"_mandos").pw_gid
1238
1253
    except KeyError:
1239
1254
        try:
1240
 
            uid = pwd.getpwnam("mandos").pw_uid
1241
 
            gid = pwd.getpwnam("mandos").pw_gid
 
1255
            uid = pwd.getpwnam(u"mandos").pw_uid
 
1256
            gid = pwd.getpwnam(u"mandos").pw_gid
1242
1257
        except KeyError:
1243
1258
            try:
1244
 
                uid = pwd.getpwnam("nobody").pw_uid
1245
 
                gid = pwd.getpwnam("nogroup").pw_gid
 
1259
                uid = pwd.getpwnam(u"nobody").pw_uid
 
1260
                gid = pwd.getpwnam(u"nobody").pw_gid
1246
1261
            except KeyError:
1247
1262
                uid = 65534
1248
1263
                gid = 65534
1261
1276
        
1262
1277
        @gnutls.library.types.gnutls_log_func
1263
1278
        def debug_gnutls(level, string):
1264
 
            logger.debug("GnuTLS: %s", string[:-1])
 
1279
            logger.debug(u"GnuTLS: %s", string[:-1])
1265
1280
        
1266
1281
        (gnutls.library.functions
1267
1282
         .gnutls_global_set_log_function(debug_gnutls))
1268
1283
    
1269
1284
    global service
1270
1285
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1271
 
    service = AvahiService(name = server_settings["servicename"],
1272
 
                           servicetype = "_mandos._tcp",
 
1286
    service = AvahiService(name = server_settings[u"servicename"],
 
1287
                           servicetype = u"_mandos._tcp",
1273
1288
                           protocol = protocol)
1274
1289
    if server_settings["interface"]:
1275
1290
        service.interface = (if_nametoindex
1276
 
                             (server_settings["interface"]))
 
1291
                             (str(server_settings[u"interface"])))
1277
1292
    
1278
1293
    global main_loop
1279
1294
    global bus
1292
1307
    client_class = Client
1293
1308
    if use_dbus:
1294
1309
        client_class = ClientDBus
1295
 
    clients.update(Set(
 
1310
    clients.update(set(
1296
1311
            client_class(name = section,
1297
1312
                         config= dict(client_config.items(section)))
1298
1313
            for section in client_config.sections()))
1349
1364
        class MandosDBusService(dbus.service.Object):
1350
1365
            """A D-Bus proxy object"""
1351
1366
            def __init__(self):
1352
 
                dbus.service.Object.__init__(self, bus, "/")
 
1367
                dbus.service.Object.__init__(self, bus, u"/")
1353
1368
            _interface = u"se.bsnet.fukt.Mandos"
1354
1369
            
1355
 
            @dbus.service.signal(_interface, signature="oa{sv}")
 
1370
            @dbus.service.signal(_interface, signature=u"oa{sv}")
1356
1371
            def ClientAdded(self, objpath, properties):
1357
1372
                "D-Bus signal"
1358
1373
                pass
1359
1374
            
1360
 
            @dbus.service.signal(_interface, signature="s")
 
1375
            @dbus.service.signal(_interface, signature=u"s")
1361
1376
            def ClientNotFound(self, fingerprint):
1362
1377
                "D-Bus signal"
1363
1378
                pass
1364
1379
            
1365
 
            @dbus.service.signal(_interface, signature="os")
 
1380
            @dbus.service.signal(_interface, signature=u"os")
1366
1381
            def ClientRemoved(self, objpath, name):
1367
1382
                "D-Bus signal"
1368
1383
                pass
1369
1384
            
1370
 
            @dbus.service.method(_interface, out_signature="ao")
 
1385
            @dbus.service.method(_interface, out_signature=u"ao")
1371
1386
            def GetAllClients(self):
1372
1387
                "D-Bus method"
1373
1388
                return dbus.Array(c.dbus_object_path for c in clients)
1374
1389
            
1375
 
            @dbus.service.method(_interface, out_signature="a{oa{sv}}")
 
1390
            @dbus.service.method(_interface,
 
1391
                                 out_signature=u"a{oa{sv}}")
1376
1392
            def GetAllClientsWithProperties(self):
1377
1393
                "D-Bus method"
1378
1394
                return dbus.Dictionary(
1379
1395
                    ((c.dbus_object_path, c.GetAllProperties())
1380
1396
                     for c in clients),
1381
 
                    signature="oa{sv}")
 
1397
                    signature=u"oa{sv}")
1382
1398
            
1383
 
            @dbus.service.method(_interface, in_signature="o")
 
1399
            @dbus.service.method(_interface, in_signature=u"o")
1384
1400
            def RemoveClient(self, object_path):
1385
1401
                "D-Bus method"
1386
1402
                for c in clients:
1422
1438
    
1423
1439
    try:
1424
1440
        # From the Avahi example code
1425
 
        server.connect_to_signal("StateChanged", server_state_changed)
 
1441
        server.connect_to_signal(u"StateChanged", server_state_changed)
1426
1442
        try:
1427
1443
            server_state_changed(server.GetState())
1428
1444
        except dbus.exceptions.DBusException, error:
1443
1459
    except KeyboardInterrupt:
1444
1460
        if debug:
1445
1461
            print >> sys.stderr
1446
 
        logger.debug("Server received KeyboardInterrupt")
1447
 
    logger.debug("Server exiting")
 
1462
        logger.debug(u"Server received KeyboardInterrupt")
 
1463
    logger.debug(u"Server exiting")
1448
1464
 
1449
1465
if __name__ == '__main__':
1450
1466
    main()