/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 01:00:35 UTC
  • Revision ID: teddy@fukt.bsnet.se-20090416010035-y7ta6ra2da4gf6mp
Minor code cleanup; one minor bug fix.

* initramfs-tools-hook: Bug fix: Use the primary group of the first
                        suitable user found, do not look for a
                        group separately.
* mandos: Unconditionally import "struct" and "fcntl".  Use unicode
          strings everywhere possible.
  (Client._datetime_to_milliseconds): New static method.
  (Client.timeout_milliseconds, Client.interval_milliseconds): Use
                                                               above
                                                               method.
  (ClientDBus.CheckedOK,
  ClientDBus.Enable, ClientDBus.StopChecker): Define normally.
  (if_nametoindex): Document non-acceptance of unicode strings.  All
                    callers adjusted.  Do not import "struct" or
                    "fcntl".  Log warning message if if_nametoindex
                    cannot be found using ctypes modules.
  (main): Bug fix: Do not look for user named "nogroup".

Show diffs side-by-side

added added

removed removed

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