/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

merge

Show diffs side-by-side

added added

removed removed

Lines of Context:
83
83
 
84
84
version = "1.0.14"
85
85
 
 
86
#logger = logging.getLogger(u'mandos')
86
87
logger = logging.Logger(u'mandos')
87
88
syslogger = (logging.handlers.SysLogHandler
88
89
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
98
99
                                       u' %(message)s'))
99
100
logger.addHandler(console)
100
101
 
101
 
multiprocessing_manager = multiprocessing.Manager()
102
 
 
103
102
class AvahiError(Exception):
104
103
    def __init__(self, value, *args, **kwargs):
105
104
        self.value = value
158
157
                            u" after %i retries, exiting.",
159
158
                            self.rename_count)
160
159
            raise AvahiServiceError(u"Too many renames")
161
 
        self.name = self.server.GetAlternativeServiceName(self.name)
 
160
        self.name = unicode(self.server.GetAlternativeServiceName(self.name))
162
161
        logger.info(u"Changing Zeroconf service name to %r ...",
163
 
                    unicode(self.name))
 
162
                    self.name)
164
163
        syslogger.setFormatter(logging.Formatter
165
164
                               (u'Mandos (%s) [%%(process)d]:'
166
165
                                u' %%(levelname)s: %%(message)s'
167
166
                                % self.name))
168
167
        self.remove()
169
 
        self.add()
 
168
        try:
 
169
            self.add()
 
170
        except dbus.exceptions.DBusException, error:
 
171
            logger.critical(u"DBusException: %s", error)
 
172
            self.cleanup()
 
173
            os._exit(1)
170
174
        self.rename_count += 1
171
175
    def remove(self):
172
176
        """Derived from the Avahi example code"""
195
199
        self.group.Commit()
196
200
    def entry_group_state_changed(self, state, error):
197
201
        """Derived from the Avahi example code"""
198
 
        logger.debug(u"Avahi state change: %i", state)
 
202
        logger.debug(u"Avahi entry group state change: %i", state)
199
203
        
200
204
        if state == avahi.ENTRY_GROUP_ESTABLISHED:
201
205
            logger.debug(u"Zeroconf service established.")
214
218
            self.group = None
215
219
    def server_state_changed(self, state):
216
220
        """Derived from the Avahi example code"""
 
221
        logger.debug(u"Avahi server state change: %i", state)
217
222
        if state == avahi.SERVER_COLLISION:
218
223
            logger.error(u"Zeroconf server name collision")
219
224
            self.remove()
231
236
        self.server_state_changed(self.server.GetState())
232
237
 
233
238
 
234
 
# XXX Need to add:
235
 
# approved_by_default (Config option for each client)
236
 
# approved_delay (config option for each client)
237
 
# approved_duration (config option for each client)
238
239
class Client(object):
239
240
    """A representation of a client host served by this server.
240
241
    
309
310
                      "rb") as secfile:
310
311
                self.secret = secfile.read()
311
312
        else:
312
 
            #XXX Need to allow secret on demand!
313
313
            raise TypeError(u"No secret or secfile for client %s"
314
314
                            % self.name)
315
315
        self.host = config.get(u"host", u"")
329
329
        self.last_connect = None
330
330
        self._approved = None
331
331
        self.approved_by_default = config.get(u"approved_by_default",
332
 
                                              False)
 
332
                                              True)
 
333
        self.approvals_pending = 0
333
334
        self.approved_delay = string_to_delta(
334
335
            config[u"approved_delay"])
335
336
        self.approved_duration = string_to_delta(
336
337
            config[u"approved_duration"])
337
338
        self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
338
 
 
 
339
    
339
340
    def send_changedstate(self):
340
341
        self.changedstate.acquire()
341
342
        self.changedstate.notify_all()
705
706
    # dbus.service.Object doesn't use super(), so we can't either.
706
707
    
707
708
    def __init__(self, bus = None, *args, **kwargs):
 
709
        self._approvals_pending = 0
708
710
        self.bus = bus
709
711
        Client.__init__(self, *args, **kwargs)
710
712
        # Only now, when this client is initialized, can it show up on
714
716
                                  + self.name.replace(u".", u"_")))
715
717
        DBusObjectWithProperties.__init__(self, self.bus,
716
718
                                          self.dbus_object_path)
 
719
 
 
720
    def _get_approvals_pending(self):
 
721
        return self._approvals_pending
 
722
    def _set_approvals_pending(self, value):
 
723
        old_value = self._approvals_pending
 
724
        self._approvals_pending = value
 
725
        bval = bool(value)
 
726
        if (hasattr(self, "dbus_object_path")
 
727
            and bval is not bool(old_value)):
 
728
            dbus_bool = dbus.Boolean(bval, variant_level=1)
 
729
            self.PropertyChanged(dbus.String(u"approved_pending"),
 
730
                                 dbus_bool)
 
731
 
 
732
    approvals_pending = property(_get_approvals_pending,
 
733
                                 _set_approvals_pending)
 
734
    del _get_approvals_pending, _set_approvals_pending
717
735
    
718
736
    @staticmethod
719
737
    def _datetime_to_dbus(dt, variant_level=0):
814
832
        return False
815
833
    
816
834
    def approve(self, value=True):
 
835
        self.send_changedstate()
817
836
        self._approved = value
818
 
        gobject.timeout_add(self._timedelta_to_milliseconds(self.approved_duration, self._reset_approved))
 
837
        gobject.timeout_add(self._timedelta_to_milliseconds(self.approved_duration),
 
838
                            self._reset_approved)
 
839
    
819
840
    
820
841
    ## D-Bus methods, signals & properties
821
842
    _interface = u"se.bsnet.fukt.Mandos.Client"
843
864
    # GotSecret - signal
844
865
    @dbus.service.signal(_interface)
845
866
    def GotSecret(self):
846
 
        "D-Bus signal"
 
867
        """D-Bus signal
 
868
        Is sent after a successful transfer of secret from the Mandos
 
869
        server to mandos-client
 
870
        """
847
871
        pass
848
872
    
849
873
    # Rejected - signal
895
919
    
896
920
    ## Properties
897
921
    
898
 
    # xxx 3 new properties
 
922
    # approved_pending - property
 
923
    @dbus_service_property(_interface, signature=u"b", access=u"read")
 
924
    def approved_pending_dbus_property(self):
 
925
        return dbus.Boolean(bool(self.approvals_pending))
 
926
    
 
927
    # approved_by_default - property
 
928
    @dbus_service_property(_interface, signature=u"b",
 
929
                           access=u"readwrite")
 
930
    def approved_by_default_dbus_property(self):
 
931
        return dbus.Boolean(self.approved_by_default)
 
932
    
 
933
    # approved_delay - property
 
934
    @dbus_service_property(_interface, signature=u"t",
 
935
                           access=u"readwrite")
 
936
    def approved_delay_dbus_property(self):
 
937
        return dbus.UInt64(self.approved_delay_milliseconds())
 
938
    
 
939
    # approved_duration - property
 
940
    @dbus_service_property(_interface, signature=u"t",
 
941
                           access=u"readwrite")
 
942
    def approved_duration_dbus_property(self):
 
943
        return dbus.UInt64(self._timedelta_to_milliseconds(
 
944
                self.approved_duration))
899
945
    
900
946
    # name - property
901
947
    @dbus_service_property(_interface, signature=u"s", access=u"read")
1117
1163
                # established.  Just abandon the request.
1118
1164
                return
1119
1165
            logger.debug(u"Handshake succeeded")
 
1166
 
 
1167
            approval_required = False
1120
1168
            try:
1121
1169
                try:
1122
1170
                    fpr = self.fingerprint(self.peer_certificate
1132
1180
                except KeyError:
1133
1181
                    return
1134
1182
                
1135
 
                delay = client.approved_delay
 
1183
                if client.approved_delay:
 
1184
                    delay = client.approved_delay
 
1185
                    client.approvals_pending += 1
 
1186
                    approval_required = True
 
1187
                
1136
1188
                while True:
1137
1189
                    if not client.enabled:
1138
1190
                        logger.warning(u"Client %s is disabled",
1141
1193
                            # Emit D-Bus signal
1142
1194
                            client.Rejected("Disabled")                    
1143
1195
                        return
1144
 
                    if client._approved is None:
 
1196
                    
 
1197
                    if client._approved or not client.approved_delay:
 
1198
                        #We are approved or approval is disabled
 
1199
                        break
 
1200
                    elif client._approved is None:
1145
1201
                        logger.info(u"Client %s need approval",
1146
1202
                                    client.name)
1147
1203
                        if self.server.use_dbus:
1149
1205
                            client.NeedApproval(
1150
1206
                                client.approved_delay_milliseconds(),
1151
1207
                                client.approved_by_default)
1152
 
                    elif client._approved:
1153
 
                        #We have a password and are approved
1154
 
                        break
1155
1208
                    else:
1156
1209
                        logger.warning(u"Client %s was not approved",
1157
1210
                                       client.name)
1158
1211
                        if self.server.use_dbus:
1159
 
                            # Emit D-Bus signal                        
 
1212
                            # Emit D-Bus signal
1160
1213
                            client.Rejected("Disapproved")
1161
1214
                        return
1162
1215
                    
1183
1236
                
1184
1237
                sent_size = 0
1185
1238
                while sent_size < len(client.secret):
1186
 
                    # XXX handle session exception
1187
 
                    sent = session.send(client.secret[sent_size:])
 
1239
                    try:
 
1240
                        sent = session.send(client.secret[sent_size:])
 
1241
                    except (gnutls.errors.GNUTLSError), error:
 
1242
                        logger.warning("gnutls send failed")
 
1243
                        return
1188
1244
                    logger.debug(u"Sent: %d, remaining: %d",
1189
1245
                                 sent, len(client.secret)
1190
1246
                                 - (sent_size + sent))
1196
1252
                if self.server.use_dbus:
1197
1253
                    # Emit D-Bus signal
1198
1254
                    client.GotSecret()
1199
 
 
 
1255
            
1200
1256
            finally:
1201
 
                session.bye()
 
1257
                if approval_required:
 
1258
                    client.approvals_pending -= 1
 
1259
                try:
 
1260
                    session.bye()
 
1261
                except (gnutls.errors.GNUTLSError), error:
 
1262
                    logger.warning("gnutls bye failed")
1202
1263
    
1203
1264
    @staticmethod
1204
1265
    def peer_certificate(session):
1289
1350
 
1290
1351
        super(MultiprocessingMixInWithPipe,
1291
1352
              self).process_request(request, client_address)
 
1353
        self.child_pipe.close()
1292
1354
        self.add_pipe(parent_pipe)
 
1355
 
1293
1356
    def add_pipe(self, parent_pipe):
1294
1357
        """Dummy function; override as necessary"""
1295
1358
        pass
1410
1473
                                       if cond & condition)
1411
1474
        logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1412
1475
                     conditions_string)
 
1476
 
 
1477
        # error or the other end of multiprocessing.Pipe has closed
 
1478
        if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
 
1479
            return False
1413
1480
        
1414
1481
        # Read a request from the child
1415
1482
        request = parent_pipe.recv()
 
1483
        logger.debug(u"IPC request: %s", repr(request))
1416
1484
        command = request[0]
1417
1485
        
1418
1486
        if command == 'init':
1453
1521
                parent_pipe.send(('function',))
1454
1522
            else:
1455
1523
                parent_pipe.send(('data', client_object.__getattribute__(attrname)))
1456
 
 
 
1524
        
1457
1525
        if command == 'setattr':
1458
1526
            attrname = request[1]
1459
1527
            value = request[2]
1460
1528
            setattr(client_object, attrname, value)
1461
 
            
 
1529
 
1462
1530
        return True
1463
1531
 
1464
1532
 
1567
1635
    parser.add_option("--debug", action=u"store_true",
1568
1636
                      help=u"Debug mode; run in foreground and log to"
1569
1637
                      u" terminal")
 
1638
    parser.add_option("--debuglevel", type=u"string", metavar="Level",
 
1639
                      help=u"Debug level for stdout output")
1570
1640
    parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1571
1641
                      u" priority string (see GnuTLS documentation)")
1572
1642
    parser.add_option("--servicename", type=u"string",
1597
1667
                        u"servicename": u"Mandos",
1598
1668
                        u"use_dbus": u"True",
1599
1669
                        u"use_ipv6": u"True",
 
1670
                        u"debuglevel": u"",
1600
1671
                        }
1601
1672
    
1602
1673
    # Parse config file for server-global settings
1619
1690
    # options, if set.
1620
1691
    for option in (u"interface", u"address", u"port", u"debug",
1621
1692
                   u"priority", u"servicename", u"configdir",
1622
 
                   u"use_dbus", u"use_ipv6"):
 
1693
                   u"use_dbus", u"use_ipv6", u"debuglevel"):
1623
1694
        value = getattr(options, option)
1624
1695
        if value is not None:
1625
1696
            server_settings[option] = value
1634
1705
    
1635
1706
    # For convenience
1636
1707
    debug = server_settings[u"debug"]
 
1708
    debuglevel = server_settings[u"debuglevel"]
1637
1709
    use_dbus = server_settings[u"use_dbus"]
1638
1710
    use_ipv6 = server_settings[u"use_ipv6"]
1639
 
    
1640
 
    if not debug:
1641
 
        syslogger.setLevel(logging.WARNING)
1642
 
        console.setLevel(logging.WARNING)
1643
 
    
 
1711
 
1644
1712
    if server_settings[u"servicename"] != u"Mandos":
1645
1713
        syslogger.setFormatter(logging.Formatter
1646
1714
                               (u'Mandos (%s) [%%(process)d]:'
1652
1720
                        u"interval": u"5m",
1653
1721
                        u"checker": u"fping -q -- %%(host)s",
1654
1722
                        u"host": u"",
1655
 
                        u"approved_delay": u"5m",
 
1723
                        u"approved_delay": u"0s",
1656
1724
                        u"approved_duration": u"1s",
1657
1725
                        }
1658
1726
    client_config = configparser.SafeConfigParser(client_defaults)
1665
1733
    tcp_server = MandosServer((server_settings[u"address"],
1666
1734
                               server_settings[u"port"]),
1667
1735
                              ClientHandler,
1668
 
                              interface=server_settings[u"interface"],
 
1736
                              interface=(server_settings[u"interface"]
 
1737
                                         or None),
1669
1738
                              use_ipv6=use_ipv6,
1670
1739
                              gnutls_priority=
1671
1740
                              server_settings[u"priority"],
1698
1767
            raise error
1699
1768
    
1700
1769
    # Enable all possible GnuTLS debugging
 
1770
 
 
1771
 
 
1772
    if not debug and not debuglevel:
 
1773
        syslogger.setLevel(logging.WARNING)
 
1774
        console.setLevel(logging.WARNING)
 
1775
    if debuglevel:
 
1776
        level = getattr(logging, debuglevel.upper())
 
1777
        syslogger.setLevel(level)
 
1778
        console.setLevel(level)
 
1779
 
1701
1780
    if debug:
1702
1781
        # "Use a log level over 10 to enable all debugging options."
1703
1782
        # - GnuTLS manual
1709
1788
        
1710
1789
        (gnutls.library.functions
1711
1790
         .gnutls_global_set_log_function(debug_gnutls))
 
1791
 
 
1792
        # Redirect stdin so all checkers get /dev/null
 
1793
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
 
1794
        os.dup2(null, sys.stdin.fileno())
 
1795
        if null > 2:
 
1796
            os.close(null)
 
1797
    else:
 
1798
        # No console logging
 
1799
        logger.removeHandler(console)
 
1800
 
1712
1801
    
1713
1802
    global main_loop
1714
1803
    # From the Avahi example code
1732
1821
    if server_settings["interface"]:
1733
1822
        service.interface = (if_nametoindex
1734
1823
                             (str(server_settings[u"interface"])))
 
1824
 
 
1825
    if not debug:
 
1826
        # Close all input and output, do double fork, etc.
 
1827
        daemon()
 
1828
        
 
1829
    global multiprocessing_manager
 
1830
    multiprocessing_manager = multiprocessing.Manager()
1735
1831
    
1736
1832
    client_class = Client
1737
1833
    if use_dbus:
1738
1834
        client_class = functools.partial(ClientDBus, bus = bus)
1739
1835
    def client_config_items(config, section):
1740
1836
        special_settings = {
1741
 
            "approve_by_default":
 
1837
            "approved_by_default":
1742
1838
                lambda: config.getboolean(section,
1743
 
                                          "approve_by_default"),
 
1839
                                          "approved_by_default"),
1744
1840
            }
1745
1841
        for name, value in config.items(section):
1746
1842
            try:
1747
 
                yield special_settings[name]()
 
1843
                yield (name, special_settings[name]())
1748
1844
            except KeyError:
1749
1845
                yield (name, value)
1750
1846
    
1755
1851
            for section in client_config.sections()))
1756
1852
    if not tcp_server.clients:
1757
1853
        logger.warning(u"No clients defined")
1758
 
    
1759
 
    if debug:
1760
 
        # Redirect stdin so all checkers get /dev/null
1761
 
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1762
 
        os.dup2(null, sys.stdin.fileno())
1763
 
        if null > 2:
1764
 
            os.close(null)
1765
 
    else:
1766
 
        # No console logging
1767
 
        logger.removeHandler(console)
1768
 
        # Close all input and output, do double fork, etc.
1769
 
        daemon()
1770
 
    
 
1854
        
1771
1855
    try:
1772
1856
        with pidfile:
1773
1857
            pid = os.getpid()