/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:
6
6
# This program is partly derived from an example program for an Avahi
7
7
# service publisher, downloaded from
8
8
# <http://avahi.org/wiki/PythonPublishExample>.  This includes the
9
 
# methods "add", "remove", "server_state_changed",
10
 
# "entry_group_state_changed", "cleanup", and "activate" in the
11
 
# "AvahiService" class, and some lines in "main".
 
9
# methods "add" and "remove" in the "AvahiService" class, the
 
10
# "server_state_changed" and "entry_group_state_changed" functions,
 
11
# and some lines in "main".
12
12
13
13
# Everything else is
14
14
# Copyright © 2008,2009 Teddy Hogeborn
33
33
 
34
34
from __future__ import division, with_statement, absolute_import
35
35
 
36
 
import SocketServer as socketserver
 
36
import 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 as configparser
 
47
import ConfigParser
48
48
import sys
49
49
import re
50
50
import os
51
51
import signal
 
52
from sets import Set
52
53
import subprocess
53
54
import atexit
54
55
import stat
58
59
from contextlib import closing
59
60
import struct
60
61
import fcntl
61
 
import functools
62
62
 
63
63
import dbus
64
64
import dbus.service
125
125
    max_renames: integer; maximum number of renames
126
126
    rename_count: integer; counter so we only rename after collisions
127
127
                  a sensible number of times
128
 
    group: D-Bus Entry Group
129
 
    server: D-Bus Server
130
128
    """
131
129
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
132
130
                 servicetype = None, port = None, TXT = None,
133
131
                 domain = u"", host = u"", max_renames = 32768,
134
 
                 protocol = avahi.PROTO_UNSPEC, bus = None):
 
132
                 protocol = avahi.PROTO_UNSPEC):
135
133
        self.interface = interface
136
134
        self.name = name
137
135
        self.type = servicetype
142
140
        self.rename_count = 0
143
141
        self.max_renames = max_renames
144
142
        self.protocol = protocol
145
 
        self.group = None       # our entry group
146
 
        self.server = None
147
 
        self.bus = bus
148
143
    def rename(self):
149
144
        """Derived from the Avahi example code"""
150
145
        if self.rename_count >= self.max_renames:
152
147
                            u" after %i retries, exiting.",
153
148
                            self.rename_count)
154
149
            raise AvahiServiceError(u"Too many renames")
155
 
        self.name = self.server.GetAlternativeServiceName(self.name)
 
150
        self.name = server.GetAlternativeServiceName(self.name)
156
151
        logger.info(u"Changing Zeroconf service name to %r ...",
157
 
                    unicode(self.name))
 
152
                    self.name)
158
153
        syslogger.setFormatter(logging.Formatter
159
154
                               (u'Mandos (%s) [%%(process)d]:'
160
155
                                u' %%(levelname)s: %%(message)s'
164
159
        self.rename_count += 1
165
160
    def remove(self):
166
161
        """Derived from the Avahi example code"""
167
 
        if self.group is not None:
168
 
            self.group.Reset()
 
162
        if group is not None:
 
163
            group.Reset()
169
164
    def add(self):
170
165
        """Derived from the Avahi example code"""
171
 
        if self.group is None:
172
 
            self.group = dbus.Interface(
173
 
                self.bus.get_object(avahi.DBUS_NAME,
174
 
                                    self.server.EntryGroupNew()),
175
 
                avahi.DBUS_INTERFACE_ENTRY_GROUP)
176
 
            self.group.connect_to_signal('StateChanged',
177
 
                                         self.entry_group_state_changed)
 
166
        global group
 
167
        if group is None:
 
168
            group = dbus.Interface(bus.get_object
 
169
                                   (avahi.DBUS_NAME,
 
170
                                    server.EntryGroupNew()),
 
171
                                   avahi.DBUS_INTERFACE_ENTRY_GROUP)
 
172
            group.connect_to_signal('StateChanged',
 
173
                                    entry_group_state_changed)
178
174
        logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
179
 
                     self.name, self.type)
180
 
        self.group.AddService(
181
 
            self.interface,
182
 
            self.protocol,
183
 
            dbus.UInt32(0),     # flags
184
 
            self.name, self.type,
185
 
            self.domain, self.host,
186
 
            dbus.UInt16(self.port),
187
 
            avahi.string_array_to_txt_array(self.TXT))
188
 
        self.group.Commit()
189
 
    def entry_group_state_changed(self, state, error):
190
 
        """Derived from the Avahi example code"""
191
 
        logger.debug(u"Avahi state change: %i", state)
192
 
        
193
 
        if state == avahi.ENTRY_GROUP_ESTABLISHED:
194
 
            logger.debug(u"Zeroconf service established.")
195
 
        elif state == avahi.ENTRY_GROUP_COLLISION:
196
 
            logger.warning(u"Zeroconf service name collision.")
197
 
            self.rename()
198
 
        elif state == avahi.ENTRY_GROUP_FAILURE:
199
 
            logger.critical(u"Avahi: Error in group state changed %s",
200
 
                            unicode(error))
201
 
            raise AvahiGroupError(u"State changed: %s"
202
 
                                  % unicode(error))
203
 
    def cleanup(self):
204
 
        """Derived from the Avahi example code"""
205
 
        if self.group is not None:
206
 
            self.group.Free()
207
 
            self.group = None
208
 
    def server_state_changed(self, state):
209
 
        """Derived from the Avahi example code"""
210
 
        if state == avahi.SERVER_COLLISION:
211
 
            logger.error(u"Zeroconf server name collision")
212
 
            self.remove()
213
 
        elif state == avahi.SERVER_RUNNING:
214
 
            self.add()
215
 
    def activate(self):
216
 
        """Derived from the Avahi example code"""
217
 
        if self.server is None:
218
 
            self.server = dbus.Interface(
219
 
                self.bus.get_object(avahi.DBUS_NAME,
220
 
                                    avahi.DBUS_PATH_SERVER),
221
 
                avahi.DBUS_INTERFACE_SERVER)
222
 
        self.server.connect_to_signal(u"StateChanged",
223
 
                                 self.server_state_changed)
224
 
        self.server_state_changed(self.server.GetState())
 
175
                     service.name, service.type)
 
176
        group.AddService(
 
177
                self.interface,         # interface
 
178
                self.protocol,          # protocol
 
179
                dbus.UInt32(0),         # flags
 
180
                self.name, self.type,
 
181
                self.domain, self.host,
 
182
                dbus.UInt16(self.port),
 
183
                avahi.string_array_to_txt_array(self.TXT))
 
184
        group.Commit()
 
185
 
 
186
# From the Avahi example code:
 
187
group = None                            # our entry group
 
188
# End of Avahi example code
 
189
 
 
190
 
 
191
def _datetime_to_dbus(dt, variant_level=0):
 
192
    """Convert a UTC datetime.datetime() to a D-Bus type."""
 
193
    return dbus.String(dt.isoformat(), variant_level=variant_level)
225
194
 
226
195
 
227
196
class Client(object):
482
451
    """
483
452
    # dbus.service.Object doesn't use super(), so we can't either.
484
453
    
485
 
    def __init__(self, bus = None, *args, **kwargs):
486
 
        self.bus = bus
 
454
    def __init__(self, *args, **kwargs):
487
455
        Client.__init__(self, *args, **kwargs)
488
456
        # Only now, when this client is initialized, can it show up on
489
457
        # the D-Bus
490
458
        self.dbus_object_path = (dbus.ObjectPath
491
459
                                 (u"/clients/"
492
460
                                  + self.name.replace(u".", u"_")))
493
 
        dbus.service.Object.__init__(self, self.bus,
 
461
        dbus.service.Object.__init__(self, bus,
494
462
                                     self.dbus_object_path)
495
 
    
496
 
    @staticmethod
497
 
    def _datetime_to_dbus(dt, variant_level=0):
498
 
        """Convert a UTC datetime.datetime() to a D-Bus type."""
499
 
        return dbus.String(dt.isoformat(),
500
 
                           variant_level=variant_level)
501
 
    
502
463
    def enable(self):
503
464
        oldstate = getattr(self, u"enabled", False)
504
465
        r = Client.enable(self)
506
467
            # Emit D-Bus signals
507
468
            self.PropertyChanged(dbus.String(u"enabled"),
508
469
                                 dbus.Boolean(True, variant_level=1))
509
 
            self.PropertyChanged(
510
 
                dbus.String(u"last_enabled"),
511
 
                self._datetime_to_dbus(self.last_enabled,
512
 
                                       variant_level=1))
 
470
            self.PropertyChanged(dbus.String(u"last_enabled"),
 
471
                                 (_datetime_to_dbus(self.last_enabled,
 
472
                                                    variant_level=1)))
513
473
        return r
514
474
    
515
475
    def disable(self, signal = True):
557
517
        # Emit D-Bus signal
558
518
        self.PropertyChanged(
559
519
            dbus.String(u"last_checked_ok"),
560
 
            (self._datetime_to_dbus(self.last_checked_ok,
561
 
                                    variant_level=1)))
 
520
            (_datetime_to_dbus(self.last_checked_ok,
 
521
                               variant_level=1)))
562
522
        return r
563
523
    
564
524
    def start_checker(self, *args, **kwargs):
619
579
                dbus.String(u"host"):
620
580
                    dbus.String(self.host, variant_level=1),
621
581
                dbus.String(u"created"):
622
 
                    self._datetime_to_dbus(self.created,
623
 
                                           variant_level=1),
 
582
                    _datetime_to_dbus(self.created, variant_level=1),
624
583
                dbus.String(u"last_enabled"):
625
 
                    (self._datetime_to_dbus(self.last_enabled,
626
 
                                            variant_level=1)
 
584
                    (_datetime_to_dbus(self.last_enabled,
 
585
                                       variant_level=1)
627
586
                     if self.last_enabled is not None
628
587
                     else dbus.Boolean(False, variant_level=1)),
629
588
                dbus.String(u"enabled"):
630
589
                    dbus.Boolean(self.enabled, variant_level=1),
631
590
                dbus.String(u"last_checked_ok"):
632
 
                    (self._datetime_to_dbus(self.last_checked_ok,
633
 
                                            variant_level=1)
 
591
                    (_datetime_to_dbus(self.last_checked_ok,
 
592
                                       variant_level=1)
634
593
                     if self.last_checked_ok is not None
635
594
                     else dbus.Boolean (False, variant_level=1)),
636
595
                dbus.String(u"timeout"):
743
702
    del _interface
744
703
 
745
704
 
746
 
class ClientHandler(socketserver.BaseRequestHandler, object):
 
705
class ClientHandler(SocketServer.BaseRequestHandler, object):
747
706
    """A class to handle client connections.
748
707
    
749
708
    Instantiated once for each connection to handle it.
891
850
        return hex_fpr
892
851
 
893
852
 
894
 
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
895
 
    """Like socketserver.ForkingMixIn, but also pass a pipe.
 
853
class ForkingMixInWithPipe(SocketServer.ForkingMixIn, object):
 
854
    """Like SocketServer.ForkingMixIn, but also pass a pipe.
896
855
    
897
856
    Assumes a gobject.MainLoop event loop.
898
857
    """
916
875
 
917
876
 
918
877
class IPv6_TCPServer(ForkingMixInWithPipe,
919
 
                     socketserver.TCPServer, object):
 
878
                     SocketServer.TCPServer, object):
920
879
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
921
880
    
922
881
    Attributes:
924
883
        interface:      None or a network interface name (string)
925
884
        use_ipv6:       Boolean; to use IPv6 or not
926
885
        ----
927
 
        clients:        set of Client objects
 
886
        clients:        Set() of Client objects
928
887
        gnutls_priority GnuTLS priority string
929
888
        use_dbus:       Boolean; to emit D-Bus signals or not
930
889
    """
938
897
        self.clients = clients
939
898
        self.use_dbus = use_dbus
940
899
        self.gnutls_priority = gnutls_priority
941
 
        socketserver.TCPServer.__init__(self, server_address,
 
900
        SocketServer.TCPServer.__init__(self, server_address,
942
901
                                        RequestHandlerClass)
943
902
    def server_bind(self):
944
903
        """This overrides the normal server_bind() function
974
933
#                                            0, # flowinfo
975
934
#                                            if_nametoindex
976
935
#                                            (self.interface))
977
 
            return socketserver.TCPServer.server_bind(self)
 
936
            return SocketServer.TCPServer.server_bind(self)
978
937
    def server_activate(self):
979
938
        if self.enabled:
980
 
            return socketserver.TCPServer.server_activate(self)
 
939
            return SocketServer.TCPServer.server_activate(self)
981
940
    def enable(self):
982
941
        self.enabled = True
983
942
    def handle_ipc(self, source, condition, file_objects={}):
1091
1050
    return timevalue
1092
1051
 
1093
1052
 
 
1053
def server_state_changed(state):
 
1054
    """Derived from the Avahi example code"""
 
1055
    if state == avahi.SERVER_COLLISION:
 
1056
        logger.error(u"Zeroconf server name collision")
 
1057
        service.remove()
 
1058
    elif state == avahi.SERVER_RUNNING:
 
1059
        service.add()
 
1060
 
 
1061
 
 
1062
def entry_group_state_changed(state, error):
 
1063
    """Derived from the Avahi example code"""
 
1064
    logger.debug(u"Avahi state change: %i", state)
 
1065
    
 
1066
    if state == avahi.ENTRY_GROUP_ESTABLISHED:
 
1067
        logger.debug(u"Zeroconf service established.")
 
1068
    elif state == avahi.ENTRY_GROUP_COLLISION:
 
1069
        logger.warning(u"Zeroconf service name collision.")
 
1070
        service.rename()
 
1071
    elif state == avahi.ENTRY_GROUP_FAILURE:
 
1072
        logger.critical(u"Avahi: Error in group state changed %s",
 
1073
                        unicode(error))
 
1074
        raise AvahiGroupError(u"State changed: %s" % unicode(error))
 
1075
 
1094
1076
def if_nametoindex(interface):
1095
1077
    """Call the C function if_nametoindex(), or equivalent
1096
1078
    
1189
1171
                        }
1190
1172
    
1191
1173
    # Parse config file for server-global settings
1192
 
    server_config = configparser.SafeConfigParser(server_defaults)
 
1174
    server_config = ConfigParser.SafeConfigParser(server_defaults)
1193
1175
    del server_defaults
1194
1176
    server_config.read(os.path.join(options.configdir,
1195
1177
                                    u"mandos.conf"))
1242
1224
                        u"checker": u"fping -q -- %%(host)s",
1243
1225
                        u"host": u"",
1244
1226
                        }
1245
 
    client_config = configparser.SafeConfigParser(client_defaults)
 
1227
    client_config = ConfigParser.SafeConfigParser(client_defaults)
1246
1228
    client_config.read(os.path.join(server_settings[u"configdir"],
1247
1229
                                    u"clients.conf"))
1248
1230
    
1249
1231
    global mandos_dbus_service
1250
1232
    mandos_dbus_service = None
1251
1233
    
1252
 
    clients = set()
 
1234
    clients = Set()
1253
1235
    tcp_server = IPv6_TCPServer((server_settings[u"address"],
1254
1236
                                 server_settings[u"port"]),
1255
1237
                                ClientHandler,
1300
1282
        (gnutls.library.functions
1301
1283
         .gnutls_global_set_log_function(debug_gnutls))
1302
1284
    
 
1285
    global service
 
1286
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
 
1287
    service = AvahiService(name = server_settings[u"servicename"],
 
1288
                           servicetype = u"_mandos._tcp",
 
1289
                           protocol = protocol)
 
1290
    if server_settings["interface"]:
 
1291
        service.interface = (if_nametoindex
 
1292
                             (str(server_settings[u"interface"])))
 
1293
    
1303
1294
    global main_loop
 
1295
    global bus
 
1296
    global server
1304
1297
    # From the Avahi example code
1305
1298
    DBusGMainLoop(set_as_default=True )
1306
1299
    main_loop = gobject.MainLoop()
1307
1300
    bus = dbus.SystemBus()
 
1301
    server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
 
1302
                                           avahi.DBUS_PATH_SERVER),
 
1303
                            avahi.DBUS_INTERFACE_SERVER)
1308
1304
    # End of Avahi example code
1309
1305
    if use_dbus:
1310
1306
        bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1311
 
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1312
 
    service = AvahiService(name = server_settings[u"servicename"],
1313
 
                           servicetype = u"_mandos._tcp",
1314
 
                           protocol = protocol, bus = bus)
1315
 
    if server_settings["interface"]:
1316
 
        service.interface = (if_nametoindex
1317
 
                             (str(server_settings[u"interface"])))
1318
1307
    
1319
1308
    client_class = Client
1320
1309
    if use_dbus:
1321
 
        client_class = functools.partial(ClientDBus, bus = bus)
1322
 
    clients.update(set(
 
1310
        client_class = ClientDBus
 
1311
    clients.update(Set(
1323
1312
            client_class(name = section,
1324
1313
                         config= dict(client_config.items(section)))
1325
1314
            for section in client_config.sections()))
1353
1342
    
1354
1343
    def cleanup():
1355
1344
        "Cleanup function; run on exit"
1356
 
        service.cleanup()
 
1345
        global group
 
1346
        # From the Avahi example code
 
1347
        if not group is None:
 
1348
            group.Free()
 
1349
            group = None
 
1350
        # End of Avahi example code
1357
1351
        
1358
1352
        while clients:
1359
1353
            client = clients.pop()
1445
1439
    
1446
1440
    try:
1447
1441
        # From the Avahi example code
 
1442
        server.connect_to_signal(u"StateChanged", server_state_changed)
1448
1443
        try:
1449
 
            service.activate()
 
1444
            server_state_changed(server.GetState())
1450
1445
        except dbus.exceptions.DBusException, error:
1451
1446
            logger.critical(u"DBusException: %s", error)
1452
1447
            sys.exit(1)