590d18
From 3cec31570b04fa9ece1f3d02768a676c6c2f35ff Mon Sep 17 00:00:00 2001
590d18
From: David Kupka <dkupka@redhat.com>
590d18
Date: Tue, 7 Jul 2015 15:49:27 +0200
590d18
Subject: [PATCH] cermonger: Use private unix socket when DBus SystemBus is not
590d18
 available.
590d18
590d18
https://fedorahosted.org/freeipa/ticket/5095
590d18
590d18
Reviewed-By: Jan Cholasta <jcholast@redhat.com>
590d18
---
590d18
 ipaplatform/base/paths.py |   4 ++
590d18
 ipapython/certmonger.py   | 137 +++++++++++++++++++++++++++++++---------------
590d18
 2 files changed, 98 insertions(+), 43 deletions(-)
590d18
590d18
diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py
590d18
index 9fef3e7a1351dd42895fe560bb3c1bc5a1c852b4..5756040172126438d42275b734f4d766d53048fe 100644
590d18
--- a/ipaplatform/base/paths.py
590d18
+++ b/ipaplatform/base/paths.py
590d18
@@ -348,3 +348,7 @@ class BasePathNamespace(object):
590d18
     BAK2DB = '/usr/sbin/bak2db'
590d18
     DB2BAK = '/usr/sbin/db2bak'
590d18
     KDCPROXY_CONFIG = '/etc/ipa/kdcproxy/kdcproxy.conf'
590d18
+    CERTMONGER = '/usr/sbin/certmonger'
590d18
+
590d18
+
590d18
+path_namespace = BasePathNamespace
590d18
diff --git a/ipapython/certmonger.py b/ipapython/certmonger.py
590d18
index 4baaaa85da08bb943d6b9f0091a1d2acc36b18d6..b37676872a8b983636c7b2dc5590e83c8b08ea98 100644
590d18
--- a/ipapython/certmonger.py
590d18
+++ b/ipapython/certmonger.py
590d18
@@ -27,6 +27,8 @@ import sys
590d18
 import time
590d18
 import dbus
590d18
 import shlex
590d18
+import subprocess
590d18
+import tempfile
590d18
 from ipapython import ipautil
590d18
 from ipapython import dogtag
590d18
 from ipapython.ipa_log_manager import *
590d18
@@ -35,6 +37,7 @@ from ipaplatform import services
590d18
 
590d18
 DBUS_CM_PATH = '/org/fedorahosted/certmonger'
590d18
 DBUS_CM_IF = 'org.fedorahosted.certmonger'
590d18
+DBUS_CM_NAME = 'org.fedorahosted.certmonger'
590d18
 DBUS_CM_REQUEST_IF = 'org.fedorahosted.certmonger.request'
590d18
 DBUS_CM_CA_IF = 'org.fedorahosted.certmonger.ca'
590d18
 DBUS_PROPERTY_IF = 'org.freedesktop.DBus.Properties'
590d18
@@ -44,7 +47,7 @@ class _cm_dbus_object(object):
590d18
     """
590d18
     Auxiliary class for convenient DBus object handling.
590d18
     """
590d18
-    def __init__(self, bus, object_path, object_dbus_interface,
590d18
+    def __init__(self, bus, parent, object_path, object_dbus_interface,
590d18
                  parent_dbus_interface=None, property_interface=False):
590d18
         """
590d18
         bus - DBus bus object, result of dbus.SystemBus() or dbus.SessionBus()
590d18
@@ -60,6 +63,7 @@ class _cm_dbus_object(object):
590d18
         if parent_dbus_interface is None:
590d18
             parent_dbus_interface = object_dbus_interface
590d18
         self.bus = bus
590d18
+        self.parent = parent
590d18
         self.path = object_path
590d18
         self.obj_dbus_if = object_dbus_interface
590d18
         self.parent_dbus_if = parent_dbus_interface
590d18
@@ -69,36 +73,83 @@ class _cm_dbus_object(object):
590d18
             self.prop_if = dbus.Interface(self.obj, DBUS_PROPERTY_IF)
590d18
 
590d18
 
590d18
-def _start_certmonger():
590d18
-    """
590d18
-    Start certmonger daemon. If it's already running systemctl just ignores
590d18
-    the command.
590d18
-    """
590d18
-    if not services.knownservices.certmonger.is_running():
590d18
+class _certmonger(_cm_dbus_object):
590d18
+    """
590d18
+    Create a connection to certmonger.
590d18
+    By default use SystemBus. When not available use private connection
590d18
+    over Unix socket.
590d18
+    This solution is really ugly and should be removed as soon as DBus
590d18
+    SystemBus is available at system install time.
590d18
+    """
590d18
+    timeout = 300
590d18
+
590d18
+    def _start_private_conn(self):
590d18
+        sock_filename = os.path.join(tempfile.mkdtemp(), 'certmonger')
590d18
+        self._proc = subprocess.Popen([paths.CERTMONGER, '-n', '-L', '-P',
590d18
+                                       sock_filename])
590d18
+        for t in range(0, self.timeout, 5):
590d18
+            if os.path.exists(sock_filename):
590d18
+                return "unix:path=%s" % sock_filename
590d18
+            time.sleep(5)
590d18
+        self._stop_private_conn()
590d18
+        raise RuntimeError("Failed to start certmonger: Timed out")
590d18
+
590d18
+    def _stop_private_conn(self):
590d18
+        if self._proc:
590d18
+            retcode = self._proc.poll()
590d18
+            if retcode is not None:
590d18
+                return
590d18
+            self._proc.terminate()
590d18
+            for t in range(0, self.timeout, 5):
590d18
+                retcode = self._proc.poll()
590d18
+                if retcode is not None:
590d18
+                    return
590d18
+                time.sleep(5)
590d18
+            root_logger.error("Failed to stop certmonger.")
590d18
+
590d18
+    def __del__(self):
590d18
+        self._stop_private_conn()
590d18
+
590d18
+    def __init__(self):
590d18
+        self._proc = None
590d18
+        self._bus = None
590d18
         try:
590d18
-            services.knownservices.certmonger.start()
590d18
-        except Exception, e:
590d18
-            root_logger.error('Failed to start certmonger: %s' % e)
590d18
-            raise
590d18
-
590d18
-
590d18
-def _connect_to_certmonger():
590d18
-    """
590d18
-    Start certmonger daemon and connect to it via DBus.
590d18
-    """
590d18
-    try:
590d18
-        _start_certmonger()
590d18
-    except (KeyboardInterrupt, OSError), e:
590d18
-        root_logger.error('Failed to start certmonger: %s' % e)
590d18
-        raise
590d18
-
590d18
-    try:
590d18
-        bus = dbus.SystemBus()
590d18
-        cm = _cm_dbus_object(bus, DBUS_CM_PATH, DBUS_CM_IF)
590d18
-    except dbus.DBusException, e:
590d18
-        root_logger.error("Failed to access certmonger over DBus: %s", e)
590d18
-        raise
590d18
-    return cm
590d18
+            self._bus = dbus.SystemBus()
590d18
+        except dbus.DBusException as e:
590d18
+            err_name = e.get_dbus_name()
590d18
+            if err_name not in ['org.freedesktop.DBus.Error.NoServer',
590d18
+                                'org.freedesktop.DBus.Error.FileNotFound']:
590d18
+                root_logger.error("Failed to connect to certmonger over "
590d18
+                                  "SystemBus: %s" % e)
590d18
+                raise
590d18
+            try:
590d18
+                self._private_sock = self._start_private_conn()
590d18
+                self._bus = dbus.connection.Connection(self._private_sock)
590d18
+            except dbus.DBusException as e:
590d18
+                root_logger.error("Failed to connect to certmonger over "
590d18
+                                  "private socket: %s" % e)
590d18
+                raise
590d18
+        else:
590d18
+            try:
590d18
+                self._bus.get_name_owner(DBUS_CM_NAME)
590d18
+            except dbus.DBusException:
590d18
+                try:
590d18
+                    services.knownservices.certmonger.start()
590d18
+                except Exception as e:
590d18
+                    root_logger.error("Failed to start certmonger: %s" % e)
590d18
+                    raise
590d18
+
590d18
+                for t in range(0, self.timeout, 5):
590d18
+                    try:
590d18
+                        self._bus.get_name_owner(DBUS_CM_NAME)
590d18
+                        break
590d18
+                    except dbus.DBusException:
590d18
+                        pass
590d18
+                    time.sleep(5)
590d18
+                    raise RuntimeError('Failed to start certmonger')
590d18
+
590d18
+        super(_certmonger, self).__init__(self._bus, None, DBUS_CM_PATH,
590d18
+                                          DBUS_CM_IF)
590d18
 
590d18
 
590d18
 def _get_requests(criteria=dict()):
590d18
@@ -108,7 +159,7 @@ def _get_requests(criteria=dict()):
590d18
     if not isinstance(criteria, dict):
590d18
         raise TypeError('"criteria" must be dict.')
590d18
 
590d18
-    cm = _connect_to_certmonger()
590d18
+    cm = _certmonger()
590d18
     requests = []
590d18
     requests_paths = []
590d18
     if 'nickname' in criteria:
590d18
@@ -119,12 +170,12 @@ def _get_requests(criteria=dict()):
590d18
         requests_paths = cm.obj_if.get_requests()
590d18
 
590d18
     for request_path in requests_paths:
590d18
-        request = _cm_dbus_object(cm.bus, request_path, DBUS_CM_REQUEST_IF,
590d18
+        request = _cm_dbus_object(cm.bus, cm, request_path, DBUS_CM_REQUEST_IF,
590d18
                                   DBUS_CM_IF, True)
590d18
         for criterion in criteria:
590d18
             if criterion == 'ca-name':
590d18
                 ca_path = request.obj_if.get_ca()
590d18
-                ca = _cm_dbus_object(cm.bus, ca_path, DBUS_CM_CA_IF,
590d18
+                ca = _cm_dbus_object(cm.bus, cm, ca_path, DBUS_CM_CA_IF,
590d18
                                      DBUS_CM_IF)
590d18
                 value = ca.obj_if.get_nickname()
590d18
             else:
590d18
@@ -133,6 +184,7 @@ def _get_requests(criteria=dict()):
590d18
                 break
590d18
         else:
590d18
             requests.append(request)
590d18
+
590d18
     return requests
590d18
 
590d18
 
590d18
@@ -166,7 +218,7 @@ def get_request_value(request_id, directive):
590d18
     if request:
590d18
         if directive == 'ca-name':
590d18
             ca_path = request.obj_if.get_ca()
590d18
-            ca = _cm_dbus_object(request.bus, ca_path, DBUS_CM_CA_IF,
590d18
+            ca = _cm_dbus_object(request.bus, request, ca_path, DBUS_CM_CA_IF,
590d18
                                  DBUS_CM_IF)
590d18
             return ca.obj_if.get_nickname()
590d18
         else:
590d18
@@ -250,7 +302,7 @@ def request_cert(nssdb, nickname, subject, principal, passwd_fname=None):
590d18
     """
590d18
     Execute certmonger to request a server certificate.
590d18
     """
590d18
-    cm = _connect_to_certmonger()
590d18
+    cm = _certmonger()
590d18
     ca_path = cm.obj_if.find_ca_by_nickname('IPA')
590d18
     if not ca_path:
590d18
         raise RuntimeError('IPA CA not found')
590d18
@@ -264,7 +316,7 @@ def request_cert(nssdb, nickname, subject, principal, passwd_fname=None):
590d18
     result = cm.obj_if.add_request(request_parameters)
590d18
     try:
590d18
         if result[0]:
590d18
-            request = _cm_dbus_object(cm.bus, result[1], DBUS_CM_REQUEST_IF,
590d18
+            request = _cm_dbus_object(cm.bus, cm, result[1], DBUS_CM_REQUEST_IF,
590d18
                                       DBUS_CM_IF, True)
590d18
     except TypeError:
590d18
         root_logger.error('Failed to get create new request.')
590d18
@@ -283,7 +335,7 @@ def start_tracking(nickname, secdir, password_file=None, command=None):
590d18
 
590d18
     Returns certificate nickname.
590d18
     """
590d18
-    cm = _connect_to_certmonger()
590d18
+    cm = _certmonger()
590d18
     params = {'TRACK': True}
590d18
     params['cert-nickname'] = nickname
590d18
     params['cert-database'] = os.path.abspath(secdir)
590d18
@@ -302,7 +354,7 @@ def start_tracking(nickname, secdir, password_file=None, command=None):
590d18
     result = cm.obj_if.add_request(params)
590d18
     try:
590d18
         if result[0]:
590d18
-            request = _cm_dbus_object(cm.bus, result[1], DBUS_CM_REQUEST_IF,
590d18
+            request = _cm_dbus_object(cm.bus, cm, result[1], DBUS_CM_REQUEST_IF,
590d18
                                       DBUS_CM_IF, True)
590d18
     except TypeError, e:
590d18
         root_logger.error('Failed to add new request.')
590d18
@@ -330,8 +382,7 @@ def stop_tracking(secdir, request_id=None, nickname=None):
590d18
         root_logger.error('Failed to get request: %s' % e)
590d18
         raise
590d18
     if request:
590d18
-        cm = _connect_to_certmonger()
590d18
-        cm.obj_if.remove_request(request.path)
590d18
+        request.parent.obj_if.remove_request(request.path)
590d18
 
590d18
 
590d18
 def modify(request_id, profile=None):
590d18
@@ -357,9 +408,9 @@ def _find_IPA_ca():
590d18
     We can use find_request_value because the ca files have the
590d18
     same file format.
590d18
     """
590d18
-    cm = _connect_to_certmonger()
590d18
+    cm = _certmonger()
590d18
     ca_path = cm.obj_if.find_ca_by_nickname('IPA')
590d18
-    return _cm_dbus_object(cm.bus, ca_path, DBUS_CM_CA_IF, DBUS_CM_IF, True)
590d18
+    return _cm_dbus_object(cm.bus, cm, ca_path, DBUS_CM_CA_IF, DBUS_CM_IF, True)
590d18
 
590d18
 
590d18
 def add_principal_to_cas(principal):
590d18
@@ -423,7 +474,7 @@ def dogtag_start_tracking(ca, nickname, pin, pinfile, secdir, pre_command,
590d18
     Both commands can be None.
590d18
     """
590d18
 
590d18
-    cm = _connect_to_certmonger()
590d18
+    cm = _certmonger()
590d18
     certmonger_cmd_template = paths.CERTMONGER_COMMAND_TEMPLATE
590d18
 
590d18
     params = {'TRACK': True}
590d18
-- 
590d18
2.4.3
590d18