/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: Björn Påhlsson
  • Date: 2011-12-03 14:19:48 UTC
  • mfrom: (518.2.10 persistent-state-gpgme)
  • mto: This revision was merged to the branch mainline in revision 524.
  • Revision ID: belorn@recompile.se-20111203141948-jq8899pinnglhcpg
merge

Show diffs side-by-side

added added

removed removed

Lines of Context:
63
63
import cPickle as pickle
64
64
import multiprocessing
65
65
import types
 
66
import binascii
 
67
import tempfile
66
68
 
67
69
import dbus
68
70
import dbus.service
73
75
import ctypes.util
74
76
import xml.dom.minidom
75
77
import inspect
 
78
import GnuPGInterface
76
79
 
77
80
try:
78
81
    SO_BINDTODEVICE = socket.SO_BINDTODEVICE
83
86
        SO_BINDTODEVICE = None
84
87
 
85
88
 
86
 
version = "1.4.0"
 
89
version = "1.4.1"
 
90
stored_state_file = "clients.pickle"
87
91
 
88
 
#logger = logging.getLogger('mandos')
89
 
logger = logging.Logger('mandos')
 
92
logger = logging.getLogger()
90
93
syslogger = (logging.handlers.SysLogHandler
91
94
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
92
95
              address = str("/dev/log")))
93
 
syslogger.setFormatter(logging.Formatter
94
 
                       ('Mandos [%(process)d]: %(levelname)s:'
95
 
                        ' %(message)s'))
96
 
logger.addHandler(syslogger)
97
 
 
98
 
console = logging.StreamHandler()
99
 
console.setFormatter(logging.Formatter('%(name)s [%(process)d]:'
100
 
                                       ' %(levelname)s:'
101
 
                                       ' %(message)s'))
102
 
logger.addHandler(console)
 
96
 
 
97
try:
 
98
    if_nametoindex = (ctypes.cdll.LoadLibrary
 
99
                      (ctypes.util.find_library("c"))
 
100
                      .if_nametoindex)
 
101
except (OSError, AttributeError):
 
102
    def if_nametoindex(interface):
 
103
        "Get an interface index the hard way, i.e. using fcntl()"
 
104
        SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
 
105
        with contextlib.closing(socket.socket()) as s:
 
106
            ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
 
107
                                struct.pack(str("16s16x"),
 
108
                                            interface))
 
109
        interface_index = struct.unpack(str("I"),
 
110
                                        ifreq[16:20])[0]
 
111
        return interface_index
 
112
 
 
113
 
 
114
def initlogger(level=logging.WARNING):
 
115
    """init logger and add loglevel"""
 
116
    
 
117
    syslogger.setFormatter(logging.Formatter
 
118
                           ('Mandos [%(process)d]: %(levelname)s:'
 
119
                            ' %(message)s'))
 
120
    logger.addHandler(syslogger)
 
121
    
 
122
    console = logging.StreamHandler()
 
123
    console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
 
124
                                           ' [%(process)d]:'
 
125
                                           ' %(levelname)s:'
 
126
                                           ' %(message)s'))
 
127
    logger.addHandler(console)
 
128
    logger.setLevel(level)
 
129
 
 
130
 
 
131
class PGPError(Exception):
 
132
    """Exception if encryption/decryption fails"""
 
133
    pass
 
134
 
 
135
 
 
136
class PGPEngine(object):
 
137
    """A simple class for OpenPGP symmetric encryption & decryption"""
 
138
    def __init__(self):
 
139
        self.gnupg = GnuPGInterface.GnuPG()
 
140
        self.tempdir = tempfile.mkdtemp(prefix="mandos-")
 
141
        self.gnupg = GnuPGInterface.GnuPG()
 
142
        self.gnupg.options.meta_interactive = False
 
143
        self.gnupg.options.homedir = self.tempdir
 
144
        self.gnupg.options.extra_args.extend(['--force-mdc',
 
145
                                              '--quiet'])
 
146
    
 
147
    def __enter__(self):
 
148
        return self
 
149
    
 
150
    def __exit__ (self, exc_type, exc_value, traceback):
 
151
        self._cleanup()
 
152
        return False
 
153
    
 
154
    def __del__(self):
 
155
        self._cleanup()
 
156
    
 
157
    def _cleanup(self):
 
158
        if self.tempdir is not None:
 
159
            # Delete contents of tempdir
 
160
            for root, dirs, files in os.walk(self.tempdir,
 
161
                                             topdown = False):
 
162
                for filename in files:
 
163
                    os.remove(os.path.join(root, filename))
 
164
                for dirname in dirs:
 
165
                    os.rmdir(os.path.join(root, dirname))
 
166
            # Remove tempdir
 
167
            os.rmdir(self.tempdir)
 
168
            self.tempdir = None
 
169
    
 
170
    def password_encode(self, password):
 
171
        # Passphrase can not be empty and can not contain newlines or
 
172
        # NUL bytes.  So we prefix it and hex encode it.
 
173
        return b"mandos" + binascii.hexlify(password)
 
174
    
 
175
    def encrypt(self, data, password):
 
176
        self.gnupg.passphrase = self.password_encode(password)
 
177
        with open(os.devnull) as devnull:
 
178
            try:
 
179
                proc = self.gnupg.run(['--symmetric'],
 
180
                                      create_fhs=['stdin', 'stdout'],
 
181
                                      attach_fhs={'stderr': devnull})
 
182
                with contextlib.closing(proc.handles['stdin']) as f:
 
183
                    f.write(data)
 
184
                with contextlib.closing(proc.handles['stdout']) as f:
 
185
                    ciphertext = f.read()
 
186
                proc.wait()
 
187
            except IOError as e:
 
188
                raise PGPError(e)
 
189
        self.gnupg.passphrase = None
 
190
        return ciphertext
 
191
    
 
192
    def decrypt(self, data, password):
 
193
        self.gnupg.passphrase = self.password_encode(password)
 
194
        with open(os.devnull) as devnull:
 
195
            try:
 
196
                proc = self.gnupg.run(['--decrypt'],
 
197
                                      create_fhs=['stdin', 'stdout'],
 
198
                                      attach_fhs={'stderr': devnull})
 
199
                with contextlib.closing(proc.handles['stdin'] ) as f:
 
200
                    f.write(data)
 
201
                with contextlib.closing(proc.handles['stdout']) as f:
 
202
                    decrypted_plaintext = f.read()
 
203
                proc.wait()
 
204
            except IOError as e:
 
205
                raise PGPError(e)
 
206
        self.gnupg.passphrase = None
 
207
        return decrypted_plaintext
 
208
 
 
209
 
103
210
 
104
211
class AvahiError(Exception):
105
212
    def __init__(self, value, *args, **kwargs):
164
271
                            .GetAlternativeServiceName(self.name))
165
272
        logger.info("Changing Zeroconf service name to %r ...",
166
273
                    self.name)
167
 
        syslogger.setFormatter(logging.Formatter
168
 
                               ('Mandos (%s) [%%(process)d]:'
169
 
                                ' %%(levelname)s: %%(message)s'
170
 
                                % self.name))
171
274
        self.remove()
172
275
        try:
173
276
            self.add()
193
296
                avahi.DBUS_INTERFACE_ENTRY_GROUP)
194
297
        self.entry_group_state_changed_match = (
195
298
            self.group.connect_to_signal(
196
 
                'StateChanged', self .entry_group_state_changed))
 
299
                'StateChanged', self.entry_group_state_changed))
197
300
        logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
198
301
                     self.name, self.type)
199
302
        self.group.AddService(
225
328
            try:
226
329
                self.group.Free()
227
330
            except (dbus.exceptions.UnknownMethodException,
228
 
                    dbus.exceptions.DBusException) as e:
 
331
                    dbus.exceptions.DBusException):
229
332
                pass
230
333
            self.group = None
231
334
        self.remove()
265
368
                                 self.server_state_changed)
266
369
        self.server_state_changed(self.server.GetState())
267
370
 
 
371
class AvahiServiceToSyslog(AvahiService):
 
372
    def rename(self):
 
373
        """Add the new name to the syslog messages"""
 
374
        ret = AvahiService.rename(self)
 
375
        syslogger.setFormatter(logging.Formatter
 
376
                               ('Mandos (%s) [%%(process)d]:'
 
377
                                ' %%(levelname)s: %%(message)s'
 
378
                                % self.name))
 
379
        return ret
268
380
 
269
 
def _timedelta_to_milliseconds(td):
 
381
def timedelta_to_milliseconds(td):
270
382
    "Convert a datetime.timedelta() to milliseconds"
271
383
    return ((td.days * 24 * 60 * 60 * 1000)
272
384
            + (td.seconds * 1000)
276
388
    """A representation of a client host served by this server.
277
389
    
278
390
    Attributes:
279
 
    _approved:   bool(); 'None' if not yet approved/disapproved
 
391
    approved:   bool(); 'None' if not yet approved/disapproved
280
392
    approval_delay: datetime.timedelta(); Time to wait for approval
281
393
    approval_duration: datetime.timedelta(); Duration of one approval
282
394
    checker:    subprocess.Popen(); a running checker process used
289
401
                     instance %(name)s can be used in the command.
290
402
    checker_initiator_tag: a gobject event source tag, or None
291
403
    created:    datetime.datetime(); (UTC) object creation
 
404
    client_structure: Object describing what attributes a client has
 
405
                      and is used for storing the client at exit
292
406
    current_checker_command: string; current running checker_command
293
 
    disable_hook:  If set, called by disable() as disable_hook(self)
294
407
    disable_initiator_tag: a gobject event source tag, or None
295
408
    enabled:    bool()
296
409
    fingerprint: string (40 or 32 hexadecimal digits); used to
299
412
    interval:   datetime.timedelta(); How often to start a new checker
300
413
    last_approval_request: datetime.datetime(); (UTC) or None
301
414
    last_checked_ok: datetime.datetime(); (UTC) or None
302
 
    last_enabled: datetime.datetime(); (UTC)
 
415
 
 
416
    last_checker_status: integer between 0 and 255 reflecting exit
 
417
                         status of last checker. -1 reflects crashed
 
418
                         checker, or None.
 
419
    last_enabled: datetime.datetime(); (UTC) or None
303
420
    name:       string; from the config file, used in log messages and
304
421
                        D-Bus identifiers
305
422
    secret:     bytestring; sent verbatim (over TLS) to client
318
435
    
319
436
    def timeout_milliseconds(self):
320
437
        "Return the 'timeout' attribute in milliseconds"
321
 
        return _timedelta_to_milliseconds(self.timeout)
 
438
        return timedelta_to_milliseconds(self.timeout)
322
439
    
323
440
    def extended_timeout_milliseconds(self):
324
441
        "Return the 'extended_timeout' attribute in milliseconds"
325
 
        return _timedelta_to_milliseconds(self.extended_timeout)
 
442
        return timedelta_to_milliseconds(self.extended_timeout)
326
443
    
327
444
    def interval_milliseconds(self):
328
445
        "Return the 'interval' attribute in milliseconds"
329
 
        return _timedelta_to_milliseconds(self.interval)
 
446
        return timedelta_to_milliseconds(self.interval)
330
447
    
331
448
    def approval_delay_milliseconds(self):
332
 
        return _timedelta_to_milliseconds(self.approval_delay)
 
449
        return timedelta_to_milliseconds(self.approval_delay)
333
450
    
334
 
    def __init__(self, name = None, disable_hook=None, config=None):
 
451
    def __init__(self, name = None, config=None):
335
452
        """Note: the 'checker' key in 'config' sets the
336
453
        'checker_command' attribute and *not* the 'checker'
337
454
        attribute."""
357
474
                            % self.name)
358
475
        self.host = config.get("host", "")
359
476
        self.created = datetime.datetime.utcnow()
360
 
        self.enabled = False
 
477
        self.enabled = config.get("enabled", True)
361
478
        self.last_approval_request = None
362
 
        self.last_enabled = None
 
479
        if self.enabled:
 
480
            self.last_enabled = datetime.datetime.utcnow()
 
481
        else:
 
482
            self.last_enabled = None
363
483
        self.last_checked_ok = None
 
484
        self.last_checker_status = None
364
485
        self.timeout = string_to_delta(config["timeout"])
365
486
        self.extended_timeout = string_to_delta(config
366
487
                                                ["extended_timeout"])
367
488
        self.interval = string_to_delta(config["interval"])
368
 
        self.disable_hook = disable_hook
369
489
        self.checker = None
370
490
        self.checker_initiator_tag = None
371
491
        self.disable_initiator_tag = None
372
 
        self.expires = None
 
492
        if self.enabled:
 
493
            self.expires = datetime.datetime.utcnow() + self.timeout
 
494
        else:
 
495
            self.expires = None
373
496
        self.checker_callback_tag = None
374
497
        self.checker_command = config["checker"]
375
498
        self.current_checker_command = None
376
 
        self.last_connect = None
377
 
        self._approved = None
 
499
        self.approved = None
378
500
        self.approved_by_default = config.get("approved_by_default",
379
501
                                              True)
380
502
        self.approvals_pending = 0
385
507
        self.changedstate = (multiprocessing_manager
386
508
                             .Condition(multiprocessing_manager
387
509
                                        .Lock()))
 
510
        self.client_structure = [attr for attr in
 
511
                                 self.__dict__.iterkeys()
 
512
                                 if not attr.startswith("_")]
 
513
        self.client_structure.append("client_structure")
 
514
        
 
515
        for name, t in inspect.getmembers(type(self),
 
516
                                          lambda obj:
 
517
                                              isinstance(obj,
 
518
                                                         property)):
 
519
            if not name.startswith("_"):
 
520
                self.client_structure.append(name)
388
521
    
 
522
    # Send notice to process children that client state has changed
389
523
    def send_changedstate(self):
390
 
        self.changedstate.acquire()
391
 
        self.changedstate.notify_all()
392
 
        self.changedstate.release()
 
524
        with self.changedstate:
 
525
            self.changedstate.notify_all()
393
526
    
394
527
    def enable(self):
395
528
        """Start this client's checker and timeout hooks"""
397
530
            # Already enabled
398
531
            return
399
532
        self.send_changedstate()
400
 
        # Schedule a new checker to be started an 'interval' from now,
401
 
        # and every interval from then on.
402
 
        self.checker_initiator_tag = (gobject.timeout_add
403
 
                                      (self.interval_milliseconds(),
404
 
                                       self.start_checker))
405
 
        # Schedule a disable() when 'timeout' has passed
406
533
        self.expires = datetime.datetime.utcnow() + self.timeout
407
 
        self.disable_initiator_tag = (gobject.timeout_add
408
 
                                   (self.timeout_milliseconds(),
409
 
                                    self.disable))
410
534
        self.enabled = True
411
535
        self.last_enabled = datetime.datetime.utcnow()
412
 
        # Also start a new checker *right now*.
413
 
        self.start_checker()
 
536
        self.init_checker()
414
537
    
415
538
    def disable(self, quiet=True):
416
539
        """Disable this client."""
428
551
            gobject.source_remove(self.checker_initiator_tag)
429
552
            self.checker_initiator_tag = None
430
553
        self.stop_checker()
431
 
        if self.disable_hook:
432
 
            self.disable_hook(self)
433
554
        self.enabled = False
434
555
        # Do not run this again if called by a gobject.timeout_add
435
556
        return False
436
557
    
437
558
    def __del__(self):
438
 
        self.disable_hook = None
439
559
        self.disable()
440
560
    
 
561
    def init_checker(self):
 
562
        # Schedule a new checker to be started an 'interval' from now,
 
563
        # and every interval from then on.
 
564
        self.checker_initiator_tag = (gobject.timeout_add
 
565
                                      (self.interval_milliseconds(),
 
566
                                       self.start_checker))
 
567
        # Schedule a disable() when 'timeout' has passed
 
568
        self.disable_initiator_tag = (gobject.timeout_add
 
569
                                   (self.timeout_milliseconds(),
 
570
                                    self.disable))
 
571
        # Also start a new checker *right now*.
 
572
        self.start_checker()
 
573
    
441
574
    def checker_callback(self, pid, condition, command):
442
575
        """The checker has completed, so take appropriate actions."""
443
576
        self.checker_callback_tag = None
444
577
        self.checker = None
445
578
        if os.WIFEXITED(condition):
446
 
            exitstatus = os.WEXITSTATUS(condition)
447
 
            if exitstatus == 0:
 
579
            self.last_checker_status = os.WEXITSTATUS(condition)
 
580
            if self.last_checker_status == 0:
448
581
                logger.info("Checker for %(name)s succeeded",
449
582
                            vars(self))
450
583
                self.checked_ok()
452
585
                logger.info("Checker for %(name)s failed",
453
586
                            vars(self))
454
587
        else:
 
588
            self.last_checker_status = -1
455
589
            logger.warning("Checker for %(name)s crashed?",
456
590
                           vars(self))
457
591
    
464
598
        if timeout is None:
465
599
            timeout = self.timeout
466
600
        self.last_checked_ok = datetime.datetime.utcnow()
467
 
        gobject.source_remove(self.disable_initiator_tag)
468
 
        self.disable_initiator_tag = (gobject.timeout_add
469
 
                                      (_timedelta_to_milliseconds
470
 
                                       (timeout), self.disable))
471
 
        self.expires = datetime.datetime.utcnow() + timeout
 
601
        if self.disable_initiator_tag is not None:
 
602
            gobject.source_remove(self.disable_initiator_tag)
 
603
        if getattr(self, "enabled", False):
 
604
            self.disable_initiator_tag = (gobject.timeout_add
 
605
                                          (timedelta_to_milliseconds
 
606
                                           (timeout), self.disable))
 
607
            self.expires = datetime.datetime.utcnow() + timeout
472
608
    
473
609
    def need_approval(self):
474
610
        self.last_approval_request = datetime.datetime.utcnow()
689
825
        
690
826
        Note: Will not include properties with access="write".
691
827
        """
692
 
        all = {}
 
828
        properties = {}
693
829
        for name, prop in self._get_all_dbus_properties():
694
830
            if (interface_name
695
831
                and interface_name != prop._dbus_interface):
700
836
                continue
701
837
            value = prop()
702
838
            if not hasattr(value, "variant_level"):
703
 
                all[name] = value
 
839
                properties[name] = value
704
840
                continue
705
 
            all[name] = type(value)(value, variant_level=
706
 
                                    value.variant_level+1)
707
 
        return dbus.Dictionary(all, signature="sv")
 
841
            properties[name] = type(value)(value, variant_level=
 
842
                                           value.variant_level+1)
 
843
        return dbus.Dictionary(properties, signature="sv")
708
844
    
709
845
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
710
846
                         out_signature="s",
761
897
    return dbus.String(dt.isoformat(),
762
898
                       variant_level=variant_level)
763
899
 
 
900
 
764
901
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
765
902
                                  .__metaclass__):
766
903
    """Applied to an empty subclass of a D-Bus object, this metaclass
858
995
                                        attribute.func_closure)))
859
996
        return type.__new__(mcs, name, bases, attr)
860
997
 
 
998
 
861
999
class ClientDBus(Client, DBusObjectWithProperties):
862
1000
    """A Client class using D-Bus
863
1001
    
872
1010
    # dbus.service.Object doesn't use super(), so we can't either.
873
1011
    
874
1012
    def __init__(self, bus = None, *args, **kwargs):
 
1013
        self.bus = bus
 
1014
        Client.__init__(self, *args, **kwargs)
 
1015
        
875
1016
        self._approvals_pending = 0
876
 
        self.bus = bus
877
 
        Client.__init__(self, *args, **kwargs)
878
1017
        # Only now, when this client is initialized, can it show up on
879
1018
        # the D-Bus
880
1019
        client_object_name = unicode(self.name).translate(
890
1029
                             variant_level=1):
891
1030
        """ Modify a variable so that it's a property which announces
892
1031
        its changes to DBus.
893
 
 
 
1032
        
894
1033
        transform_fun: Function that takes a value and a variant_level
895
1034
                       and transforms it to a D-Bus type.
896
1035
        dbus_name: D-Bus name of the variable
930
1069
        datetime_to_dbus, "LastApprovalRequest")
931
1070
    approved_by_default = notifychangeproperty(dbus.Boolean,
932
1071
                                               "ApprovedByDefault")
933
 
    approval_delay = notifychangeproperty(dbus.UInt16,
 
1072
    approval_delay = notifychangeproperty(dbus.UInt64,
934
1073
                                          "ApprovalDelay",
935
1074
                                          type_func =
936
 
                                          _timedelta_to_milliseconds)
 
1075
                                          timedelta_to_milliseconds)
937
1076
    approval_duration = notifychangeproperty(
938
 
        dbus.UInt16, "ApprovalDuration",
939
 
        type_func = _timedelta_to_milliseconds)
 
1077
        dbus.UInt64, "ApprovalDuration",
 
1078
        type_func = timedelta_to_milliseconds)
940
1079
    host = notifychangeproperty(dbus.String, "Host")
941
 
    timeout = notifychangeproperty(dbus.UInt16, "Timeout",
 
1080
    timeout = notifychangeproperty(dbus.UInt64, "Timeout",
942
1081
                                   type_func =
943
 
                                   _timedelta_to_milliseconds)
 
1082
                                   timedelta_to_milliseconds)
944
1083
    extended_timeout = notifychangeproperty(
945
 
        dbus.UInt16, "ExtendedTimeout",
946
 
        type_func = _timedelta_to_milliseconds)
947
 
    interval = notifychangeproperty(dbus.UInt16,
 
1084
        dbus.UInt64, "ExtendedTimeout",
 
1085
        type_func = timedelta_to_milliseconds)
 
1086
    interval = notifychangeproperty(dbus.UInt64,
948
1087
                                    "Interval",
949
1088
                                    type_func =
950
 
                                    _timedelta_to_milliseconds)
 
1089
                                    timedelta_to_milliseconds)
951
1090
    checker_command = notifychangeproperty(dbus.String, "Checker")
952
1091
    
953
1092
    del notifychangeproperty
995
1134
        return r
996
1135
    
997
1136
    def _reset_approved(self):
998
 
        self._approved = None
 
1137
        self.approved = None
999
1138
        return False
1000
1139
    
1001
1140
    def approve(self, value=True):
1002
1141
        self.send_changedstate()
1003
 
        self._approved = value
1004
 
        gobject.timeout_add(_timedelta_to_milliseconds
 
1142
        self.approved = value
 
1143
        gobject.timeout_add(timedelta_to_milliseconds
1005
1144
                            (self.approval_duration),
1006
1145
                            self._reset_approved)
1007
1146
    
1050
1189
        "D-Bus signal"
1051
1190
        return self.need_approval()
1052
1191
    
 
1192
    # NeRwequest - signal
 
1193
    @dbus.service.signal(_interface, signature="s")
 
1194
    def NewRequest(self, ip):
 
1195
        """D-Bus signal
 
1196
        Is sent after a client request a password.
 
1197
        """
 
1198
        pass
 
1199
    
1053
1200
    ## Methods
1054
1201
    
1055
1202
    # Approve - method
1113
1260
                           access="readwrite")
1114
1261
    def ApprovalDuration_dbus_property(self, value=None):
1115
1262
        if value is None:       # get
1116
 
            return dbus.UInt64(_timedelta_to_milliseconds(
 
1263
            return dbus.UInt64(timedelta_to_milliseconds(
1117
1264
                    self.approval_duration))
1118
1265
        self.approval_duration = datetime.timedelta(0, 0, 0, value)
1119
1266
    
1133
1280
    def Host_dbus_property(self, value=None):
1134
1281
        if value is None:       # get
1135
1282
            return dbus.String(self.host)
1136
 
        self.host = value
 
1283
        self.host = unicode(value)
1137
1284
    
1138
1285
    # Created - property
1139
1286
    @dbus_service_property(_interface, signature="s", access="read")
1140
1287
    def Created_dbus_property(self):
1141
 
        return dbus.String(datetime_to_dbus(self.created))
 
1288
        return datetime_to_dbus(self.created)
1142
1289
    
1143
1290
    # LastEnabled - property
1144
1291
    @dbus_service_property(_interface, signature="s", access="read")
1188
1335
        gobject.source_remove(self.disable_initiator_tag)
1189
1336
        self.disable_initiator_tag = None
1190
1337
        self.expires = None
1191
 
        time_to_die = _timedelta_to_milliseconds((self
1192
 
                                                  .last_checked_ok
1193
 
                                                  + self.timeout)
1194
 
                                                 - datetime.datetime
1195
 
                                                 .utcnow())
 
1338
        time_to_die = timedelta_to_milliseconds((self
 
1339
                                                 .last_checked_ok
 
1340
                                                 + self.timeout)
 
1341
                                                - datetime.datetime
 
1342
                                                .utcnow())
1196
1343
        if time_to_die <= 0:
1197
1344
            # The timeout has passed
1198
1345
            self.disable()
1220
1367
        self.interval = datetime.timedelta(0, 0, 0, value)
1221
1368
        if getattr(self, "checker_initiator_tag", None) is None:
1222
1369
            return
1223
 
        # Reschedule checker run
1224
 
        gobject.source_remove(self.checker_initiator_tag)
1225
 
        self.checker_initiator_tag = (gobject.timeout_add
1226
 
                                      (value, self.start_checker))
1227
 
        self.start_checker()    # Start one now, too
 
1370
        if self.enabled:
 
1371
            # Reschedule checker run
 
1372
            gobject.source_remove(self.checker_initiator_tag)
 
1373
            self.checker_initiator_tag = (gobject.timeout_add
 
1374
                                          (value, self.start_checker))
 
1375
            self.start_checker()    # Start one now, too
1228
1376
    
1229
1377
    # Checker - property
1230
1378
    @dbus_service_property(_interface, signature="s",
1232
1380
    def Checker_dbus_property(self, value=None):
1233
1381
        if value is None:       # get
1234
1382
            return dbus.String(self.checker_command)
1235
 
        self.checker_command = value
 
1383
        self.checker_command = unicode(value)
1236
1384
    
1237
1385
    # CheckerRunning - property
1238
1386
    @dbus_service_property(_interface, signature="b",
1267
1415
            raise KeyError()
1268
1416
    
1269
1417
    def __getattribute__(self, name):
1270
 
        if(name == '_pipe'):
 
1418
        if name == '_pipe':
1271
1419
            return super(ProxyClient, self).__getattribute__(name)
1272
1420
        self._pipe.send(('getattr', name))
1273
1421
        data = self._pipe.recv()
1280
1428
            return func
1281
1429
    
1282
1430
    def __setattr__(self, name, value):
1283
 
        if(name == '_pipe'):
 
1431
        if name == '_pipe':
1284
1432
            return super(ProxyClient, self).__setattr__(name, value)
1285
1433
        self._pipe.send(('setattr', name, value))
1286
1434
 
 
1435
 
1287
1436
class ClientDBusTransitional(ClientDBus):
1288
1437
    __metaclass__ = AlternateDBusNamesMetaclass
1289
1438
 
 
1439
 
1290
1440
class ClientHandler(socketserver.BaseRequestHandler, object):
1291
1441
    """A class to handle client connections.
1292
1442
    
1360
1510
                except KeyError:
1361
1511
                    return
1362
1512
                
 
1513
                if self.server.use_dbus:
 
1514
                    # Emit D-Bus signal
 
1515
                    client.NewRequest(str(self.client_address))
 
1516
                
1363
1517
                if client.approval_delay:
1364
1518
                    delay = client.approval_delay
1365
1519
                    client.approvals_pending += 1
1374
1528
                            client.Rejected("Disabled")
1375
1529
                        return
1376
1530
                    
1377
 
                    if client._approved or not client.approval_delay:
 
1531
                    if client.approved or not client.approval_delay:
1378
1532
                        #We are approved or approval is disabled
1379
1533
                        break
1380
 
                    elif client._approved is None:
 
1534
                    elif client.approved is None:
1381
1535
                        logger.info("Client %s needs approval",
1382
1536
                                    client.name)
1383
1537
                        if self.server.use_dbus:
1397
1551
                    time = datetime.datetime.now()
1398
1552
                    client.changedstate.acquire()
1399
1553
                    (client.changedstate.wait
1400
 
                     (float(client._timedelta_to_milliseconds(delay)
 
1554
                     (float(client.timedelta_to_milliseconds(delay)
1401
1555
                            / 1000)))
1402
1556
                    client.changedstate.release()
1403
1557
                    time2 = datetime.datetime.now()
1502
1656
        # Convert the buffer to a Python bytestring
1503
1657
        fpr = ctypes.string_at(buf, buf_len.value)
1504
1658
        # Convert the bytestring to hexadecimal notation
1505
 
        hex_fpr = ''.join("%02X" % ord(char) for char in fpr)
 
1659
        hex_fpr = binascii.hexlify(fpr).upper()
1506
1660
        return hex_fpr
1507
1661
 
1508
1662
 
1622
1776
        self.enabled = False
1623
1777
        self.clients = clients
1624
1778
        if self.clients is None:
1625
 
            self.clients = set()
 
1779
            self.clients = {}
1626
1780
        self.use_dbus = use_dbus
1627
1781
        self.gnutls_priority = gnutls_priority
1628
1782
        IPv6_TCPServer.__init__(self, server_address,
1675
1829
            fpr = request[1]
1676
1830
            address = request[2]
1677
1831
            
1678
 
            for c in self.clients:
 
1832
            for c in self.clients.itervalues():
1679
1833
                if c.fingerprint == fpr:
1680
1834
                    client = c
1681
1835
                    break
1765
1919
    return timevalue
1766
1920
 
1767
1921
 
1768
 
def if_nametoindex(interface):
1769
 
    """Call the C function if_nametoindex(), or equivalent
1770
 
    
1771
 
    Note: This function cannot accept a unicode string."""
1772
 
    global if_nametoindex
1773
 
    try:
1774
 
        if_nametoindex = (ctypes.cdll.LoadLibrary
1775
 
                          (ctypes.util.find_library("c"))
1776
 
                          .if_nametoindex)
1777
 
    except (OSError, AttributeError):
1778
 
        logger.warning("Doing if_nametoindex the hard way")
1779
 
        def if_nametoindex(interface):
1780
 
            "Get an interface index the hard way, i.e. using fcntl()"
1781
 
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
1782
 
            with contextlib.closing(socket.socket()) as s:
1783
 
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1784
 
                                    struct.pack(str("16s16x"),
1785
 
                                                interface))
1786
 
            interface_index = struct.unpack(str("I"),
1787
 
                                            ifreq[16:20])[0]
1788
 
            return interface_index
1789
 
    return if_nametoindex(interface)
1790
 
 
1791
 
 
1792
1922
def daemon(nochdir = False, noclose = False):
1793
1923
    """See daemon(3).  Standard BSD Unix function.
1794
1924
    
1849
1979
                        " system bus interface")
1850
1980
    parser.add_argument("--no-ipv6", action="store_false",
1851
1981
                        dest="use_ipv6", help="Do not use IPv6")
 
1982
    parser.add_argument("--no-restore", action="store_false",
 
1983
                        dest="restore", help="Do not restore stored"
 
1984
                        " state")
 
1985
    parser.add_argument("--statedir", metavar="DIR",
 
1986
                        help="Directory to save/restore state in")
 
1987
    
1852
1988
    options = parser.parse_args()
1853
1989
    
1854
1990
    if options.check:
1867
2003
                        "use_dbus": "True",
1868
2004
                        "use_ipv6": "True",
1869
2005
                        "debuglevel": "",
 
2006
                        "restore": "True",
 
2007
                        "statedir": "/var/lib/mandos"
1870
2008
                        }
1871
2009
    
1872
2010
    # Parse config file for server-global settings
1889
2027
    # options, if set.
1890
2028
    for option in ("interface", "address", "port", "debug",
1891
2029
                   "priority", "servicename", "configdir",
1892
 
                   "use_dbus", "use_ipv6", "debuglevel"):
 
2030
                   "use_dbus", "use_ipv6", "debuglevel", "restore",
 
2031
                   "statedir"):
1893
2032
        value = getattr(options, option)
1894
2033
        if value is not None:
1895
2034
            server_settings[option] = value
1907
2046
    debuglevel = server_settings["debuglevel"]
1908
2047
    use_dbus = server_settings["use_dbus"]
1909
2048
    use_ipv6 = server_settings["use_ipv6"]
 
2049
    stored_state_path = os.path.join(server_settings["statedir"],
 
2050
                                     stored_state_file)
 
2051
    
 
2052
    if debug:
 
2053
        initlogger(logging.DEBUG)
 
2054
    else:
 
2055
        if not debuglevel:
 
2056
            initlogger()
 
2057
        else:
 
2058
            level = getattr(logging, debuglevel.upper())
 
2059
            initlogger(level)
1910
2060
    
1911
2061
    if server_settings["servicename"] != "Mandos":
1912
2062
        syslogger.setFormatter(logging.Formatter
1967
2117
        if error[0] != errno.EPERM:
1968
2118
            raise error
1969
2119
    
1970
 
    if not debug and not debuglevel:
1971
 
        syslogger.setLevel(logging.WARNING)
1972
 
        console.setLevel(logging.WARNING)
1973
 
    if debuglevel:
1974
 
        level = getattr(logging, debuglevel.upper())
1975
 
        syslogger.setLevel(level)
1976
 
        console.setLevel(level)
1977
 
    
1978
2120
    if debug:
1979
2121
        # Enable all possible GnuTLS debugging
1980
2122
        
2022
2164
            server_settings["use_dbus"] = False
2023
2165
            tcp_server.use_dbus = False
2024
2166
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2025
 
    service = AvahiService(name = server_settings["servicename"],
2026
 
                           servicetype = "_mandos._tcp",
2027
 
                           protocol = protocol, bus = bus)
 
2167
    service = AvahiServiceToSyslog(name =
 
2168
                                   server_settings["servicename"],
 
2169
                                   servicetype = "_mandos._tcp",
 
2170
                                   protocol = protocol, bus = bus)
2028
2171
    if server_settings["interface"]:
2029
2172
        service.interface = (if_nametoindex
2030
2173
                             (str(server_settings["interface"])))
2036
2179
    if use_dbus:
2037
2180
        client_class = functools.partial(ClientDBusTransitional,
2038
2181
                                         bus = bus)
2039
 
    def client_config_items(config, section):
2040
 
        special_settings = {
2041
 
            "approved_by_default":
2042
 
                lambda: config.getboolean(section,
2043
 
                                          "approved_by_default"),
2044
 
            }
2045
 
        for name, value in config.items(section):
 
2182
    
 
2183
    special_settings = {
 
2184
        # Some settings need to be accessd by special methods;
 
2185
        # booleans need .getboolean(), etc.  Here is a list of them:
 
2186
        "approved_by_default":
 
2187
            lambda section:
 
2188
            client_config.getboolean(section, "approved_by_default"),
 
2189
        "enabled":
 
2190
            lambda section:
 
2191
            client_config.getboolean(section, "enabled"),
 
2192
        }
 
2193
    # Construct a new dict of client settings of this form:
 
2194
    # { client_name: {setting_name: value, ...}, ...}
 
2195
    # with exceptions for any special settings as defined above
 
2196
    client_settings = dict((clientname,
 
2197
                           dict((setting,
 
2198
                                 (value
 
2199
                                  if setting not in special_settings
 
2200
                                  else special_settings[setting]
 
2201
                                  (clientname)))
 
2202
                                for setting, value in
 
2203
                                client_config.items(clientname)))
 
2204
                          for clientname in client_config.sections())
 
2205
    
 
2206
    old_client_settings = {}
 
2207
    clients_data = []
 
2208
    
 
2209
    # Get client data and settings from last running state.
 
2210
    if server_settings["restore"]:
 
2211
        try:
 
2212
            with open(stored_state_path, "rb") as stored_state:
 
2213
                clients_data, old_client_settings = (pickle.load
 
2214
                                                     (stored_state))
 
2215
            os.remove(stored_state_path)
 
2216
        except IOError as e:
 
2217
            logger.warning("Could not load persistent state: {0}"
 
2218
                           .format(e))
 
2219
            if e.errno != errno.ENOENT:
 
2220
                raise
 
2221
    
 
2222
    with PGPEngine() as pgp:
 
2223
        for client in clients_data:
 
2224
            client_name = client["name"]
 
2225
            
 
2226
            # Decide which value to use after restoring saved state.
 
2227
            # We have three different values: Old config file,
 
2228
            # new config file, and saved state.
 
2229
            # New config value takes precedence if it differs from old
 
2230
            # config value, otherwise use saved state.
 
2231
            for name, value in client_settings[client_name].items():
 
2232
                try:
 
2233
                    # For each value in new config, check if it
 
2234
                    # differs from the old config value (Except for
 
2235
                    # the "secret" attribute)
 
2236
                    if (name != "secret" and
 
2237
                        value != old_client_settings[client_name]
 
2238
                        [name]):
 
2239
                        client[name] = value
 
2240
                except KeyError:
 
2241
                    pass
 
2242
            
 
2243
            # Clients who has passed its expire date can still be
 
2244
            # enabled if its last checker was sucessful.  Clients
 
2245
            # whose checker failed before we stored its state is
 
2246
            # assumed to have failed all checkers during downtime.
 
2247
            if client["enabled"]:
 
2248
                if client["expires"] <= (datetime.datetime
 
2249
                                         .utcnow()):
 
2250
                    # Client has expired
 
2251
                    if client["last_checker_status"] != 0:
 
2252
                        client["enabled"] = False
 
2253
                    else:
 
2254
                        client["expires"] = (datetime.datetime
 
2255
                                             .utcnow()
 
2256
                                             + client["timeout"])
 
2257
            
 
2258
            client["changedstate"] = (multiprocessing_manager
 
2259
                                      .Condition
 
2260
                                      (multiprocessing_manager
 
2261
                                       .Lock()))
 
2262
            if use_dbus:
 
2263
                new_client = (ClientDBusTransitional.__new__
 
2264
                              (ClientDBusTransitional))
 
2265
                tcp_server.clients[client_name] = new_client
 
2266
                new_client.bus = bus
 
2267
                for name, value in client.iteritems():
 
2268
                    setattr(new_client, name, value)
 
2269
                client_object_name = unicode(client_name).translate(
 
2270
                    {ord("."): ord("_"),
 
2271
                     ord("-"): ord("_")})
 
2272
                new_client.dbus_object_path = (dbus.ObjectPath
 
2273
                                               ("/clients/"
 
2274
                                                + client_object_name))
 
2275
                DBusObjectWithProperties.__init__(new_client,
 
2276
                                                  new_client.bus,
 
2277
                                                  new_client
 
2278
                                                  .dbus_object_path)
 
2279
            else:
 
2280
                tcp_server.clients[client_name] = (Client.__new__
 
2281
                                                   (Client))
 
2282
                for name, value in client.iteritems():
 
2283
                    setattr(tcp_server.clients[client_name],
 
2284
                            name, value)
 
2285
            
2046
2286
            try:
2047
 
                yield (name, special_settings[name]())
2048
 
            except KeyError:
2049
 
                yield (name, value)
2050
 
    
2051
 
    tcp_server.clients.update(set(
2052
 
            client_class(name = section,
2053
 
                         config= dict(client_config_items(
2054
 
                        client_config, section)))
2055
 
            for section in client_config.sections()))
 
2287
                tcp_server.clients[client_name].secret = (
 
2288
                    pgp.decrypt(tcp_server.clients[client_name]
 
2289
                                .encrypted_secret,
 
2290
                                client_settings[client_name]
 
2291
                                ["secret"]))
 
2292
            except PGPError:
 
2293
                # If decryption fails, we use secret from new settings
 
2294
                logger.debug("Failed to decrypt {0} old secret"
 
2295
                             .format(client_name))
 
2296
                tcp_server.clients[client_name].secret = (
 
2297
                    client_settings[client_name]["secret"])
 
2298
    
 
2299
    # Create/remove clients based on new changes made to config
 
2300
    for clientname in set(old_client_settings) - set(client_settings):
 
2301
        del tcp_server.clients[clientname]
 
2302
    for clientname in set(client_settings) - set(old_client_settings):
 
2303
        tcp_server.clients[clientname] = (client_class(name
 
2304
                                                       = clientname,
 
2305
                                                       config =
 
2306
                                                       client_settings
 
2307
                                                       [clientname]))
 
2308
    
2056
2309
    if not tcp_server.clients:
2057
2310
        logger.warning("No clients defined")
2058
2311
        
2101
2354
            def GetAllClients(self):
2102
2355
                "D-Bus method"
2103
2356
                return dbus.Array(c.dbus_object_path
2104
 
                                  for c in tcp_server.clients)
 
2357
                                  for c in
 
2358
                                  tcp_server.clients.itervalues())
2105
2359
            
2106
2360
            @dbus.service.method(_interface,
2107
2361
                                 out_signature="a{oa{sv}}")
2109
2363
                "D-Bus method"
2110
2364
                return dbus.Dictionary(
2111
2365
                    ((c.dbus_object_path, c.GetAll(""))
2112
 
                     for c in tcp_server.clients),
 
2366
                     for c in tcp_server.clients.itervalues()),
2113
2367
                    signature="oa{sv}")
2114
2368
            
2115
2369
            @dbus.service.method(_interface, in_signature="o")
2116
2370
            def RemoveClient(self, object_path):
2117
2371
                "D-Bus method"
2118
 
                for c in tcp_server.clients:
 
2372
                for c in tcp_server.clients.itervalues():
2119
2373
                    if c.dbus_object_path == object_path:
2120
 
                        tcp_server.clients.remove(c)
 
2374
                        del tcp_server.clients[c.name]
2121
2375
                        c.remove_from_connection()
2122
2376
                        # Don't signal anything except ClientRemoved
2123
2377
                        c.disable(quiet=True)
2137
2391
        service.cleanup()
2138
2392
        
2139
2393
        multiprocessing.active_children()
 
2394
        if not (tcp_server.clients or client_settings):
 
2395
            return
 
2396
        
 
2397
        # Store client before exiting. Secrets are encrypted with key
 
2398
        # based on what config file has. If config file is
 
2399
        # removed/edited, old secret will thus be unrecovable.
 
2400
        clients = []
 
2401
        with PGPEngine() as pgp:
 
2402
            for client in tcp_server.clients.itervalues():
 
2403
                key = client_settings[client.name]["secret"]
 
2404
                client.encrypted_secret = pgp.encrypt(client.secret,
 
2405
                                                      key)
 
2406
                client_dict = {}
 
2407
                
 
2408
                # A list of attributes that will not be stored when
 
2409
                # shutting down.
 
2410
                exclude = set(("bus", "changedstate", "secret"))
 
2411
                for name, typ in (inspect.getmembers
 
2412
                                  (dbus.service.Object)):
 
2413
                    exclude.add(name)
 
2414
                
 
2415
                client_dict["encrypted_secret"] = (client
 
2416
                                                   .encrypted_secret)
 
2417
                for attr in client.client_structure:
 
2418
                    if attr not in exclude:
 
2419
                        client_dict[attr] = getattr(client, attr)
 
2420
                
 
2421
                clients.append(client_dict)
 
2422
                del client_settings[client.name]["secret"]
 
2423
        
 
2424
        try:
 
2425
            with os.fdopen(os.open(stored_state_path,
 
2426
                                   os.O_CREAT|os.O_WRONLY|os.O_TRUNC,
 
2427
                                   0600), "wb") as stored_state:
 
2428
                pickle.dump((clients, client_settings), stored_state)
 
2429
        except (IOError, OSError) as e:
 
2430
            logger.warning("Could not save persistent state: {0}"
 
2431
                           .format(e))
 
2432
            if e.errno not in (errno.ENOENT, errno.EACCES):
 
2433
                raise
 
2434
        
 
2435
        # Delete all clients, and settings from config
2140
2436
        while tcp_server.clients:
2141
 
            client = tcp_server.clients.pop()
 
2437
            name, client = tcp_server.clients.popitem()
2142
2438
            if use_dbus:
2143
2439
                client.remove_from_connection()
2144
 
            client.disable_hook = None
2145
2440
            # Don't signal anything except ClientRemoved
2146
2441
            client.disable(quiet=True)
2147
2442
            if use_dbus:
2149
2444
                mandos_dbus_service.ClientRemoved(client
2150
2445
                                                  .dbus_object_path,
2151
2446
                                                  client.name)
 
2447
        client_settings.clear()
2152
2448
    
2153
2449
    atexit.register(cleanup)
2154
2450
    
2155
 
    for client in tcp_server.clients:
 
2451
    for client in tcp_server.clients.itervalues():
2156
2452
        if use_dbus:
2157
2453
            # Emit D-Bus signal
2158
2454
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
2159
 
        client.enable()
 
2455
        # Need to initiate checking of clients
 
2456
        if client.enabled:
 
2457
            client.init_checker()
2160
2458
    
2161
2459
    tcp_server.enable()
2162
2460
    tcp_server.server_activate()