/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: 2011-10-22 00:47:58 UTC
  • mfrom: (505.1.20 teddy)
  • Revision ID: teddy@recompile.se-20111022004758-cypxz7o293e6rvc4
Clean up logging.

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
68
66
 
69
67
import dbus
70
68
import dbus.service
75
73
import ctypes.util
76
74
import xml.dom.minidom
77
75
import inspect
78
 
import GnuPGInterface
79
76
 
80
77
try:
81
78
    SO_BINDTODEVICE = socket.SO_BINDTODEVICE
85
82
    except ImportError:
86
83
        SO_BINDTODEVICE = None
87
84
 
 
85
 
88
86
version = "1.4.1"
89
 
stored_state_file = "clients.pickle"
90
87
 
91
88
logger = logging.getLogger()
92
89
syslogger = (logging.handlers.SysLogHandler
93
90
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
94
91
              address = str("/dev/log")))
95
 
 
96
 
try:
97
 
    if_nametoindex = (ctypes.cdll.LoadLibrary
98
 
                      (ctypes.util.find_library("c"))
99
 
                      .if_nametoindex)
100
 
except (OSError, AttributeError):
101
 
    def if_nametoindex(interface):
102
 
        "Get an interface index the hard way, i.e. using fcntl()"
103
 
        SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
104
 
        with contextlib.closing(socket.socket()) as s:
105
 
            ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
106
 
                                struct.pack(str("16s16x"),
107
 
                                            interface))
108
 
        interface_index = struct.unpack(str("I"),
109
 
                                        ifreq[16:20])[0]
110
 
        return interface_index
111
 
 
112
 
 
113
 
def initlogger(level=logging.WARNING):
114
 
    """init logger and add loglevel"""
115
 
    
116
 
    syslogger.setFormatter(logging.Formatter
117
 
                           ('Mandos [%(process)d]: %(levelname)s:'
118
 
                            ' %(message)s'))
119
 
    logger.addHandler(syslogger)
120
 
    
121
 
    console = logging.StreamHandler()
122
 
    console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
123
 
                                           ' [%(process)d]:'
124
 
                                           ' %(levelname)s:'
125
 
                                           ' %(message)s'))
126
 
    logger.addHandler(console)
127
 
    logger.setLevel(level)
128
 
 
129
 
 
130
 
class PGPError(Exception):
131
 
    """Exception if encryption/decryption fails"""
132
 
    pass
133
 
 
134
 
 
135
 
class PGPEngine(object):
136
 
    """A simple class for OpenPGP symmetric encryption & decryption"""
137
 
    def __init__(self):
138
 
        self.gnupg = GnuPGInterface.GnuPG()
139
 
        self.tempdir = tempfile.mkdtemp(prefix="mandos-")
140
 
        self.gnupg = GnuPGInterface.GnuPG()
141
 
        self.gnupg.options.meta_interactive = False
142
 
        self.gnupg.options.homedir = self.tempdir
143
 
        self.gnupg.options.extra_args.extend(['--force-mdc',
144
 
                                              '--quiet'])
145
 
    
146
 
    def __enter__(self):
147
 
        return self
148
 
    
149
 
    def __exit__ (self, exc_type, exc_value, traceback):
150
 
        self._cleanup()
151
 
        return False
152
 
    
153
 
    def __del__(self):
154
 
        self._cleanup()
155
 
    
156
 
    def _cleanup(self):
157
 
        if self.tempdir is not None:
158
 
            # Delete contents of tempdir
159
 
            for root, dirs, files in os.walk(self.tempdir,
160
 
                                             topdown = False):
161
 
                for filename in files:
162
 
                    os.remove(os.path.join(root, filename))
163
 
                for dirname in dirs:
164
 
                    os.rmdir(os.path.join(root, dirname))
165
 
            # Remove tempdir
166
 
            os.rmdir(self.tempdir)
167
 
            self.tempdir = None
168
 
    
169
 
    def password_encode(self, password):
170
 
        # Passphrase can not be empty and can not contain newlines or
171
 
        # NUL bytes.  So we prefix it and hex encode it.
172
 
        return b"mandos" + binascii.hexlify(password)
173
 
    
174
 
    def encrypt(self, data, password):
175
 
        self.gnupg.passphrase = self.password_encode(password)
176
 
        with open(os.devnull) as devnull:
177
 
            try:
178
 
                proc = self.gnupg.run(['--symmetric'],
179
 
                                      create_fhs=['stdin', 'stdout'],
180
 
                                      attach_fhs={'stderr': devnull})
181
 
                with contextlib.closing(proc.handles['stdin']) as f:
182
 
                    f.write(data)
183
 
                with contextlib.closing(proc.handles['stdout']) as f:
184
 
                    ciphertext = f.read()
185
 
                proc.wait()
186
 
            except IOError as e:
187
 
                raise PGPError(e)
188
 
        self.gnupg.passphrase = None
189
 
        return ciphertext
190
 
    
191
 
    def decrypt(self, data, password):
192
 
        self.gnupg.passphrase = self.password_encode(password)
193
 
        with open(os.devnull) as devnull:
194
 
            try:
195
 
                proc = self.gnupg.run(['--decrypt'],
196
 
                                      create_fhs=['stdin', 'stdout'],
197
 
                                      attach_fhs={'stderr': devnull})
198
 
                with contextlib.closing(proc.handles['stdin'] ) as f:
199
 
                    f.write(data)
200
 
                with contextlib.closing(proc.handles['stdout']) as f:
201
 
                    decrypted_plaintext = f.read()
202
 
                proc.wait()
203
 
            except IOError as e:
204
 
                raise PGPError(e)
205
 
        self.gnupg.passphrase = None
206
 
        return decrypted_plaintext
207
 
 
 
92
syslogger.setFormatter(logging.Formatter
 
93
                       ('Mandos [%(process)d]: %(levelname)s:'
 
94
                        ' %(message)s'))
 
95
logger.addHandler(syslogger)
 
96
 
 
97
console = logging.StreamHandler()
 
98
console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
 
99
                                       ' [%(process)d]:'
 
100
                                       ' %(levelname)s:'
 
101
                                       ' %(message)s'))
 
102
logger.addHandler(console)
208
103
 
209
104
 
210
105
class AvahiError(Exception):
327
222
            try:
328
223
                self.group.Free()
329
224
            except (dbus.exceptions.UnknownMethodException,
330
 
                    dbus.exceptions.DBusException):
 
225
                    dbus.exceptions.DBusException) as e:
331
226
                pass
332
227
            self.group = None
333
228
        self.remove()
377
272
                                % self.name))
378
273
        return ret
379
274
 
380
 
def timedelta_to_milliseconds(td):
 
275
def _timedelta_to_milliseconds(td):
381
276
    "Convert a datetime.timedelta() to milliseconds"
382
277
    return ((td.days * 24 * 60 * 60 * 1000)
383
278
            + (td.seconds * 1000)
387
282
    """A representation of a client host served by this server.
388
283
    
389
284
    Attributes:
390
 
    approved:   bool(); 'None' if not yet approved/disapproved
 
285
    _approved:   bool(); 'None' if not yet approved/disapproved
391
286
    approval_delay: datetime.timedelta(); Time to wait for approval
392
287
    approval_duration: datetime.timedelta(); Duration of one approval
393
288
    checker:    subprocess.Popen(); a running checker process used
400
295
                     instance %(name)s can be used in the command.
401
296
    checker_initiator_tag: a gobject event source tag, or None
402
297
    created:    datetime.datetime(); (UTC) object creation
403
 
    client_structure: Object describing what attributes a client has
404
 
                      and is used for storing the client at exit
405
298
    current_checker_command: string; current running checker_command
 
299
    disable_hook:  If set, called by disable() as disable_hook(self)
406
300
    disable_initiator_tag: a gobject event source tag, or None
407
301
    enabled:    bool()
408
302
    fingerprint: string (40 or 32 hexadecimal digits); used to
411
305
    interval:   datetime.timedelta(); How often to start a new checker
412
306
    last_approval_request: datetime.datetime(); (UTC) or None
413
307
    last_checked_ok: datetime.datetime(); (UTC) or None
414
 
 
415
 
    last_checker_status: integer between 0 and 255 reflecting exit
416
 
                         status of last checker. -1 reflects crashed
417
 
                         checker, or None.
418
 
    last_enabled: datetime.datetime(); (UTC) or None
 
308
    last_enabled: datetime.datetime(); (UTC)
419
309
    name:       string; from the config file, used in log messages and
420
310
                        D-Bus identifiers
421
311
    secret:     bytestring; sent verbatim (over TLS) to client
431
321
                          "created", "enabled", "fingerprint",
432
322
                          "host", "interval", "last_checked_ok",
433
323
                          "last_enabled", "name", "timeout")
434
 
    client_defaults = { "timeout": "5m",
435
 
                        "extended_timeout": "15m",
436
 
                        "interval": "2m",
437
 
                        "checker": "fping -q -- %%(host)s",
438
 
                        "host": "",
439
 
                        "approval_delay": "0s",
440
 
                        "approval_duration": "1s",
441
 
                        "approved_by_default": "True",
442
 
                        "enabled": "True",
443
 
                        }
444
324
    
445
325
    def timeout_milliseconds(self):
446
326
        "Return the 'timeout' attribute in milliseconds"
447
 
        return timedelta_to_milliseconds(self.timeout)
 
327
        return _timedelta_to_milliseconds(self.timeout)
448
328
    
449
329
    def extended_timeout_milliseconds(self):
450
330
        "Return the 'extended_timeout' attribute in milliseconds"
451
 
        return timedelta_to_milliseconds(self.extended_timeout)
 
331
        return _timedelta_to_milliseconds(self.extended_timeout)
452
332
    
453
333
    def interval_milliseconds(self):
454
334
        "Return the 'interval' attribute in milliseconds"
455
 
        return timedelta_to_milliseconds(self.interval)
 
335
        return _timedelta_to_milliseconds(self.interval)
456
336
    
457
337
    def approval_delay_milliseconds(self):
458
 
        return timedelta_to_milliseconds(self.approval_delay)
459
 
 
460
 
    @staticmethod
461
 
    def config_parser(config):
462
 
        """ Construct a new dict of client settings of this form:
463
 
        { client_name: {setting_name: value, ...}, ...}
464
 
        with exceptions for any special settings as defined above"""
465
 
        settings = {}
466
 
        for client_name in config.sections():
467
 
            section = dict(config.items(client_name))
468
 
            client = settings[client_name] = {}
469
 
            
470
 
            # Default copying each value from config to new dict
471
 
            for setting, value in section.iteritems():
472
 
                client[setting] = value
473
 
            
474
 
            # Reformat values from string types to Python types
475
 
            client["approved_by_default"] = config.getboolean(
476
 
                client_name, "approved_by_default")
477
 
            client["enabled"] = config.getboolean(client_name, "enabled")
478
 
            
479
 
            client["fingerprint"] = (section["fingerprint"].upper()
480
 
                                     .replace(" ", ""))
481
 
            if "secret" in section:
482
 
                client["secret"] = section["secret"].decode("base64")
483
 
            elif "secfile" in section:
484
 
                with open(os.path.expanduser(os.path.expandvars
485
 
                                             (section["secfile"])),
486
 
                          "rb") as secfile:
487
 
                    client["secret"] = secfile.read()
488
 
            else:
489
 
                raise TypeError("No secret or secfile for section %s"
490
 
                                % section)
491
 
            client["timeout"] = string_to_delta(section["timeout"])
492
 
            client["extended_timeout"] = string_to_delta(
493
 
                section["extended_timeout"])
494
 
            client["interval"] = string_to_delta(section["interval"])
495
 
            client["approval_delay"] = string_to_delta(
496
 
                section["approval_delay"])
497
 
            client["approval_duration"] = string_to_delta(
498
 
                section["approval_duration"])
499
 
 
500
 
        return settings
501
 
        
502
 
        
503
 
    def __init__(self, config, name = None):
 
338
        return _timedelta_to_milliseconds(self.approval_delay)
 
339
    
 
340
    def __init__(self, name = None, disable_hook=None, config=None):
504
341
        """Note: the 'checker' key in 'config' sets the
505
342
        'checker_command' attribute and *not* the 'checker'
506
343
        attribute."""
507
344
        self.name = name
 
345
        if config is None:
 
346
            config = {}
508
347
        logger.debug("Creating client %r", self.name)
509
348
        # Uppercase and remove spaces from fingerprint for later
510
349
        # comparison purposes with return value from the fingerprint()
511
350
        # function
512
 
        self.fingerprint = config["fingerprint"]
 
351
        self.fingerprint = (config["fingerprint"].upper()
 
352
                            .replace(" ", ""))
513
353
        logger.debug("  Fingerprint: %s", self.fingerprint)
514
 
        self.secret = config["secret"]
515
 
        self.host = config["host"]
 
354
        if "secret" in config:
 
355
            self.secret = config["secret"].decode("base64")
 
356
        elif "secfile" in config:
 
357
            with open(os.path.expanduser(os.path.expandvars
 
358
                                         (config["secfile"])),
 
359
                      "rb") as secfile:
 
360
                self.secret = secfile.read()
 
361
        else:
 
362
            raise TypeError("No secret or secfile for client %s"
 
363
                            % self.name)
 
364
        self.host = config.get("host", "")
516
365
        self.created = datetime.datetime.utcnow()
517
 
        self.enabled = config["enabled"]
 
366
        self.enabled = False
518
367
        self.last_approval_request = None
519
 
        if self.enabled:
520
 
            self.last_enabled = datetime.datetime.utcnow()
521
 
        else:
522
 
            self.last_enabled = None
 
368
        self.last_enabled = None
523
369
        self.last_checked_ok = None
524
 
        self.last_checker_status = None
525
 
        self.timeout = config["timeout"]
526
 
        self.extended_timeout = config["extended_timeout"]
527
 
        self.interval = config["interval"]
 
370
        self.timeout = string_to_delta(config["timeout"])
 
371
        self.extended_timeout = string_to_delta(config
 
372
                                                ["extended_timeout"])
 
373
        self.interval = string_to_delta(config["interval"])
 
374
        self.disable_hook = disable_hook
528
375
        self.checker = None
529
376
        self.checker_initiator_tag = None
530
377
        self.disable_initiator_tag = None
531
 
        if self.enabled:
532
 
            self.expires = datetime.datetime.utcnow() + self.timeout
533
 
        else:
534
 
            self.expires = None
 
378
        self.expires = None
535
379
        self.checker_callback_tag = None
536
380
        self.checker_command = config["checker"]
537
381
        self.current_checker_command = None
538
 
        self.approved = None
539
 
        self.approved_by_default = config["approved_by_default"]
 
382
        self.last_connect = None
 
383
        self._approved = None
 
384
        self.approved_by_default = config.get("approved_by_default",
 
385
                                              True)
540
386
        self.approvals_pending = 0
541
 
        self.approval_delay = config["approval_delay"]
542
 
        self.approval_duration = config["approval_duration"]
 
387
        self.approval_delay = string_to_delta(
 
388
            config["approval_delay"])
 
389
        self.approval_duration = string_to_delta(
 
390
            config["approval_duration"])
543
391
        self.changedstate = (multiprocessing_manager
544
392
                             .Condition(multiprocessing_manager
545
393
                                        .Lock()))
546
 
        self.client_structure = [attr for attr in
547
 
                                 self.__dict__.iterkeys()
548
 
                                 if not attr.startswith("_")]
549
 
        self.client_structure.append("client_structure")
550
 
        
551
 
        for name, t in inspect.getmembers(type(self),
552
 
                                          lambda obj:
553
 
                                              isinstance(obj,
554
 
                                                         property)):
555
 
            if not name.startswith("_"):
556
 
                self.client_structure.append(name)
557
394
    
558
 
    # Send notice to process children that client state has changed
559
395
    def send_changedstate(self):
560
 
        with self.changedstate:
561
 
            self.changedstate.notify_all()
 
396
        self.changedstate.acquire()
 
397
        self.changedstate.notify_all()
 
398
        self.changedstate.release()
562
399
    
563
400
    def enable(self):
564
401
        """Start this client's checker and timeout hooks"""
566
403
            # Already enabled
567
404
            return
568
405
        self.send_changedstate()
 
406
        # Schedule a new checker to be started an 'interval' from now,
 
407
        # and every interval from then on.
 
408
        self.checker_initiator_tag = (gobject.timeout_add
 
409
                                      (self.interval_milliseconds(),
 
410
                                       self.start_checker))
 
411
        # Schedule a disable() when 'timeout' has passed
569
412
        self.expires = datetime.datetime.utcnow() + self.timeout
 
413
        self.disable_initiator_tag = (gobject.timeout_add
 
414
                                   (self.timeout_milliseconds(),
 
415
                                    self.disable))
570
416
        self.enabled = True
571
417
        self.last_enabled = datetime.datetime.utcnow()
572
 
        self.init_checker()
 
418
        # Also start a new checker *right now*.
 
419
        self.start_checker()
573
420
    
574
421
    def disable(self, quiet=True):
575
422
        """Disable this client."""
587
434
            gobject.source_remove(self.checker_initiator_tag)
588
435
            self.checker_initiator_tag = None
589
436
        self.stop_checker()
 
437
        if self.disable_hook:
 
438
            self.disable_hook(self)
590
439
        self.enabled = False
591
440
        # Do not run this again if called by a gobject.timeout_add
592
441
        return False
593
442
    
594
443
    def __del__(self):
 
444
        self.disable_hook = None
595
445
        self.disable()
596
446
    
597
 
    def init_checker(self):
598
 
        # Schedule a new checker to be started an 'interval' from now,
599
 
        # and every interval from then on.
600
 
        self.checker_initiator_tag = (gobject.timeout_add
601
 
                                      (self.interval_milliseconds(),
602
 
                                       self.start_checker))
603
 
        # Schedule a disable() when 'timeout' has passed
604
 
        self.disable_initiator_tag = (gobject.timeout_add
605
 
                                   (self.timeout_milliseconds(),
606
 
                                    self.disable))
607
 
        # Also start a new checker *right now*.
608
 
        self.start_checker()
609
 
    
610
447
    def checker_callback(self, pid, condition, command):
611
448
        """The checker has completed, so take appropriate actions."""
612
449
        self.checker_callback_tag = None
613
450
        self.checker = None
614
451
        if os.WIFEXITED(condition):
615
 
            self.last_checker_status = os.WEXITSTATUS(condition)
616
 
            if self.last_checker_status == 0:
 
452
            exitstatus = os.WEXITSTATUS(condition)
 
453
            if exitstatus == 0:
617
454
                logger.info("Checker for %(name)s succeeded",
618
455
                            vars(self))
619
456
                self.checked_ok()
621
458
                logger.info("Checker for %(name)s failed",
622
459
                            vars(self))
623
460
        else:
624
 
            self.last_checker_status = -1
625
461
            logger.warning("Checker for %(name)s crashed?",
626
462
                           vars(self))
627
463
    
638
474
            gobject.source_remove(self.disable_initiator_tag)
639
475
        if getattr(self, "enabled", False):
640
476
            self.disable_initiator_tag = (gobject.timeout_add
641
 
                                          (timedelta_to_milliseconds
 
477
                                          (_timedelta_to_milliseconds
642
478
                                           (timeout), self.disable))
643
479
            self.expires = datetime.datetime.utcnow() + timeout
644
480
    
861
697
        
862
698
        Note: Will not include properties with access="write".
863
699
        """
864
 
        properties = {}
 
700
        all = {}
865
701
        for name, prop in self._get_all_dbus_properties():
866
702
            if (interface_name
867
703
                and interface_name != prop._dbus_interface):
872
708
                continue
873
709
            value = prop()
874
710
            if not hasattr(value, "variant_level"):
875
 
                properties[name] = value
 
711
                all[name] = value
876
712
                continue
877
 
            properties[name] = type(value)(value, variant_level=
878
 
                                           value.variant_level+1)
879
 
        return dbus.Dictionary(properties, signature="sv")
 
713
            all[name] = type(value)(value, variant_level=
 
714
                                    value.variant_level+1)
 
715
        return dbus.Dictionary(all, signature="sv")
880
716
    
881
717
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
882
718
                         out_signature="s",
933
769
    return dbus.String(dt.isoformat(),
934
770
                       variant_level=variant_level)
935
771
 
936
 
 
937
772
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
938
773
                                  .__metaclass__):
939
774
    """Applied to an empty subclass of a D-Bus object, this metaclass
1031
866
                                        attribute.func_closure)))
1032
867
        return type.__new__(mcs, name, bases, attr)
1033
868
 
1034
 
 
1035
869
class ClientDBus(Client, DBusObjectWithProperties):
1036
870
    """A Client class using D-Bus
1037
871
    
1046
880
    # dbus.service.Object doesn't use super(), so we can't either.
1047
881
    
1048
882
    def __init__(self, bus = None, *args, **kwargs):
 
883
        self._approvals_pending = 0
1049
884
        self.bus = bus
1050
885
        Client.__init__(self, *args, **kwargs)
1051
 
        
1052
 
        self._approvals_pending = 0
1053
886
        # Only now, when this client is initialized, can it show up on
1054
887
        # the D-Bus
1055
888
        client_object_name = unicode(self.name).translate(
1065
898
                             variant_level=1):
1066
899
        """ Modify a variable so that it's a property which announces
1067
900
        its changes to DBus.
1068
 
        
 
901
 
1069
902
        transform_fun: Function that takes a value and a variant_level
1070
903
                       and transforms it to a D-Bus type.
1071
904
        dbus_name: D-Bus name of the variable
1105
938
        datetime_to_dbus, "LastApprovalRequest")
1106
939
    approved_by_default = notifychangeproperty(dbus.Boolean,
1107
940
                                               "ApprovedByDefault")
1108
 
    approval_delay = notifychangeproperty(dbus.UInt64,
 
941
    approval_delay = notifychangeproperty(dbus.UInt16,
1109
942
                                          "ApprovalDelay",
1110
943
                                          type_func =
1111
 
                                          timedelta_to_milliseconds)
 
944
                                          _timedelta_to_milliseconds)
1112
945
    approval_duration = notifychangeproperty(
1113
 
        dbus.UInt64, "ApprovalDuration",
1114
 
        type_func = timedelta_to_milliseconds)
 
946
        dbus.UInt16, "ApprovalDuration",
 
947
        type_func = _timedelta_to_milliseconds)
1115
948
    host = notifychangeproperty(dbus.String, "Host")
1116
 
    timeout = notifychangeproperty(dbus.UInt64, "Timeout",
 
949
    timeout = notifychangeproperty(dbus.UInt16, "Timeout",
1117
950
                                   type_func =
1118
 
                                   timedelta_to_milliseconds)
 
951
                                   _timedelta_to_milliseconds)
1119
952
    extended_timeout = notifychangeproperty(
1120
 
        dbus.UInt64, "ExtendedTimeout",
1121
 
        type_func = timedelta_to_milliseconds)
1122
 
    interval = notifychangeproperty(dbus.UInt64,
 
953
        dbus.UInt16, "ExtendedTimeout",
 
954
        type_func = _timedelta_to_milliseconds)
 
955
    interval = notifychangeproperty(dbus.UInt16,
1123
956
                                    "Interval",
1124
957
                                    type_func =
1125
 
                                    timedelta_to_milliseconds)
 
958
                                    _timedelta_to_milliseconds)
1126
959
    checker_command = notifychangeproperty(dbus.String, "Checker")
1127
960
    
1128
961
    del notifychangeproperty
1170
1003
        return r
1171
1004
    
1172
1005
    def _reset_approved(self):
1173
 
        self.approved = None
 
1006
        self._approved = None
1174
1007
        return False
1175
1008
    
1176
1009
    def approve(self, value=True):
1177
1010
        self.send_changedstate()
1178
 
        self.approved = value
1179
 
        gobject.timeout_add(timedelta_to_milliseconds
 
1011
        self._approved = value
 
1012
        gobject.timeout_add(_timedelta_to_milliseconds
1180
1013
                            (self.approval_duration),
1181
1014
                            self._reset_approved)
1182
1015
    
1225
1058
        "D-Bus signal"
1226
1059
        return self.need_approval()
1227
1060
    
1228
 
    # NeRwequest - signal
1229
 
    @dbus.service.signal(_interface, signature="s")
1230
 
    def NewRequest(self, ip):
1231
 
        """D-Bus signal
1232
 
        Is sent after a client request a password.
1233
 
        """
1234
 
        pass
1235
 
    
1236
1061
    ## Methods
1237
1062
    
1238
1063
    # Approve - method
1296
1121
                           access="readwrite")
1297
1122
    def ApprovalDuration_dbus_property(self, value=None):
1298
1123
        if value is None:       # get
1299
 
            return dbus.UInt64(timedelta_to_milliseconds(
 
1124
            return dbus.UInt64(_timedelta_to_milliseconds(
1300
1125
                    self.approval_duration))
1301
1126
        self.approval_duration = datetime.timedelta(0, 0, 0, value)
1302
1127
    
1316
1141
    def Host_dbus_property(self, value=None):
1317
1142
        if value is None:       # get
1318
1143
            return dbus.String(self.host)
1319
 
        self.host = unicode(value)
 
1144
        self.host = value
1320
1145
    
1321
1146
    # Created - property
1322
1147
    @dbus_service_property(_interface, signature="s", access="read")
1323
1148
    def Created_dbus_property(self):
1324
 
        return datetime_to_dbus(self.created)
 
1149
        return dbus.String(datetime_to_dbus(self.created))
1325
1150
    
1326
1151
    # LastEnabled - property
1327
1152
    @dbus_service_property(_interface, signature="s", access="read")
1371
1196
        gobject.source_remove(self.disable_initiator_tag)
1372
1197
        self.disable_initiator_tag = None
1373
1198
        self.expires = None
1374
 
        time_to_die = timedelta_to_milliseconds((self
1375
 
                                                 .last_checked_ok
1376
 
                                                 + self.timeout)
1377
 
                                                - datetime.datetime
1378
 
                                                .utcnow())
 
1199
        time_to_die = _timedelta_to_milliseconds((self
 
1200
                                                  .last_checked_ok
 
1201
                                                  + self.timeout)
 
1202
                                                 - datetime.datetime
 
1203
                                                 .utcnow())
1379
1204
        if time_to_die <= 0:
1380
1205
            # The timeout has passed
1381
1206
            self.disable()
1403
1228
        self.interval = datetime.timedelta(0, 0, 0, value)
1404
1229
        if getattr(self, "checker_initiator_tag", None) is None:
1405
1230
            return
1406
 
        if self.enabled:
1407
 
            # Reschedule checker run
1408
 
            gobject.source_remove(self.checker_initiator_tag)
1409
 
            self.checker_initiator_tag = (gobject.timeout_add
1410
 
                                          (value, self.start_checker))
1411
 
            self.start_checker()    # Start one now, too
 
1231
        # Reschedule checker run
 
1232
        gobject.source_remove(self.checker_initiator_tag)
 
1233
        self.checker_initiator_tag = (gobject.timeout_add
 
1234
                                      (value, self.start_checker))
 
1235
        self.start_checker()    # Start one now, too
1412
1236
    
1413
1237
    # Checker - property
1414
1238
    @dbus_service_property(_interface, signature="s",
1416
1240
    def Checker_dbus_property(self, value=None):
1417
1241
        if value is None:       # get
1418
1242
            return dbus.String(self.checker_command)
1419
 
        self.checker_command = unicode(value)
 
1243
        self.checker_command = value
1420
1244
    
1421
1245
    # CheckerRunning - property
1422
1246
    @dbus_service_property(_interface, signature="b",
1451
1275
            raise KeyError()
1452
1276
    
1453
1277
    def __getattribute__(self, name):
1454
 
        if name == '_pipe':
 
1278
        if(name == '_pipe'):
1455
1279
            return super(ProxyClient, self).__getattribute__(name)
1456
1280
        self._pipe.send(('getattr', name))
1457
1281
        data = self._pipe.recv()
1464
1288
            return func
1465
1289
    
1466
1290
    def __setattr__(self, name, value):
1467
 
        if name == '_pipe':
 
1291
        if(name == '_pipe'):
1468
1292
            return super(ProxyClient, self).__setattr__(name, value)
1469
1293
        self._pipe.send(('setattr', name, value))
1470
1294
 
1471
 
 
1472
1295
class ClientDBusTransitional(ClientDBus):
1473
1296
    __metaclass__ = AlternateDBusNamesMetaclass
1474
1297
 
1475
 
 
1476
1298
class ClientHandler(socketserver.BaseRequestHandler, object):
1477
1299
    """A class to handle client connections.
1478
1300
    
1546
1368
                except KeyError:
1547
1369
                    return
1548
1370
                
1549
 
                if self.server.use_dbus:
1550
 
                    # Emit D-Bus signal
1551
 
                    client.NewRequest(str(self.client_address))
1552
 
                
1553
1371
                if client.approval_delay:
1554
1372
                    delay = client.approval_delay
1555
1373
                    client.approvals_pending += 1
1564
1382
                            client.Rejected("Disabled")
1565
1383
                        return
1566
1384
                    
1567
 
                    if client.approved or not client.approval_delay:
 
1385
                    if client._approved or not client.approval_delay:
1568
1386
                        #We are approved or approval is disabled
1569
1387
                        break
1570
 
                    elif client.approved is None:
 
1388
                    elif client._approved is None:
1571
1389
                        logger.info("Client %s needs approval",
1572
1390
                                    client.name)
1573
1391
                        if self.server.use_dbus:
1587
1405
                    time = datetime.datetime.now()
1588
1406
                    client.changedstate.acquire()
1589
1407
                    (client.changedstate.wait
1590
 
                     (float(client.timedelta_to_milliseconds(delay)
 
1408
                     (float(client._timedelta_to_milliseconds(delay)
1591
1409
                            / 1000)))
1592
1410
                    client.changedstate.release()
1593
1411
                    time2 = datetime.datetime.now()
1692
1510
        # Convert the buffer to a Python bytestring
1693
1511
        fpr = ctypes.string_at(buf, buf_len.value)
1694
1512
        # Convert the bytestring to hexadecimal notation
1695
 
        hex_fpr = binascii.hexlify(fpr).upper()
 
1513
        hex_fpr = ''.join("%02X" % ord(char) for char in fpr)
1696
1514
        return hex_fpr
1697
1515
 
1698
1516
 
1701
1519
    def sub_process_main(self, request, address):
1702
1520
        try:
1703
1521
            self.finish_request(request, address)
1704
 
        except Exception:
 
1522
        except:
1705
1523
            self.handle_error(request, address)
1706
1524
        self.close_request(request)
1707
1525
    
1812
1630
        self.enabled = False
1813
1631
        self.clients = clients
1814
1632
        if self.clients is None:
1815
 
            self.clients = {}
 
1633
            self.clients = set()
1816
1634
        self.use_dbus = use_dbus
1817
1635
        self.gnutls_priority = gnutls_priority
1818
1636
        IPv6_TCPServer.__init__(self, server_address,
1865
1683
            fpr = request[1]
1866
1684
            address = request[2]
1867
1685
            
1868
 
            for c in self.clients.itervalues():
 
1686
            for c in self.clients:
1869
1687
                if c.fingerprint == fpr:
1870
1688
                    client = c
1871
1689
                    break
1955
1773
    return timevalue
1956
1774
 
1957
1775
 
 
1776
def if_nametoindex(interface):
 
1777
    """Call the C function if_nametoindex(), or equivalent
 
1778
    
 
1779
    Note: This function cannot accept a unicode string."""
 
1780
    global if_nametoindex
 
1781
    try:
 
1782
        if_nametoindex = (ctypes.cdll.LoadLibrary
 
1783
                          (ctypes.util.find_library("c"))
 
1784
                          .if_nametoindex)
 
1785
    except (OSError, AttributeError):
 
1786
        logger.warning("Doing if_nametoindex the hard way")
 
1787
        def if_nametoindex(interface):
 
1788
            "Get an interface index the hard way, i.e. using fcntl()"
 
1789
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
 
1790
            with contextlib.closing(socket.socket()) as s:
 
1791
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
 
1792
                                    struct.pack(str("16s16x"),
 
1793
                                                interface))
 
1794
            interface_index = struct.unpack(str("I"),
 
1795
                                            ifreq[16:20])[0]
 
1796
            return interface_index
 
1797
    return if_nametoindex(interface)
 
1798
 
 
1799
 
1958
1800
def daemon(nochdir = False, noclose = False):
1959
1801
    """See daemon(3).  Standard BSD Unix function.
1960
1802
    
2015
1857
                        " system bus interface")
2016
1858
    parser.add_argument("--no-ipv6", action="store_false",
2017
1859
                        dest="use_ipv6", help="Do not use IPv6")
2018
 
    parser.add_argument("--no-restore", action="store_false",
2019
 
                        dest="restore", help="Do not restore stored"
2020
 
                        " state")
2021
 
    parser.add_argument("--statedir", metavar="DIR",
2022
 
                        help="Directory to save/restore state in")
2023
 
    
2024
1860
    options = parser.parse_args()
2025
1861
    
2026
1862
    if options.check:
2039
1875
                        "use_dbus": "True",
2040
1876
                        "use_ipv6": "True",
2041
1877
                        "debuglevel": "",
2042
 
                        "restore": "True",
2043
 
                        "statedir": "/var/lib/mandos"
2044
1878
                        }
2045
1879
    
2046
1880
    # Parse config file for server-global settings
2063
1897
    # options, if set.
2064
1898
    for option in ("interface", "address", "port", "debug",
2065
1899
                   "priority", "servicename", "configdir",
2066
 
                   "use_dbus", "use_ipv6", "debuglevel", "restore",
2067
 
                   "statedir"):
 
1900
                   "use_dbus", "use_ipv6", "debuglevel"):
2068
1901
        value = getattr(options, option)
2069
1902
        if value is not None:
2070
1903
            server_settings[option] = value
2082
1915
    debuglevel = server_settings["debuglevel"]
2083
1916
    use_dbus = server_settings["use_dbus"]
2084
1917
    use_ipv6 = server_settings["use_ipv6"]
2085
 
    stored_state_path = os.path.join(server_settings["statedir"],
2086
 
                                     stored_state_file)
2087
 
    
2088
 
    if debug:
2089
 
        initlogger(logging.DEBUG)
2090
 
    else:
2091
 
        if not debuglevel:
2092
 
            initlogger()
2093
 
        else:
2094
 
            level = getattr(logging, debuglevel.upper())
2095
 
            initlogger(level)
2096
1918
    
2097
1919
    if server_settings["servicename"] != "Mandos":
2098
1920
        syslogger.setFormatter(logging.Formatter
2101
1923
                                % server_settings["servicename"]))
2102
1924
    
2103
1925
    # Parse config file with clients
2104
 
    client_config = configparser.SafeConfigParser(Client.client_defaults)
 
1926
    client_defaults = { "timeout": "5m",
 
1927
                        "extended_timeout": "15m",
 
1928
                        "interval": "2m",
 
1929
                        "checker": "fping -q -- %%(host)s",
 
1930
                        "host": "",
 
1931
                        "approval_delay": "0s",
 
1932
                        "approval_duration": "1s",
 
1933
                        }
 
1934
    client_config = configparser.SafeConfigParser(client_defaults)
2105
1935
    client_config.read(os.path.join(server_settings["configdir"],
2106
1936
                                    "clients.conf"))
2107
1937
    
2145
1975
        if error[0] != errno.EPERM:
2146
1976
            raise error
2147
1977
    
 
1978
    if not debug and not debuglevel:
 
1979
        logger.setLevel(logging.WARNING)
 
1980
    if debuglevel:
 
1981
        level = getattr(logging, debuglevel.upper())
 
1982
        logger.setLevel(level)
 
1983
    
2148
1984
    if debug:
 
1985
        logger.setLevel(logging.DEBUG)
2149
1986
        # Enable all possible GnuTLS debugging
2150
1987
        
2151
1988
        # "Use a log level over 10 to enable all debugging options."
2207
2044
    if use_dbus:
2208
2045
        client_class = functools.partial(ClientDBusTransitional,
2209
2046
                                         bus = bus)
2210
 
    
2211
 
    client_settings = Client.config_parser(client_config)
2212
 
    old_client_settings = {}
2213
 
    clients_data = []
2214
 
    
2215
 
    # Get client data and settings from last running state.
2216
 
    if server_settings["restore"]:
2217
 
        try:
2218
 
            with open(stored_state_path, "rb") as stored_state:
2219
 
                clients_data, old_client_settings = (pickle.load
2220
 
                                                     (stored_state))
2221
 
            os.remove(stored_state_path)
2222
 
        except IOError as e:
2223
 
            logger.warning("Could not load persistent state: {0}"
2224
 
                           .format(e))
2225
 
            if e.errno != errno.ENOENT:
2226
 
                raise
2227
 
    
2228
 
    with PGPEngine() as pgp:
2229
 
        for client in clients_data:
2230
 
            client_name = client["name"]
2231
 
            
2232
 
            # Decide which value to use after restoring saved state.
2233
 
            # We have three different values: Old config file,
2234
 
            # new config file, and saved state.
2235
 
            # New config value takes precedence if it differs from old
2236
 
            # config value, otherwise use saved state.
2237
 
            for name, value in client_settings[client_name].items():
2238
 
                try:
2239
 
                    # For each value in new config, check if it
2240
 
                    # differs from the old config value (Except for
2241
 
                    # the "secret" attribute)
2242
 
                    if (name != "secret" and
2243
 
                        value != old_client_settings[client_name]
2244
 
                        [name]):
2245
 
                        client[name] = value
2246
 
                except KeyError:
2247
 
                    pass
2248
 
            
2249
 
            # Clients who has passed its expire date can still be
2250
 
            # enabled if its last checker was successful.  Clients
2251
 
            # whose checker failed before we stored its state is
2252
 
            # assumed to have failed all checkers during downtime.
2253
 
            if client["enabled"]:
2254
 
                if datetime.datetime.utcnow() >= client["expires"]:
2255
 
                    if not client["last_checked_ok"]:
2256
 
                        logger.warning(
2257
 
                            "disabling client {0} - Client never "
2258
 
                            "performed a successfull checker"
2259
 
                            .format(client["name"]))
2260
 
                        client["enabled"] = False
2261
 
                    elif client["last_checker_status"] != 0:
2262
 
                        logger.warning(
2263
 
                            "disabling client {0} - Client "
2264
 
                            "last checker failed with error code {1}"
2265
 
                            .format(client["name"],
2266
 
                                    client["last_checker_status"]))
2267
 
                        client["enabled"] = False
2268
 
                    else:
2269
 
                        client["expires"] = (datetime.datetime
2270
 
                                             .utcnow()
2271
 
                                             + client["timeout"])
2272
 
            
2273
 
            client["changedstate"] = (multiprocessing_manager
2274
 
                                      .Condition
2275
 
                                      (multiprocessing_manager
2276
 
                                       .Lock()))
2277
 
            client["checker"] = None
2278
 
            if use_dbus:
2279
 
                new_client = (ClientDBusTransitional.__new__
2280
 
                              (ClientDBusTransitional))
2281
 
                tcp_server.clients[client_name] = new_client
2282
 
                new_client.bus = bus
2283
 
                for name, value in client.iteritems():
2284
 
                    setattr(new_client, name, value)
2285
 
                client_object_name = unicode(client_name).translate(
2286
 
                    {ord("."): ord("_"),
2287
 
                     ord("-"): ord("_")})
2288
 
                new_client.dbus_object_path = (dbus.ObjectPath
2289
 
                                               ("/clients/"
2290
 
                                                + client_object_name))
2291
 
                DBusObjectWithProperties.__init__(new_client,
2292
 
                                                  new_client.bus,
2293
 
                                                  new_client
2294
 
                                                  .dbus_object_path)
2295
 
            else:
2296
 
                tcp_server.clients[client_name] = (Client.__new__
2297
 
                                                   (Client))
2298
 
                for name, value in client.iteritems():
2299
 
                    setattr(tcp_server.clients[client_name],
2300
 
                            name, value)
2301
 
            
 
2047
    def client_config_items(config, section):
 
2048
        special_settings = {
 
2049
            "approved_by_default":
 
2050
                lambda: config.getboolean(section,
 
2051
                                          "approved_by_default"),
 
2052
            }
 
2053
        for name, value in config.items(section):
2302
2054
            try:
2303
 
                tcp_server.clients[client_name].secret = (
2304
 
                    pgp.decrypt(tcp_server.clients[client_name]
2305
 
                                .encrypted_secret,
2306
 
                                client_settings[client_name]
2307
 
                                ["secret"]))
2308
 
            except PGPError:
2309
 
                # If decryption fails, we use secret from new settings
2310
 
                logger.debug("Failed to decrypt {0} old secret"
2311
 
                             .format(client_name))
2312
 
                tcp_server.clients[client_name].secret = (
2313
 
                    client_settings[client_name]["secret"])
2314
 
    
2315
 
    # Create/remove clients based on new changes made to config
2316
 
    for clientname in set(old_client_settings) - set(client_settings):
2317
 
        del tcp_server.clients[clientname]
2318
 
    for clientname in set(client_settings) - set(old_client_settings):
2319
 
        tcp_server.clients[clientname] = (client_class(name = clientname,
2320
 
                                                       config =
2321
 
                                                       client_settings
2322
 
                                                       [clientname]))
2323
 
    
 
2055
                yield (name, special_settings[name]())
 
2056
            except KeyError:
 
2057
                yield (name, value)
 
2058
    
 
2059
    tcp_server.clients.update(set(
 
2060
            client_class(name = section,
 
2061
                         config= dict(client_config_items(
 
2062
                        client_config, section)))
 
2063
            for section in client_config.sections()))
2324
2064
    if not tcp_server.clients:
2325
2065
        logger.warning("No clients defined")
2326
2066
        
2337
2077
            # "pidfile" was never created
2338
2078
            pass
2339
2079
        del pidfilename
 
2080
        
2340
2081
        signal.signal(signal.SIGINT, signal.SIG_IGN)
2341
2082
    
2342
2083
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2368
2109
            def GetAllClients(self):
2369
2110
                "D-Bus method"
2370
2111
                return dbus.Array(c.dbus_object_path
2371
 
                                  for c in
2372
 
                                  tcp_server.clients.itervalues())
 
2112
                                  for c in tcp_server.clients)
2373
2113
            
2374
2114
            @dbus.service.method(_interface,
2375
2115
                                 out_signature="a{oa{sv}}")
2377
2117
                "D-Bus method"
2378
2118
                return dbus.Dictionary(
2379
2119
                    ((c.dbus_object_path, c.GetAll(""))
2380
 
                     for c in tcp_server.clients.itervalues()),
 
2120
                     for c in tcp_server.clients),
2381
2121
                    signature="oa{sv}")
2382
2122
            
2383
2123
            @dbus.service.method(_interface, in_signature="o")
2384
2124
            def RemoveClient(self, object_path):
2385
2125
                "D-Bus method"
2386
 
                for c in tcp_server.clients.itervalues():
 
2126
                for c in tcp_server.clients:
2387
2127
                    if c.dbus_object_path == object_path:
2388
 
                        del tcp_server.clients[c.name]
 
2128
                        tcp_server.clients.remove(c)
2389
2129
                        c.remove_from_connection()
2390
2130
                        # Don't signal anything except ClientRemoved
2391
2131
                        c.disable(quiet=True)
2405
2145
        service.cleanup()
2406
2146
        
2407
2147
        multiprocessing.active_children()
2408
 
        if not (tcp_server.clients or client_settings):
2409
 
            return
2410
 
        
2411
 
        # Store client before exiting. Secrets are encrypted with key
2412
 
        # based on what config file has. If config file is
2413
 
        # removed/edited, old secret will thus be unrecovable.
2414
 
        clients = []
2415
 
        with PGPEngine() as pgp:
2416
 
            for client in tcp_server.clients.itervalues():
2417
 
                key = client_settings[client.name]["secret"]
2418
 
                client.encrypted_secret = pgp.encrypt(client.secret,
2419
 
                                                      key)
2420
 
                client_dict = {}
2421
 
                
2422
 
                # A list of attributes that will not be stored when
2423
 
                # shutting down.
2424
 
                exclude = set(("bus", "changedstate", "secret",
2425
 
                               "checker"))
2426
 
                for name, typ in (inspect.getmembers
2427
 
                                  (dbus.service.Object)):
2428
 
                    exclude.add(name)
2429
 
                
2430
 
                client_dict["encrypted_secret"] = (client
2431
 
                                                   .encrypted_secret)
2432
 
                for attr in client.client_structure:
2433
 
                    if attr not in exclude:
2434
 
                        client_dict[attr] = getattr(client, attr)
2435
 
                
2436
 
                clients.append(client_dict)
2437
 
                del client_settings[client.name]["secret"]
2438
 
        
2439
 
        try:
2440
 
            with os.fdopen(os.open(stored_state_path,
2441
 
                                   os.O_CREAT|os.O_WRONLY|os.O_TRUNC,
2442
 
                                   0600), "wb") as stored_state:
2443
 
                pickle.dump((clients, client_settings), stored_state)
2444
 
        except (IOError, OSError) as e:
2445
 
            logger.warning("Could not save persistent state: {0}"
2446
 
                           .format(e))
2447
 
            if e.errno not in (errno.ENOENT, errno.EACCES):
2448
 
                raise
2449
 
        
2450
 
        # Delete all clients, and settings from config
2451
2148
        while tcp_server.clients:
2452
 
            name, client = tcp_server.clients.popitem()
 
2149
            client = tcp_server.clients.pop()
2453
2150
            if use_dbus:
2454
2151
                client.remove_from_connection()
 
2152
            client.disable_hook = None
2455
2153
            # Don't signal anything except ClientRemoved
2456
2154
            client.disable(quiet=True)
2457
2155
            if use_dbus:
2459
2157
                mandos_dbus_service.ClientRemoved(client
2460
2158
                                                  .dbus_object_path,
2461
2159
                                                  client.name)
2462
 
        client_settings.clear()
2463
2160
    
2464
2161
    atexit.register(cleanup)
2465
2162
    
2466
 
    for client in tcp_server.clients.itervalues():
 
2163
    for client in tcp_server.clients:
2467
2164
        if use_dbus:
2468
2165
            # Emit D-Bus signal
2469
2166
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
2470
 
        # Need to initiate checking of clients
2471
 
        if client.enabled:
2472
 
            client.init_checker()
 
2167
        client.enable()
2473
2168
    
2474
2169
    tcp_server.enable()
2475
2170
    tcp_server.server_activate()
2515
2210
    # Must run before the D-Bus bus name gets deregistered
2516
2211
    cleanup()
2517
2212
 
 
2213
 
2518
2214
if __name__ == '__main__':
2519
2215
    main()