/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: 2012-05-24 18:45:45 UTC
  • Revision ID: teddy@recompile.se-20120524184545-68ijg4bprz30r0u8
* plugins.d/mandos-client.c (start_mandos_communication): Eliminate
  warning by first casting to "intptr_t"; this is how GnuTLS's own
  Guile function "set-session-transport-fd!" is implemented.

Show diffs side-by-side

added added

removed removed

Lines of Context:
34
34
from __future__ import (division, absolute_import, print_function,
35
35
                        unicode_literals)
36
36
 
 
37
from future_builtins import *
 
38
 
37
39
import SocketServer as socketserver
38
40
import socket
39
41
import argparse
86
88
    except ImportError:
87
89
        SO_BINDTODEVICE = None
88
90
 
89
 
version = "1.5.3"
 
91
version = "1.5.4"
90
92
stored_state_file = "clients.pickle"
91
93
 
92
94
logger = logging.getLogger()
149
151
    def __enter__(self):
150
152
        return self
151
153
    
152
 
    def __exit__ (self, exc_type, exc_value, traceback):
 
154
    def __exit__(self, exc_type, exc_value, traceback):
153
155
        self._cleanup()
154
156
        return False
155
157
    
377
379
                                 self.server_state_changed)
378
380
        self.server_state_changed(self.server.GetState())
379
381
 
 
382
 
380
383
class AvahiServiceToSyslog(AvahiService):
381
384
    def rename(self):
382
385
        """Add the new name to the syslog messages"""
387
390
                                .format(self.name)))
388
391
        return ret
389
392
 
 
393
 
390
394
def timedelta_to_milliseconds(td):
391
395
    "Convert a datetime.timedelta() to milliseconds"
392
396
    return ((td.days * 24 * 60 * 60 * 1000)
393
397
            + (td.seconds * 1000)
394
398
            + (td.microseconds // 1000))
395
399
 
 
400
 
396
401
class Client(object):
397
402
    """A representation of a client host served by this server.
398
403
    
437
442
    """
438
443
    
439
444
    runtime_expansions = ("approval_delay", "approval_duration",
440
 
                          "created", "enabled", "fingerprint",
441
 
                          "host", "interval", "last_checked_ok",
 
445
                          "created", "enabled", "expires",
 
446
                          "fingerprint", "host", "interval",
 
447
                          "last_approval_request", "last_checked_ok",
442
448
                          "last_enabled", "name", "timeout")
443
449
    client_defaults = { "timeout": "5m",
444
450
                        "extended_timeout": "15m",
570
576
        if getattr(self, "enabled", False):
571
577
            # Already enabled
572
578
            return
573
 
        self.send_changedstate()
574
579
        self.expires = datetime.datetime.utcnow() + self.timeout
575
580
        self.enabled = True
576
581
        self.last_enabled = datetime.datetime.utcnow()
577
582
        self.init_checker()
 
583
        self.send_changedstate()
578
584
    
579
585
    def disable(self, quiet=True):
580
586
        """Disable this client."""
581
587
        if not getattr(self, "enabled", False):
582
588
            return False
583
589
        if not quiet:
584
 
            self.send_changedstate()
585
 
        if not quiet:
586
590
            logger.info("Disabling client %s", self.name)
587
 
        if getattr(self, "disable_initiator_tag", False):
 
591
        if getattr(self, "disable_initiator_tag", None) is not None:
588
592
            gobject.source_remove(self.disable_initiator_tag)
589
593
            self.disable_initiator_tag = None
590
594
        self.expires = None
591
 
        if getattr(self, "checker_initiator_tag", False):
 
595
        if getattr(self, "checker_initiator_tag", None) is not None:
592
596
            gobject.source_remove(self.checker_initiator_tag)
593
597
            self.checker_initiator_tag = None
594
598
        self.stop_checker()
595
599
        self.enabled = False
 
600
        if not quiet:
 
601
            self.send_changedstate()
596
602
        # Do not run this again if called by a gobject.timeout_add
597
603
        return False
598
604
    
602
608
    def init_checker(self):
603
609
        # Schedule a new checker to be started an 'interval' from now,
604
610
        # and every interval from then on.
 
611
        if self.checker_initiator_tag is not None:
 
612
            gobject.source_remove(self.checker_initiator_tag)
605
613
        self.checker_initiator_tag = (gobject.timeout_add
606
614
                                      (self.interval_milliseconds(),
607
615
                                       self.start_checker))
608
616
        # Schedule a disable() when 'timeout' has passed
 
617
        if self.disable_initiator_tag is not None:
 
618
            gobject.source_remove(self.disable_initiator_tag)
609
619
        self.disable_initiator_tag = (gobject.timeout_add
610
620
                                   (self.timeout_milliseconds(),
611
621
                                    self.disable))
642
652
            timeout = self.timeout
643
653
        if self.disable_initiator_tag is not None:
644
654
            gobject.source_remove(self.disable_initiator_tag)
 
655
            self.disable_initiator_tag = None
645
656
        if getattr(self, "enabled", False):
646
657
            self.disable_initiator_tag = (gobject.timeout_add
647
658
                                          (timedelta_to_milliseconds
657
668
        If a checker already exists, leave it running and do
658
669
        nothing."""
659
670
        # The reason for not killing a running checker is that if we
660
 
        # did that, then if a checker (for some reason) started
661
 
        # running slowly and taking more than 'interval' time, the
662
 
        # client would inevitably timeout, since no checker would get
663
 
        # a chance to run to completion.  If we instead leave running
 
671
        # did that, and if a checker (for some reason) started running
 
672
        # slowly and taking more than 'interval' time, then the client
 
673
        # would inevitably timeout, since no checker would get a
 
674
        # chance to run to completion.  If we instead leave running
664
675
        # checkers alone, the checker would have to take more time
665
676
        # than 'timeout' for the client to be disabled, which is as it
666
677
        # should be.
680
691
                                      self.current_checker_command)
681
692
        # Start a new checker if needed
682
693
        if self.checker is None:
 
694
            # Escape attributes for the shell
 
695
            escaped_attrs = dict(
 
696
                (attr, re.escape(unicode(getattr(self, attr))))
 
697
                for attr in
 
698
                self.runtime_expansions)
683
699
            try:
684
 
                # In case checker_command has exactly one % operator
685
 
                command = self.checker_command % self.host
686
 
            except TypeError:
687
 
                # Escape attributes for the shell
688
 
                escaped_attrs = dict(
689
 
                    (attr,
690
 
                     re.escape(unicode(str(getattr(self, attr, "")),
691
 
                                       errors=
692
 
                                       'replace')))
693
 
                    for attr in
694
 
                    self.runtime_expansions)
695
 
                
696
 
                try:
697
 
                    command = self.checker_command % escaped_attrs
698
 
                except TypeError as error:
699
 
                    logger.error('Could not format string "%s"',
700
 
                                 self.checker_command, exc_info=error)
701
 
                    return True # Try again later
 
700
                command = self.checker_command % escaped_attrs
 
701
            except TypeError as error:
 
702
                logger.error('Could not format string "%s"',
 
703
                             self.checker_command, exc_info=error)
 
704
                return True # Try again later
702
705
            self.current_checker_command = command
703
706
            try:
704
707
                logger.info("Starting checker %r for %s",
710
713
                self.checker = subprocess.Popen(command,
711
714
                                                close_fds=True,
712
715
                                                shell=True, cwd="/")
713
 
                self.checker_callback_tag = (gobject.child_watch_add
714
 
                                             (self.checker.pid,
715
 
                                              self.checker_callback,
716
 
                                              data=command))
717
 
                # The checker may have completed before the gobject
718
 
                # watch was added.  Check for this.
719
 
                pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
720
 
                if pid:
721
 
                    gobject.source_remove(self.checker_callback_tag)
722
 
                    self.checker_callback(pid, status, command)
723
716
            except OSError as error:
724
717
                logger.error("Failed to start subprocess",
725
718
                             exc_info=error)
 
719
            self.checker_callback_tag = (gobject.child_watch_add
 
720
                                         (self.checker.pid,
 
721
                                          self.checker_callback,
 
722
                                          data=command))
 
723
            # The checker may have completed before the gobject
 
724
            # watch was added.  Check for this.
 
725
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
 
726
            if pid:
 
727
                gobject.source_remove(self.checker_callback_tag)
 
728
                self.checker_callback(pid, status, command)
726
729
        # Re-run this periodically if run by gobject.timeout_add
727
730
        return True
728
731
    
979
982
                            tag.appendChild(ann_tag)
980
983
                # Add interface annotation tags
981
984
                for annotation, value in dict(
982
 
                    itertools.chain(
983
 
                        *(annotations().iteritems()
984
 
                          for name, annotations in
985
 
                          self._get_all_dbus_things("interface")
986
 
                          if name == if_tag.getAttribute("name")
987
 
                          ))).iteritems():
 
985
                    itertools.chain.from_iterable(
 
986
                        annotations().iteritems()
 
987
                        for name, annotations in
 
988
                        self._get_all_dbus_things("interface")
 
989
                        if name == if_tag.getAttribute("name")
 
990
                        )).iteritems():
988
991
                    ann_tag = document.createElement("annotation")
989
992
                    ann_tag.setAttribute("name", annotation)
990
993
                    ann_tag.setAttribute("value", value)
1013
1016
        return xmlstring
1014
1017
 
1015
1018
 
1016
 
def datetime_to_dbus (dt, variant_level=0):
 
1019
def datetime_to_dbus(dt, variant_level=0):
1017
1020
    """Convert a UTC datetime.datetime() to a D-Bus type."""
1018
1021
    if dt is None:
1019
1022
        return dbus.String("", variant_level = variant_level)
1027
1030
    interface names according to the "alt_interface_names" mapping.
1028
1031
    Usage:
1029
1032
    
1030
 
    @alternate_dbus_names({"org.example.Interface":
1031
 
                               "net.example.AlternateInterface"})
 
1033
    @alternate_dbus_interfaces({"org.example.Interface":
 
1034
                                    "net.example.AlternateInterface"})
1032
1035
    class SampleDBusObject(dbus.service.Object):
1033
1036
        @dbus.service.method("org.example.Interface")
1034
1037
        def SampleDBusMethod():
1333
1336
        return False
1334
1337
    
1335
1338
    def approve(self, value=True):
1336
 
        self.send_changedstate()
1337
1339
        self.approved = value
1338
1340
        gobject.timeout_add(timedelta_to_milliseconds
1339
1341
                            (self.approval_duration),
1340
1342
                            self._reset_approved)
 
1343
        self.send_changedstate()
1341
1344
    
1342
1345
    ## D-Bus methods, signals & properties
1343
1346
    _interface = "se.recompile.Mandos.Client"
1527
1530
    def Timeout_dbus_property(self, value=None):
1528
1531
        if value is None:       # get
1529
1532
            return dbus.UInt64(self.timeout_milliseconds())
 
1533
        old_timeout = self.timeout
1530
1534
        self.timeout = datetime.timedelta(0, 0, 0, value)
1531
 
        # Reschedule timeout
 
1535
        # Reschedule disabling
1532
1536
        if self.enabled:
1533
1537
            now = datetime.datetime.utcnow()
1534
 
            time_to_die = timedelta_to_milliseconds(
1535
 
                (self.last_checked_ok + self.timeout) - now)
1536
 
            if time_to_die <= 0:
 
1538
            self.expires += self.timeout - old_timeout
 
1539
            if self.expires <= now:
1537
1540
                # The timeout has passed
1538
1541
                self.disable()
1539
1542
            else:
1540
 
                self.expires = (now +
1541
 
                                datetime.timedelta(milliseconds =
1542
 
                                                   time_to_die))
1543
1543
                if (getattr(self, "disable_initiator_tag", None)
1544
1544
                    is None):
1545
1545
                    return
1546
1546
                gobject.source_remove(self.disable_initiator_tag)
1547
 
                self.disable_initiator_tag = (gobject.timeout_add
1548
 
                                              (time_to_die,
1549
 
                                               self.disable))
 
1547
                self.disable_initiator_tag = (
 
1548
                    gobject.timeout_add(
 
1549
                        timedelta_to_milliseconds(self.expires - now),
 
1550
                        self.disable))
1550
1551
    
1551
1552
    # ExtendedTimeout - property
1552
1553
    @dbus_service_property(_interface, signature="t",
1740
1741
                    #wait until timeout or approved
1741
1742
                    time = datetime.datetime.now()
1742
1743
                    client.changedstate.acquire()
1743
 
                    (client.changedstate.wait
1744
 
                     (float(client.timedelta_to_milliseconds(delay)
1745
 
                            / 1000)))
 
1744
                    client.changedstate.wait(
 
1745
                        float(timedelta_to_milliseconds(delay)
 
1746
                              / 1000))
1746
1747
                    client.changedstate.release()
1747
1748
                    time2 = datetime.datetime.now()
1748
1749
                    if (time2 - time) >= delay:
1864
1865
    def process_request(self, request, address):
1865
1866
        """Start a new process to process the request."""
1866
1867
        proc = multiprocessing.Process(target = self.sub_process_main,
1867
 
                                       args = (request,
1868
 
                                               address))
 
1868
                                       args = (request, address))
1869
1869
        proc.start()
1870
1870
        return proc
1871
1871
 
1921
1921
                                           str(self.interface
1922
1922
                                               + '\0'))
1923
1923
                except socket.error as error:
1924
 
                    if error[0] == errno.EPERM:
 
1924
                    if error.errno == errno.EPERM:
1925
1925
                        logger.error("No permission to"
1926
1926
                                     " bind to interface %s",
1927
1927
                                     self.interface)
1928
 
                    elif error[0] == errno.ENOPROTOOPT:
 
1928
                    elif error.errno == errno.ENOPROTOOPT:
1929
1929
                        logger.error("SO_BINDTODEVICE not available;"
1930
1930
                                     " cannot bind to interface %s",
1931
1931
                                     self.interface)
 
1932
                    elif error.errno == errno.ENODEV:
 
1933
                        logger.error("Interface %s does not"
 
1934
                                     " exist, cannot bind",
 
1935
                                     self.interface)
1932
1936
                    else:
1933
1937
                        raise
1934
1938
        # Only bind(2) the socket if we really need to.
1993
1997
    
1994
1998
    def handle_ipc(self, source, condition, parent_pipe=None,
1995
1999
                   proc = None, client_object=None):
1996
 
        condition_names = {
1997
 
            gobject.IO_IN: "IN",   # There is data to read.
1998
 
            gobject.IO_OUT: "OUT", # Data can be written (without
1999
 
                                    # blocking).
2000
 
            gobject.IO_PRI: "PRI", # There is urgent data to read.
2001
 
            gobject.IO_ERR: "ERR", # Error condition.
2002
 
            gobject.IO_HUP: "HUP"  # Hung up (the connection has been
2003
 
                                    # broken, usually for pipes and
2004
 
                                    # sockets).
2005
 
            }
2006
 
        conditions_string = ' | '.join(name
2007
 
                                       for cond, name in
2008
 
                                       condition_names.iteritems()
2009
 
                                       if cond & condition)
2010
2000
        # error, or the other end of multiprocessing.Pipe has closed
2011
 
        if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
 
2001
        if condition & (gobject.IO_ERR | gobject.IO_HUP):
2012
2002
            # Wait for other process to exit
2013
2003
            proc.join()
2014
2004
            return False
2298
2288
        os.setgid(gid)
2299
2289
        os.setuid(uid)
2300
2290
    except OSError as error:
2301
 
        if error[0] != errno.EPERM:
 
2291
        if error.errno != errno.EPERM:
2302
2292
            raise error
2303
2293
    
2304
2294
    if debug:
2472
2462
            # "pidfile" was never created
2473
2463
            pass
2474
2464
        del pidfilename
2475
 
        signal.signal(signal.SIGINT, signal.SIG_IGN)
2476
2465
    
2477
2466
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2478
2467
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2578
2567
                del client_settings[client.name]["secret"]
2579
2568
        
2580
2569
        try:
2581
 
            tempfd, tempname = tempfile.mkstemp(suffix=".pickle",
2582
 
                                                prefix="clients-",
2583
 
                                                dir=os.path.dirname
2584
 
                                                (stored_state_path))
2585
 
            with os.fdopen(tempfd, "wb") as stored_state:
 
2570
            with (tempfile.NamedTemporaryFile
 
2571
                  (mode='wb', suffix=".pickle", prefix='clients-',
 
2572
                   dir=os.path.dirname(stored_state_path),
 
2573
                   delete=False)) as stored_state:
2586
2574
                pickle.dump((clients, client_settings), stored_state)
 
2575
                tempname=stored_state.name
2587
2576
            os.rename(tempname, stored_state_path)
2588
2577
        except (IOError, OSError) as e:
2589
2578
            if not debug: